Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
033abd6f57 | ||
|
|
779af3ab92 | ||
|
|
ded8d993b7 | ||
|
|
6437d07b03 | ||
|
|
4b29be3f36 | ||
|
|
2ec78d262d | ||
|
|
611c950293 | ||
|
|
0a8e546957 | ||
|
|
4f8cdc2a1e | ||
|
|
3788a055fe | ||
|
|
b71a610f5c | ||
|
|
efcfffc528 | ||
|
|
99d09c824c | ||
|
|
6c5e263d7b | ||
|
|
3fc7ef8f81 | ||
|
|
a38c29b6c8 | ||
|
|
472d8a0df5 | ||
|
|
8ec5da785b | ||
|
|
3a2669e8ae | ||
|
|
f39e51178e | ||
|
|
5f7d7ce8b0 | ||
|
|
d092d64d7a | ||
|
|
99c40ab53d | ||
|
|
26ef28467a | ||
|
|
5f28bd96db | ||
|
|
438cfed70a | ||
|
|
3a3cacfefa | ||
|
|
617909e943 | ||
|
|
e9cbfccec6 | ||
|
|
7b69183a8d | ||
|
|
af7fca797a | ||
|
|
9146ac050b | ||
|
|
ef3a5f58a8 | ||
|
|
f2f85ba354 | ||
|
|
c115c5230e | ||
|
|
65a87cb773 | ||
|
|
9a89e32a95 | ||
|
|
610eb26c11 | ||
|
|
d265a61438 | ||
|
|
2dd4cca363 | ||
|
|
bcae770819 | ||
|
|
bf802b0764 | ||
|
|
ac03e3721d | ||
|
|
31227f4faf | ||
|
|
f6f11f3ef1 | ||
|
|
569584d463 | ||
|
|
ea3e8b79a1 | ||
|
|
74609d44cd | ||
|
|
ea05c6ac47 | ||
|
|
05aed4cab9 | ||
|
|
de7f2f87f7 | ||
|
|
fb8755a636 | ||
|
|
d3d94ccf2e | ||
|
|
00a8e72cfc | ||
|
|
a31b516e25 | ||
|
|
c77b8f45e9 | ||
|
|
a7afd1d2b2 | ||
|
|
3fcddfb61f | ||
|
|
3c9f5954b5 | ||
|
|
3b1b1d1486 | ||
|
|
60f22ca830 | ||
|
|
6ceadfb580 | ||
|
|
7f0a7f0a69 | ||
|
|
3264deb24e | ||
|
|
6c6489280c | ||
|
|
2b88db90aa | ||
|
|
b94b714f81 | ||
|
|
806459f481 | ||
|
|
3a08819f51 | ||
|
|
6f0ddc9d92 | ||
|
|
731f2dc5c7 | ||
|
|
bf643a63c8 | ||
|
|
e4ddc34463 | ||
|
|
6d5d754119 | ||
|
|
e3e631f394 | ||
|
|
6263823e54 | ||
|
|
4dd8b1faa9 | ||
|
|
8038eb3147 | ||
|
|
89742a95db | ||
|
|
60e9e630bd | ||
|
|
e750c619b2 | ||
|
|
93fb83b4cb |
@@ -62,7 +62,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386 gcc-multilib g++-multilib
|
||||
make REDIS_CFLAGS='-Werror' 32bit
|
||||
|
||||
build-libc-malloc:
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install which gcc make
|
||||
dnf -y install which gcc gcc-c++ make
|
||||
make REDIS_CFLAGS='-Werror'
|
||||
|
||||
build-old-chain-jemalloc:
|
||||
@@ -96,6 +96,7 @@ jobs:
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8
|
||||
apt-get install -y make gcc-4.8 g++-4.8
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
|
||||
make CC=gcc REDIS_CFLAGS='-Werror'
|
||||
|
||||
+19
-12
@@ -94,8 +94,9 @@ jobs:
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
apt-get update && apt-get install -y make gcc-13
|
||||
apt-get update && apt-get install -y make gcc-13 g++-13
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100
|
||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 100
|
||||
make CC=gcc REDIS_CFLAGS='-Werror -DREDIS_TEST -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3'
|
||||
- name: testprep
|
||||
run: apt-get install -y tcl8.6 tclx procps
|
||||
@@ -211,7 +212,7 @@ jobs:
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386 g++ gcc-multilib g++-multilib
|
||||
make 32bit REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
- name: testprep
|
||||
run: sudo apt-get install tcl8.6 tclx
|
||||
@@ -456,7 +457,7 @@ jobs:
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 tclx valgrind -y
|
||||
sudo apt-get install tcl8.6 tclx valgrind g++ -y
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
|
||||
@@ -521,7 +522,7 @@ jobs:
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 tclx valgrind -y
|
||||
sudo apt-get install tcl8.6 tclx valgrind g++ -y
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
|
||||
@@ -678,7 +679,7 @@ jobs:
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install which gcc make
|
||||
dnf -y install which gcc make g++
|
||||
make REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
@@ -720,7 +721,7 @@ jobs:
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install which gcc make openssl-devel openssl
|
||||
dnf -y install which gcc make openssl-devel openssl g++
|
||||
make BUILD_TLS=module REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
@@ -767,7 +768,7 @@ jobs:
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install which gcc make openssl-devel openssl
|
||||
dnf -y install which gcc make openssl-devel openssl g++
|
||||
make BUILD_TLS=module REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
@@ -907,6 +908,9 @@ jobs:
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'freebsd')
|
||||
timeout-minutes: 14400
|
||||
env:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -925,7 +929,7 @@ jobs:
|
||||
version: 13.2
|
||||
shell: bash
|
||||
run: |
|
||||
sudo pkg install -y bash gmake lang/tcl86 lang/tclx
|
||||
sudo pkg install -y bash gmake lang/tcl86 lang/tclx gcc
|
||||
gmake
|
||||
./runtest --single unit/keyspace --single unit/auth --single unit/networking --single unit/protocol
|
||||
|
||||
@@ -1080,8 +1084,9 @@ jobs:
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8
|
||||
apt-get install -y make gcc-4.8 g++-4.8
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
|
||||
make CC=gcc REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: apt-get install -y tcl tcltls tclx
|
||||
@@ -1128,9 +1133,10 @@ jobs:
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8 openssl libssl-dev
|
||||
apt-get install -y make gcc-4.8 g++-4.8 openssl libssl-dev
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
make CC=gcc BUILD_TLS=module REDIS_CFLAGS='-Werror'
|
||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
|
||||
make CC=gcc CXX=g++ BUILD_TLS=module REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
apt-get install -y tcl tcltls tclx
|
||||
@@ -1182,8 +1188,9 @@ jobs:
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8 openssl libssl-dev
|
||||
apt-get install -y make gcc-4.8 g++-4.8 openssl libssl-dev
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
|
||||
make BUILD_TLS=module CC=gcc REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: redis_docs_sync
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
redis_docs_sync:
|
||||
if: github.repository == 'redis/redis'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.DOCS_APP_ID }}
|
||||
private-key: ${{ secrets.DOCS_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Invoke workflow on redis/docs
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
RELEASE_NAME: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
LATEST_RELEASE=$(
|
||||
curl -Ls \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${GH_TOKEN}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/redis/redis/releases/latest \
|
||||
| jq -r '.tag_name'
|
||||
)
|
||||
|
||||
if [[ "${LATEST_RELEASE}" == "${RELEASE_NAME}" ]]; then
|
||||
gh workflow run -R redis/docs redis_docs_sync.yaml -f release="${RELEASE_NAME}"
|
||||
fi
|
||||
@@ -30,6 +30,7 @@ deps/lua/src/luac
|
||||
deps/lua/src/liblua.a
|
||||
deps/hdr_histogram/libhdrhistogram.a
|
||||
deps/fpconv/libfpconv.a
|
||||
deps/fast_float/libfast_float.a
|
||||
tests/tls/*
|
||||
.make-*
|
||||
.prerequisites
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
# Top level makefile, the real shit is at src/Makefile
|
||||
# Top level makefile, the real stuff is at ./src/Makefile and in ./modules/Makefile
|
||||
|
||||
SUBDIRS = src
|
||||
ifeq ($(BUILD_WITH_MODULES), yes)
|
||||
SUBDIRS += modules
|
||||
endif
|
||||
|
||||
default: all
|
||||
|
||||
.DEFAULT:
|
||||
cd src && $(MAKE) $@
|
||||
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done
|
||||
|
||||
install:
|
||||
cd src && $(MAKE) $@
|
||||
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done
|
||||
|
||||
.PHONY: install
|
||||
|
||||
@@ -16,7 +16,6 @@ Another good example is to think of Redis as a more complex version of memcached
|
||||
If you want to know more, this is a list of selected starting points:
|
||||
|
||||
* Introduction to Redis data types. https://redis.io/topics/data-types-intro
|
||||
* Try Redis directly inside your browser. https://try.redis.io
|
||||
* The full list of Redis commands. https://redis.io/commands
|
||||
* There is much more inside the official Redis documentation. https://redis.io/documentation
|
||||
|
||||
|
||||
Vendored
+7
@@ -42,6 +42,7 @@ distclean:
|
||||
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
|
||||
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
|
||||
-(cd fpconv && $(MAKE) clean) > /dev/null || true
|
||||
-(cd fast_float && $(MAKE) clean) > /dev/null || true
|
||||
-(rm -f .make-*)
|
||||
|
||||
.PHONY: distclean
|
||||
@@ -74,6 +75,12 @@ fpconv: .make-prerequisites
|
||||
|
||||
.PHONY: fpconv
|
||||
|
||||
fast_float: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd fast_float && $(MAKE) libfast_float
|
||||
|
||||
.PHONY: fast_float
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# Make isinf() available
|
||||
LUA_CFLAGS= -D__C99FEATURES__=1
|
||||
|
||||
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
# Fallback to gcc/g++ when $CC or $CXX is not in $PATH.
|
||||
CC ?= gcc
|
||||
CXX ?= g++
|
||||
|
||||
CFLAGS=-Wall -O3
|
||||
# This avoids loosing the fastfloat specific compile flags when we override the CFLAGS via the main project
|
||||
FASTFLOAT_CFLAGS=-std=c++11 -DFASTFLOAT_ALLOWS_LEADING_PLUS
|
||||
LDFLAGS=
|
||||
|
||||
libfast_float: fast_float_strtod.o
|
||||
$(AR) -r libfast_float.a fast_float_strtod.o
|
||||
|
||||
32bit: CFLAGS += -m32
|
||||
32bit: LDFLAGS += -m32
|
||||
32bit: libfast_float
|
||||
|
||||
fast_float_strtod.o: fast_float_strtod.cpp
|
||||
$(CXX) $(CFLAGS) $(FASTFLOAT_CFLAGS) -c fast_float_strtod.cpp $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f *.a
|
||||
rm -f *.h.gch
|
||||
rm -rf *.dSYM
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
README for fast_float v6.1.4
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
We're using the fast_float library[1] in our (compiled-in)
|
||||
floating-point fast_float_strtod implementation for faster and more
|
||||
portable parsing of 64 decimal strings.
|
||||
|
||||
The single file fast_float.h is an amalgamation of the entire library,
|
||||
which can be (re)generated with the amalgamate.py script (from the
|
||||
fast_float repository) via the command
|
||||
|
||||
```
|
||||
git clone https://github.com/fastfloat/fast_float
|
||||
cd fast_float
|
||||
git checkout v6.1.4
|
||||
python3 ./script/amalgamate.py --license=MIT \
|
||||
> $REDIS_SRC/deps/fast_float/fast_float.h
|
||||
```
|
||||
|
||||
[1]: https://github.com/fastfloat/fast_float
|
||||
Vendored
+3838
File diff suppressed because it is too large
Load Diff
+32
@@ -0,0 +1,32 @@
|
||||
#include "fast_float.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <cerrno>
|
||||
|
||||
/* Convert NPTR to a double using the fast_float library.
|
||||
*
|
||||
* This function behaves similarly to the standard strtod function, converting
|
||||
* the initial portion of the string pointed to by `nptr` to a `double` value,
|
||||
* using the fast_float library for high performance. If the conversion fails,
|
||||
* errno is set to EINVAL error code.
|
||||
*
|
||||
* @param nptr A pointer to the null-terminated byte string to be interpreted.
|
||||
* @param endptr A pointer to a pointer to character. If `endptr` is not NULL,
|
||||
* it will point to the character after the last character used
|
||||
* in the conversion.
|
||||
* @return The converted value as a double. If no valid conversion could
|
||||
* be performed, returns 0.0.
|
||||
* If ENDPTR is not NULL, a pointer to the character after the last one used
|
||||
* in the number is put in *ENDPTR. */
|
||||
extern "C" double fast_float_strtod(const char *nptr, char **endptr) {
|
||||
double result = 0.0;
|
||||
auto answer = fast_float::from_chars(nptr, nptr + strlen(nptr), result);
|
||||
if (answer.ec != std::errc()) {
|
||||
errno = EINVAL; // Fallback to for other errors
|
||||
}
|
||||
if (endptr != NULL) {
|
||||
*endptr = (char *)answer.ptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
|
||||
#ifndef __FAST_FLOAT_STRTOD_H__
|
||||
#define __FAST_FLOAT_STRTOD_H__
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
double fast_float_strtod(const char *in, char **out);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __FAST_FLOAT_STRTOD_H__ */
|
||||
Vendored
+1
-1
@@ -296,7 +296,7 @@ static int isUnsupportedTerm(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Raw mode: 1960 magic shit. */
|
||||
/* Raw mode: 1960's magic. */
|
||||
static int enableRawMode(int fd) {
|
||||
if (getenv("FAKETTY_WITH_PROMPT") != NULL) {
|
||||
return 0;
|
||||
|
||||
Vendored
+1
@@ -132,6 +132,7 @@ static int bit_tohex(lua_State *L)
|
||||
const char *hexdigits = "0123456789abcdef";
|
||||
char buf[8];
|
||||
int i;
|
||||
if (n == INT32_MIN) n = INT32_MIN+1;
|
||||
if (n < 0) { n = -n; hexdigits = "0123456789ABCDEF"; }
|
||||
if (n > 8) n = 8;
|
||||
for (i = (int)n; --i >= 0; ) { buf[i] = hexdigits[b & 15]; b >>= 4; }
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
|
||||
SUBDIRS = redisjson redistimeseries redisbloom redisearch
|
||||
|
||||
define submake
|
||||
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $(1); done
|
||||
endef
|
||||
|
||||
all: prepare_source
|
||||
$(call submake,$@)
|
||||
|
||||
get_source:
|
||||
$(call submake,$@)
|
||||
|
||||
prepare_source: get_source handle-werrors setup_environment
|
||||
|
||||
clean:
|
||||
$(call submake,$@)
|
||||
|
||||
distclean: clean_environment
|
||||
$(call submake,$@)
|
||||
|
||||
pristine:
|
||||
$(call submake,$@)
|
||||
|
||||
install:
|
||||
$(call submake,$@)
|
||||
|
||||
setup_environment: install-rust handle-werrors
|
||||
|
||||
clean_environment: uninstall-rust
|
||||
|
||||
# Keep all of the Rust stuff in one place
|
||||
install-rust:
|
||||
ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
|
||||
@RUST_VERSION=1.80.1; \
|
||||
ARCH="$$(uname -m)"; \
|
||||
if ldd --version 2>&1 | grep -q musl; then LIBC_TYPE="musl"; else LIBC_TYPE="gnu"; fi; \
|
||||
echo "Detected architecture: $${ARCH} and libc: $${LIBC_TYPE}"; \
|
||||
case "$${ARCH}" in \
|
||||
'x86_64') \
|
||||
if [ "$${LIBC_TYPE}" = "musl" ]; then \
|
||||
RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-musl"; \
|
||||
RUST_SHA256="37bbec6a7b9f55fef79c451260766d281a7a5b9d2e65c348bbc241127cf34c8d"; \
|
||||
else \
|
||||
RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-gnu"; \
|
||||
RUST_SHA256="85e936d5d36970afb80756fa122edcc99bd72a88155f6bdd514f5d27e778e00a"; \
|
||||
fi ;; \
|
||||
'aarch64') \
|
||||
if [ "$${LIBC_TYPE}" = "musl" ]; then \
|
||||
RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-musl"; \
|
||||
RUST_SHA256="dd668c2d82f77c5458deb023932600fae633fff8d7f876330e01bc47e9976d17"; \
|
||||
else \
|
||||
RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-gnu"; \
|
||||
RUST_SHA256="2e89bad7857711a1c11d017ea28fbfeec54076317763901194f8f5decbac1850"; \
|
||||
fi ;; \
|
||||
*) echo >&2 "Unsupported architecture: '$${ARCH}'"; exit 1 ;; \
|
||||
esac; \
|
||||
echo "Downloading and installing Rust standalone installer: $${RUST_INSTALLER}"; \
|
||||
wget --quiet -O $${RUST_INSTALLER}.tar.xz https://static.rust-lang.org/dist/$${RUST_INSTALLER}.tar.xz; \
|
||||
echo "$${RUST_SHA256} $${RUST_INSTALLER}.tar.xz" | sha256sum -c --quiet || { echo "Rust standalone installer checksum failed!"; exit 1; }; \
|
||||
tar -xf $${RUST_INSTALLER}.tar.xz; \
|
||||
(cd $${RUST_INSTALLER} && ./install.sh); \
|
||||
rm -rf $${RUST_INSTALLER}
|
||||
endif
|
||||
|
||||
uninstall-rust:
|
||||
ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
|
||||
@if [ -x "/usr/local/lib/rustlib/uninstall.sh" ]; then \
|
||||
echo "Uninstalling Rust using uninstall.sh script"; \
|
||||
rm -rf ~/.cargo; \
|
||||
/usr/local/lib/rustlib/uninstall.sh; \
|
||||
else \
|
||||
echo "WARNING: Rust toolchain not found or uninstall script is missing."; \
|
||||
fi
|
||||
endif
|
||||
|
||||
handle-werrors: get_source
|
||||
ifeq ($(DISABLE_WERRORS),yes)
|
||||
@echo "Disabling -Werror for all modules"
|
||||
@for dir in $(SUBDIRS); do \
|
||||
echo "Processing $$dir"; \
|
||||
find $$dir/src -type f \
|
||||
\( -name "Makefile" \
|
||||
-o -name "*.mk" \
|
||||
-o -name "CMakeLists.txt" \) \
|
||||
-exec sed -i 's/-Werror//g' {} +; \
|
||||
done
|
||||
endif
|
||||
|
||||
.PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust handle-werrors
|
||||
@@ -0,0 +1,49 @@
|
||||
PREFIX ?= /usr/local
|
||||
INSTALL_DIR ?= $(DESTDIR)$(PREFIX)/lib/redis/modules
|
||||
INSTALL ?= install
|
||||
|
||||
# This logic *partially* follows the current module build system. It is a bit awkward and
|
||||
# should be changed if/when the modules' build process is refactored.
|
||||
|
||||
ARCH_MAP_x86_64 := x64
|
||||
ARCH_MAP_i386 := x86
|
||||
ARCH_MAP_i686 := x86
|
||||
ARCH_MAP_aarch64 := arm64v8
|
||||
ARCH_MAP_arm64 := arm64v8
|
||||
|
||||
OS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
||||
ARCH := $(ARCH_MAP_$(shell uname -m))
|
||||
ifeq ($(ARCH),)
|
||||
$(error Unrecognized CPU architecture $(shell uname -m))
|
||||
endif
|
||||
|
||||
FULL_VARIANT := $(OS)-$(ARCH)-release
|
||||
|
||||
# Common rules for all modules, based on per-module configuration
|
||||
|
||||
all: $(TARGET_MODULE)
|
||||
|
||||
$(TARGET_MODULE): get_source
|
||||
$(MAKE) -C $(SRC_DIR)
|
||||
|
||||
get_source: $(SRC_DIR)/.prepared
|
||||
|
||||
$(SRC_DIR)/.prepared:
|
||||
mkdir -p $(SRC_DIR)
|
||||
git clone --recursive --depth 1 --branch $(MODULE_VERSION) $(MODULE_REPO) $(SRC_DIR)
|
||||
touch $@
|
||||
|
||||
clean:
|
||||
-$(MAKE) -C $(SRC_DIR) clean
|
||||
|
||||
distclean:
|
||||
-$(MAKE) -C $(SRC_DIR) distclean
|
||||
|
||||
pristine:
|
||||
-rm -rf $(SRC_DIR)
|
||||
|
||||
install: $(TARGET_MODULE)
|
||||
mkdir -p $(INSTALL_DIR)
|
||||
$(INSTALL) -m 0755 -D $(TARGET_MODULE) $(INSTALL_DIR)
|
||||
|
||||
.PHONY: all clean distclean pristine install
|
||||
@@ -0,0 +1,6 @@
|
||||
SRC_DIR = src
|
||||
MODULE_VERSION = v7.99.1
|
||||
MODULE_REPO = https://github.com/redisbloom/redisbloom
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so
|
||||
|
||||
include ../common.mk
|
||||
@@ -0,0 +1,7 @@
|
||||
SRC_DIR = src
|
||||
MODULE_VERSION = v7.99.1
|
||||
MODULE_REPO = https://github.com/redisearch/redisearch
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
|
||||
|
||||
include ../common.mk
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
SRC_DIR = src
|
||||
MODULE_VERSION = v7.99.1
|
||||
MODULE_REPO = https://github.com/redisjson/redisjson
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/rejson.so
|
||||
|
||||
include ../common.mk
|
||||
|
||||
$(SRC_DIR)/.cargo_fetched:
|
||||
cd $(SRC_DIR) && cargo fetch
|
||||
|
||||
get_source: $(SRC_DIR)/.cargo_fetched
|
||||
@@ -0,0 +1,6 @@
|
||||
SRC_DIR = src
|
||||
MODULE_VERSION = v7.99.1
|
||||
MODULE_REPO = https://github.com/redistimeseries/redistimeseries
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so
|
||||
|
||||
include ../common.mk
|
||||
+10
-21
@@ -1291,38 +1291,27 @@ lazyfree-lazy-user-flush no
|
||||
# in different I/O threads. Since especially writing is so slow, normally
|
||||
# Redis users use pipelining in order to speed up the Redis performances per
|
||||
# core, and spawn multiple instances in order to scale more. Using I/O
|
||||
# threads it is possible to easily speedup two times Redis without resorting
|
||||
# threads it is possible to easily speedup several times Redis without resorting
|
||||
# to pipelining nor sharding of the instance.
|
||||
#
|
||||
# By default threading is disabled, we suggest enabling it only in machines
|
||||
# that have at least 4 or more cores, leaving at least one spare core.
|
||||
# Using more than 8 threads is unlikely to help much. We also recommend using
|
||||
# threaded I/O only if you actually have performance problems, with Redis
|
||||
# instances being able to use a quite big percentage of CPU time, otherwise
|
||||
# there is no point in using this feature.
|
||||
# We also recommend using threaded I/O only if you actually have performance
|
||||
# problems, with Redis instances being able to use a quite big percentage of
|
||||
# CPU time, otherwise there is no point in using this feature.
|
||||
#
|
||||
# So for instance if you have a four cores boxes, try to use 2 or 3 I/O
|
||||
# threads, if you have a 8 cores, try to use 6 threads. In order to
|
||||
# So for instance if you have a four cores boxes, try to use 3 I/O
|
||||
# threads, if you have a 8 cores, try to use 7 threads. In order to
|
||||
# enable I/O threads use the following configuration directive:
|
||||
#
|
||||
# io-threads 4
|
||||
#
|
||||
# Setting io-threads to 1 will just use the main thread as usual.
|
||||
# When I/O threads are enabled, we only use threads for writes, that is
|
||||
# to thread the write(2) syscall and transfer the client buffers to the
|
||||
# socket. However it is also possible to enable threading of reads and
|
||||
# protocol parsing using the following configuration directive, by setting
|
||||
# it to yes:
|
||||
# When I/O threads are enabled, we not only use threads for writes, that
|
||||
# is to thread the write(2) syscall and transfer the client buffers to the
|
||||
# socket, but also use threads for reads and protocol parsing.
|
||||
#
|
||||
# io-threads-do-reads no
|
||||
#
|
||||
# Usually threading reads doesn't help much.
|
||||
#
|
||||
# NOTE 1: This configuration directive cannot be changed at runtime via
|
||||
# CONFIG SET. Also, this feature currently does not work when SSL is
|
||||
# enabled.
|
||||
#
|
||||
# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make
|
||||
# NOTE: If you want to test the Redis speedup using redis-benchmark, make
|
||||
# sure you also run the benchmark itself in threaded mode, using the
|
||||
# --threads option to match the number of Redis threads, otherwise you'll not
|
||||
# be able to notice the improvements.
|
||||
|
||||
+5
-5
@@ -34,7 +34,7 @@ endif
|
||||
ifneq ($(OPTIMIZATION),-O0)
|
||||
OPTIMIZATION+=-fno-omit-frame-pointer
|
||||
endif
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv fast_float
|
||||
NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
@@ -127,7 +127,7 @@ endif
|
||||
|
||||
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_LDFLAGS=$(LDFLAGS) $(OPT) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm
|
||||
FINAL_LIBS=-lm -lstdc++
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
# Linux ARM32 needs -latomic at linking time
|
||||
@@ -235,7 +235,7 @@ ifdef OPENSSL_PREFIX
|
||||
endif
|
||||
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv -I../deps/fast_float
|
||||
|
||||
# Determine systemd support and/or build preference (defaulting to auto-detection)
|
||||
BUILD_WITH_SYSTEMD=no
|
||||
@@ -354,7 +354,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
|
||||
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
|
||||
REDIS_SERVER_OBJ=threads_mngr.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o mstr.o kvstore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_legacy.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o
|
||||
REDIS_SERVER_OBJ=threads_mngr.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o kvstore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_legacy.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o
|
||||
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o cli_commands.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
|
||||
@@ -409,7 +409,7 @@ endif
|
||||
|
||||
# redis-server
|
||||
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/fast_float/libfast_float.a $(FINAL_LIBS)
|
||||
|
||||
# redis-sentinel
|
||||
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
|
||||
|
||||
@@ -1066,7 +1066,7 @@ int ACLSetSelector(aclSelector *selector, const char* op, size_t oplen) {
|
||||
flags |= ACL_READ_PERMISSION;
|
||||
} else if (toupper(op[offset]) == 'W' && !(flags & ACL_WRITE_PERMISSION)) {
|
||||
flags |= ACL_WRITE_PERMISSION;
|
||||
} else if (op[offset] == '~') {
|
||||
} else if (op[offset] == '~' && flags) {
|
||||
offset++;
|
||||
break;
|
||||
} else {
|
||||
@@ -2762,7 +2762,6 @@ void aclCatWithFlags(client *c, dict *commands, uint64_t cflag, int *arraylen) {
|
||||
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = dictGetVal(de);
|
||||
if (cmd->flags & CMD_MODULE) continue;
|
||||
if (cmd->acl_categories & cflag) {
|
||||
addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
|
||||
(*arraylen)++;
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#define INITIAL_EVENT 1024
|
||||
aeEventLoop *aeCreateEventLoop(int setsize) {
|
||||
aeEventLoop *eventLoop;
|
||||
int i;
|
||||
@@ -50,8 +50,9 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
||||
monotonicInit(); /* just in case the calling app didn't initialize */
|
||||
|
||||
if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
|
||||
eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
|
||||
eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
|
||||
eventLoop->nevents = setsize < INITIAL_EVENT ? setsize : INITIAL_EVENT;
|
||||
eventLoop->events = zmalloc(sizeof(aeFileEvent)*eventLoop->nevents);
|
||||
eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*eventLoop->nevents);
|
||||
if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
|
||||
eventLoop->setsize = setsize;
|
||||
eventLoop->timeEventHead = NULL;
|
||||
@@ -61,10 +62,11 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
||||
eventLoop->beforesleep = NULL;
|
||||
eventLoop->aftersleep = NULL;
|
||||
eventLoop->flags = 0;
|
||||
memset(eventLoop->privdata, 0, sizeof(eventLoop->privdata));
|
||||
if (aeApiCreate(eventLoop) == -1) goto err;
|
||||
/* Events with mask == AE_NONE are not set. So let's initialize the
|
||||
* vector with it. */
|
||||
for (i = 0; i < setsize; i++)
|
||||
for (i = 0; i < eventLoop->nevents; i++)
|
||||
eventLoop->events[i].mask = AE_NONE;
|
||||
return eventLoop;
|
||||
|
||||
@@ -102,20 +104,19 @@ void aeSetDontWait(aeEventLoop *eventLoop, int noWait) {
|
||||
*
|
||||
* Otherwise AE_OK is returned and the operation is successful. */
|
||||
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) {
|
||||
int i;
|
||||
|
||||
if (setsize == eventLoop->setsize) return AE_OK;
|
||||
if (eventLoop->maxfd >= setsize) return AE_ERR;
|
||||
if (aeApiResize(eventLoop,setsize) == -1) return AE_ERR;
|
||||
|
||||
eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize);
|
||||
eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize);
|
||||
eventLoop->setsize = setsize;
|
||||
|
||||
/* Make sure that if we created new slots, they are initialized with
|
||||
* an AE_NONE mask. */
|
||||
for (i = eventLoop->maxfd+1; i < setsize; i++)
|
||||
eventLoop->events[i].mask = AE_NONE;
|
||||
/* If the current allocated space is larger than the requested size,
|
||||
* we need to shrink it to the requested size. */
|
||||
if (setsize < eventLoop->nevents) {
|
||||
eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize);
|
||||
eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize);
|
||||
eventLoop->nevents = setsize;
|
||||
}
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
@@ -147,6 +148,22 @@ int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||
errno = ERANGE;
|
||||
return AE_ERR;
|
||||
}
|
||||
|
||||
/* Resize the events and fired arrays if the file
|
||||
* descriptor exceeds the current number of events. */
|
||||
if (unlikely(fd >= eventLoop->nevents)) {
|
||||
int newnevents = eventLoop->nevents;
|
||||
newnevents = (newnevents * 2 > fd + 1) ? newnevents * 2 : fd + 1;
|
||||
newnevents = (newnevents > eventLoop->setsize) ? eventLoop->setsize : newnevents;
|
||||
eventLoop->events = zrealloc(eventLoop->events, sizeof(aeFileEvent) * newnevents);
|
||||
eventLoop->fired = zrealloc(eventLoop->fired, sizeof(aeFiredEvent) * newnevents);
|
||||
|
||||
/* Initialize new slots with an AE_NONE mask */
|
||||
for (int i = eventLoop->nevents; i < newnevents; i++)
|
||||
eventLoop->events[i].mask = AE_NONE;
|
||||
eventLoop->nevents = newnevents;
|
||||
}
|
||||
|
||||
aeFileEvent *fe = &eventLoop->events[fd];
|
||||
|
||||
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
|
||||
|
||||
@@ -79,6 +79,7 @@ typedef struct aeEventLoop {
|
||||
int maxfd; /* highest file descriptor currently registered */
|
||||
int setsize; /* max number of file descriptors tracked */
|
||||
long long timeEventNextId;
|
||||
int nevents; /* Size of Registered events */
|
||||
aeFileEvent *events; /* Registered events */
|
||||
aeFiredEvent *fired; /* Fired events */
|
||||
aeTimeEvent *timeEventHead;
|
||||
@@ -87,6 +88,7 @@ typedef struct aeEventLoop {
|
||||
aeBeforeSleepProc *beforesleep;
|
||||
aeBeforeSleepProc *aftersleep;
|
||||
int flags;
|
||||
void *privdata[2];
|
||||
} aeEventLoop;
|
||||
|
||||
/* Prototypes */
|
||||
|
||||
@@ -997,6 +997,29 @@ int startAppendOnly(void) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
void startAppendOnlyWithRetry(void) {
|
||||
unsigned int tries, max_tries = 10;
|
||||
for (tries = 0; tries < max_tries; ++tries) {
|
||||
if (startAppendOnly() == C_OK)
|
||||
break;
|
||||
serverLog(LL_WARNING, "Failed to enable AOF! Trying it again in one second.");
|
||||
sleep(1);
|
||||
}
|
||||
if (tries == max_tries) {
|
||||
serverLog(LL_WARNING, "FATAL: AOF can't be turned on. Exiting now.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Called after "appendonly" config is changed. */
|
||||
void applyAppendOnlyConfig(void) {
|
||||
if (!server.aof_enabled && server.aof_state != AOF_OFF) {
|
||||
stopAppendOnly();
|
||||
} else if (server.aof_enabled && server.aof_state == AOF_OFF) {
|
||||
startAppendOnlyWithRetry();
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a wrapper to the write syscall in order to retry on short writes
|
||||
* or if the syscall gets interrupted. It could look strange that we retry
|
||||
* on short writes given that we are writing to a block device: normally if
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ const char *ascii_logo =
|
||||
" _._ \n"
|
||||
" _.-``__ ''-._ \n"
|
||||
" _.-`` `. `_. ''-._ Redis Community Edition \n"
|
||||
" .-`` .-```. ```\\/ _.,_ ''-._ %s (%s/%d) %s bit\n"
|
||||
" .-`` .-```. ```\\/ _.,_ ''-._ %s (%s/%d) %s bit\n"
|
||||
" ( ' , .-` | `, ) Running in %s mode\n"
|
||||
" |`-._`-...-` __...-.``-._|'` _.-'| Port: %d\n"
|
||||
" | `-._ `._ / _.-' | PID: %ld\n"
|
||||
|
||||
@@ -81,6 +81,7 @@ static int job_comp_pipe[2]; /* Pipe used to awake the event loop */
|
||||
typedef struct bio_comp_item {
|
||||
comp_fn *func; /* callback after completion job will be processed */
|
||||
uint64_t arg; /* user data to be passed to the function */
|
||||
void *ptr; /* user pointer to be passed to the function */
|
||||
} bio_comp_item;
|
||||
|
||||
/* This structure represents a background Job. It is only used locally to this
|
||||
@@ -110,6 +111,7 @@ typedef union bio_job {
|
||||
int type; /* header */
|
||||
comp_fn *fn; /* callback. Handover to main thread to cb as notify for job completion */
|
||||
uint64_t arg; /* callback arguments */
|
||||
void *ptr; /* callback pointer */
|
||||
} comp_rq;
|
||||
} bio_job;
|
||||
|
||||
@@ -200,7 +202,7 @@ void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) {
|
||||
bioSubmitJob(BIO_LAZY_FREE, job);
|
||||
}
|
||||
|
||||
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data) {
|
||||
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data, void *user_ptr) {
|
||||
int type;
|
||||
switch (assigned_worker) {
|
||||
case BIO_WORKER_CLOSE_FILE:
|
||||
@@ -219,6 +221,7 @@ void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_
|
||||
bio_job *job = zmalloc(sizeof(*job));
|
||||
job->comp_rq.fn = func;
|
||||
job->comp_rq.arg = user_data;
|
||||
job->comp_rq.ptr = user_ptr;
|
||||
bioSubmitJob(type, job);
|
||||
}
|
||||
|
||||
@@ -339,6 +342,7 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
bio_comp_item *comp_rsp = zmalloc(sizeof(bio_comp_item));
|
||||
comp_rsp->func = job->comp_rq.fn;
|
||||
comp_rsp->arg = job->comp_rq.arg;
|
||||
comp_rsp->ptr = job->comp_rq.ptr;
|
||||
|
||||
/* just write it to completion job responses */
|
||||
pthread_mutex_lock(&bio_mutex_comp);
|
||||
@@ -432,7 +436,7 @@ void bioPipeReadJobCompList(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
listNode *ln = listFirst(tmp_list);
|
||||
bio_comp_item *rsp = ln->value;
|
||||
listDelNode(tmp_list, ln);
|
||||
rsp->func(rsp->arg);
|
||||
rsp->func(rsp->arg, rsp->ptr);
|
||||
zfree(rsp);
|
||||
}
|
||||
listRelease(tmp_list);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#define __BIO_H
|
||||
|
||||
typedef void lazy_free_fn(void *args[]);
|
||||
typedef void comp_fn(uint64_t user_data);
|
||||
typedef void comp_fn(uint64_t user_data, void *user_ptr);
|
||||
|
||||
typedef enum bio_worker_t {
|
||||
BIO_WORKER_CLOSE_FILE = 0,
|
||||
@@ -40,7 +40,7 @@ void bioCreateCloseJob(int fd, int need_fsync, int need_reclaim_cache);
|
||||
void bioCreateCloseAofJob(int fd, long long offset, int need_reclaim_cache);
|
||||
void bioCreateFsyncJob(int fd, long long offset, int need_reclaim_cache);
|
||||
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...);
|
||||
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data);
|
||||
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data, void *user_ptr);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
+72
-17
@@ -16,18 +16,49 @@
|
||||
/* Count number of bits set in the binary array pointed by 's' and long
|
||||
* 'count' bytes. The implementation of this function is required to
|
||||
* work with an input string length up to 512 MB or more (server.proto_max_bulk_len) */
|
||||
ATTRIBUTE_TARGET_POPCNT
|
||||
long long redisPopcount(void *s, long count) {
|
||||
long long bits = 0;
|
||||
unsigned char *p = s;
|
||||
uint32_t *p4;
|
||||
#if defined(HAVE_POPCNT)
|
||||
int use_popcnt = __builtin_cpu_supports("popcnt"); /* Check if CPU supports POPCNT instruction. */
|
||||
#else
|
||||
int use_popcnt = 0; /* Assume CPU does not support POPCNT if
|
||||
* __builtin_cpu_supports() is not available. */
|
||||
#endif
|
||||
static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
|
||||
|
||||
/* Count initial bytes not aligned to 32 bit. */
|
||||
while((unsigned long)p & 3 && count) {
|
||||
|
||||
/* Count initial bytes not aligned to 64-bit when using the POPCNT instruction,
|
||||
* otherwise align to 32-bit. */
|
||||
int align = use_popcnt ? 7 : 3;
|
||||
while ((unsigned long)p & align && count) {
|
||||
bits += bitsinbyte[*p++];
|
||||
count--;
|
||||
}
|
||||
|
||||
if (likely(use_popcnt)) {
|
||||
/* Use separate counters to make the CPU think there are no
|
||||
* dependencies between these popcnt operations. */
|
||||
uint64_t cnt[4];
|
||||
memset(cnt, 0, sizeof(cnt));
|
||||
|
||||
/* Count bits 32 bytes at a time by using popcnt.
|
||||
* Unroll the loop to avoid the overhead of a single popcnt per iteration,
|
||||
* allowing the CPU to extract more instruction-level parallelism.
|
||||
* Reference: https://danluu.com/assembly-intrinsics/ */
|
||||
while (count >= 32) {
|
||||
cnt[0] += __builtin_popcountll(*(uint64_t*)(p));
|
||||
cnt[1] += __builtin_popcountll(*(uint64_t*)(p + 8));
|
||||
cnt[2] += __builtin_popcountll(*(uint64_t*)(p + 16));
|
||||
cnt[3] += __builtin_popcountll(*(uint64_t*)(p + 24));
|
||||
count -= 32;
|
||||
p += 32;
|
||||
}
|
||||
bits += cnt[0] + cnt[1] + cnt[2] + cnt[3];
|
||||
goto remain;
|
||||
}
|
||||
|
||||
/* Count bits 28 bytes at a time */
|
||||
p4 = (uint32_t*)p;
|
||||
while(count>=28) {
|
||||
@@ -64,8 +95,10 @@ long long redisPopcount(void *s, long count) {
|
||||
((aux6 + (aux6 >> 4)) & 0x0F0F0F0F) +
|
||||
((aux7 + (aux7 >> 4)) & 0x0F0F0F0F))* 0x01010101) >> 24;
|
||||
}
|
||||
/* Count the remaining bytes. */
|
||||
p = (unsigned char*)p4;
|
||||
|
||||
remain:
|
||||
/* Count the remaining bytes. */
|
||||
while(count--) bits += bitsinbyte[*p++];
|
||||
return bits;
|
||||
}
|
||||
@@ -456,22 +489,27 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
|
||||
* bits to a string object. The command creates or pad with zeroes the string
|
||||
* so that the 'maxbit' bit can be addressed. The object is finally
|
||||
* returned. Otherwise if the key holds a wrong type NULL is returned and
|
||||
* an error is sent to the client. */
|
||||
robj *lookupStringForBitCommand(client *c, uint64_t maxbit, int *dirty) {
|
||||
* an error is sent to the client.
|
||||
*
|
||||
* (Must provide all the arguments to the function)
|
||||
*/
|
||||
static robj *lookupStringForBitCommand(client *c, uint64_t maxbit,
|
||||
size_t *strOldSize, size_t *strGrowSize)
|
||||
{
|
||||
size_t byte = maxbit >> 3;
|
||||
robj *o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (checkType(c,o,OBJ_STRING)) return NULL;
|
||||
if (dirty) *dirty = 0;
|
||||
|
||||
if (o == NULL) {
|
||||
o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
if (dirty) *dirty = 1;
|
||||
*strGrowSize = byte + 1;
|
||||
*strOldSize = 0;
|
||||
} else {
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
size_t oldlen = sdslen(o->ptr);
|
||||
*strOldSize = sdslen(o->ptr);
|
||||
o->ptr = sdsgrowzero(o->ptr,byte+1);
|
||||
if (dirty && oldlen != sdslen(o->ptr)) *dirty = 1;
|
||||
*strGrowSize = sdslen(o->ptr) - *strOldSize;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
@@ -528,8 +566,9 @@ void setbitCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
int dirty;
|
||||
if ((o = lookupStringForBitCommand(c,bitoffset,&dirty)) == NULL) return;
|
||||
size_t strOldSize, strGrowSize;
|
||||
if ((o = lookupStringForBitCommand(c,bitoffset,&strOldSize,&strGrowSize)) == NULL)
|
||||
return;
|
||||
|
||||
/* Get current values */
|
||||
byte = bitoffset >> 3;
|
||||
@@ -540,7 +579,7 @@ void setbitCommand(client *c) {
|
||||
/* Either it is newly created, changed length, or the bit changes before and after.
|
||||
* Note that the bitval here is actually a decimal number.
|
||||
* So we need to use `!!` to convert it to 0 or 1 for comparison. */
|
||||
if (dirty || (!!bitval != on)) {
|
||||
if (strGrowSize || (!!bitval != on)) {
|
||||
/* Update byte with new bit value. */
|
||||
byteval &= ~(1 << bit);
|
||||
byteval |= ((on & 0x1) << bit);
|
||||
@@ -548,6 +587,13 @@ void setbitCommand(client *c) {
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
/* If this is not a new key (old size not 0) and size changed, then
|
||||
* update the keysizes histogram. Otherwise, the histogram already
|
||||
* updated in lookupStringForBitCommand() by calling dbAdd(). */
|
||||
if ((strOldSize > 0) && (strGrowSize != 0))
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_STRING,
|
||||
strOldSize, strOldSize + strGrowSize);
|
||||
}
|
||||
|
||||
/* Return original value. */
|
||||
@@ -1032,7 +1078,8 @@ struct bitfieldOp {
|
||||
void bitfieldGeneric(client *c, int flags) {
|
||||
robj *o;
|
||||
uint64_t bitoffset;
|
||||
int j, numops = 0, changes = 0, dirty = 0;
|
||||
int j, numops = 0, changes = 0;
|
||||
size_t strOldSize, strGrowSize = 0;
|
||||
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
|
||||
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
|
||||
int readonly = 1;
|
||||
@@ -1126,7 +1173,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
/* Lookup by making room up to the farthest bit reached by
|
||||
* this operation. */
|
||||
if ((o = lookupStringForBitCommand(c,
|
||||
highest_write_offset,&dirty)) == NULL) {
|
||||
highest_write_offset,&strOldSize,&strGrowSize)) == NULL) {
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
@@ -1176,7 +1223,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
setSignedBitfield(o->ptr,thisop->offset,
|
||||
thisop->bits,newval);
|
||||
|
||||
if (dirty || (oldval != newval))
|
||||
if (strGrowSize || (oldval != newval))
|
||||
changes++;
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
@@ -1210,7 +1257,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
setUnsignedBitfield(o->ptr,thisop->offset,
|
||||
thisop->bits,newval);
|
||||
|
||||
if (dirty || (oldval != newval))
|
||||
if (strGrowSize || (oldval != newval))
|
||||
changes++;
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
@@ -1253,6 +1300,14 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
}
|
||||
|
||||
if (changes) {
|
||||
|
||||
/* If this is not a new key (old size not 0) and size changed, then
|
||||
* update the keysizes histogram. Otherwise, the histogram already
|
||||
* updated in lookupStringForBitCommand() by calling dbAdd(). */
|
||||
if ((strOldSize > 0) && (strGrowSize != 0))
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_STRING,
|
||||
strOldSize, strOldSize + strGrowSize);
|
||||
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
server.dirty += changes;
|
||||
|
||||
+245
-1
@@ -317,7 +317,7 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
|
||||
}
|
||||
|
||||
/* Create the connection */
|
||||
conn = connCreate(connTypeOfCluster());
|
||||
conn = connCreate(server.el, connTypeOfCluster());
|
||||
if (connBlockingConnect(conn, host->ptr, atoi(port->ptr), timeout)
|
||||
!= C_OK) {
|
||||
addReplyError(c,"-IOERR error or timeout connecting to the client");
|
||||
@@ -783,6 +783,130 @@ unsigned int countKeysInSlot(unsigned int slot) {
|
||||
return kvstoreDictSize(server.db->keys, slot);
|
||||
}
|
||||
|
||||
/* Add detailed information of a node to the output buffer of the given client. */
|
||||
void addNodeDetailsToShardReply(client *c, clusterNode *node) {
|
||||
|
||||
int reply_count = 0;
|
||||
char *hostname;
|
||||
void *node_replylen = addReplyDeferredLen(c);
|
||||
|
||||
addReplyBulkCString(c, "id");
|
||||
addReplyBulkCBuffer(c, clusterNodeGetName(node), CLUSTER_NAMELEN);
|
||||
reply_count++;
|
||||
|
||||
if (clusterNodeTcpPort(node)) {
|
||||
addReplyBulkCString(c, "port");
|
||||
addReplyLongLong(c, clusterNodeTcpPort(node));
|
||||
reply_count++;
|
||||
}
|
||||
|
||||
if (clusterNodeTlsPort(node)) {
|
||||
addReplyBulkCString(c, "tls-port");
|
||||
addReplyLongLong(c, clusterNodeTlsPort(node));
|
||||
reply_count++;
|
||||
}
|
||||
|
||||
addReplyBulkCString(c, "ip");
|
||||
addReplyBulkCString(c, clusterNodeIp(node));
|
||||
reply_count++;
|
||||
|
||||
addReplyBulkCString(c, "endpoint");
|
||||
addReplyBulkCString(c, clusterNodePreferredEndpoint(node));
|
||||
reply_count++;
|
||||
|
||||
hostname = clusterNodeHostname(node);
|
||||
if (hostname != NULL && *hostname != '\0') {
|
||||
addReplyBulkCString(c, "hostname");
|
||||
addReplyBulkCString(c, hostname);
|
||||
reply_count++;
|
||||
}
|
||||
|
||||
long long node_offset;
|
||||
if (clusterNodeIsMyself(node)) {
|
||||
node_offset = clusterNodeIsSlave(node) ? replicationGetSlaveOffset() : server.master_repl_offset;
|
||||
} else {
|
||||
node_offset = clusterNodeReplOffset(node);
|
||||
}
|
||||
|
||||
addReplyBulkCString(c, "role");
|
||||
addReplyBulkCString(c, clusterNodeIsSlave(node) ? "replica" : "master");
|
||||
reply_count++;
|
||||
|
||||
addReplyBulkCString(c, "replication-offset");
|
||||
addReplyLongLong(c, node_offset);
|
||||
reply_count++;
|
||||
|
||||
addReplyBulkCString(c, "health");
|
||||
const char *health_msg = NULL;
|
||||
if (clusterNodeIsFailing(node)) {
|
||||
health_msg = "fail";
|
||||
} else if (clusterNodeIsSlave(node) && node_offset == 0) {
|
||||
health_msg = "loading";
|
||||
} else {
|
||||
health_msg = "online";
|
||||
}
|
||||
addReplyBulkCString(c, health_msg);
|
||||
reply_count++;
|
||||
|
||||
setDeferredMapLen(c, node_replylen, reply_count);
|
||||
}
|
||||
|
||||
static clusterNode *clusterGetMasterFromShard(void *shard_handle) {
|
||||
clusterNode *n = NULL;
|
||||
void *node_it = clusterShardHandleGetNodeIterator(shard_handle);
|
||||
while((n = clusterShardNodeIteratorNext(node_it)) != NULL) {
|
||||
if (!clusterNodeIsFailing(n)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
clusterShardNodeIteratorFree(node_it);
|
||||
if (!n) return NULL;
|
||||
return clusterNodeGetMaster(n);
|
||||
}
|
||||
|
||||
/* Add the shard reply of a single shard based off the given primary node. */
|
||||
void addShardReplyForClusterShards(client *c, void *shard_handle) {
|
||||
serverAssert(clusterGetShardNodeCount(shard_handle) > 0);
|
||||
addReplyMapLen(c, 2);
|
||||
addReplyBulkCString(c, "slots");
|
||||
|
||||
/* Use slot_info_pairs from the primary only */
|
||||
clusterNode *master_node = clusterGetMasterFromShard(shard_handle);
|
||||
|
||||
if (master_node && clusterNodeHasSlotInfo(master_node)) {
|
||||
serverAssert((clusterNodeSlotInfoCount(master_node) % 2) == 0);
|
||||
addReplyArrayLen(c, clusterNodeSlotInfoCount(master_node));
|
||||
for (int i = 0; i < clusterNodeSlotInfoCount(master_node); i++)
|
||||
addReplyLongLong(c, (unsigned long)clusterNodeSlotInfoEntry(master_node, i));
|
||||
} else {
|
||||
/* If no slot info pair is provided, the node owns no slots */
|
||||
addReplyArrayLen(c, 0);
|
||||
}
|
||||
|
||||
addReplyBulkCString(c, "nodes");
|
||||
addReplyArrayLen(c, clusterGetShardNodeCount(shard_handle));
|
||||
void *node_it = clusterShardHandleGetNodeIterator(shard_handle);
|
||||
for (clusterNode *n = clusterShardNodeIteratorNext(node_it); n != NULL; n = clusterShardNodeIteratorNext(node_it)) {
|
||||
addNodeDetailsToShardReply(c, n);
|
||||
clusterFreeNodesSlotsInfo(n);
|
||||
}
|
||||
clusterShardNodeIteratorFree(node_it);
|
||||
}
|
||||
|
||||
/* Add to the output buffer of the given client, an array of slot (start, end)
|
||||
* pair owned by the shard, also the primary and set of replica(s) along with
|
||||
* information about each node. */
|
||||
void clusterCommandShards(client *c) {
|
||||
addReplyArrayLen(c, clusterGetShardCount());
|
||||
/* This call will add slot_info_pairs to all nodes */
|
||||
clusterGenNodesSlotsInfo(0);
|
||||
dictIterator *shard_it = clusterGetShardIterator();
|
||||
for(void *shard_handle = clusterNextShardHandle(shard_it); shard_handle != NULL; shard_handle = clusterNextShardHandle(shard_it)) {
|
||||
addShardReplyForClusterShards(c, shard_handle);
|
||||
}
|
||||
clusterFreeShardIterator(shard_it);
|
||||
}
|
||||
|
||||
void clusterCommandHelp(client *c) {
|
||||
const char *help[] = {
|
||||
"COUNTKEYSINSLOT <slot>",
|
||||
@@ -1434,6 +1558,126 @@ void readonlyCommand(client *c) {
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
void replySlotsFlushAndFree(client *c, SlotsFlush *sflush) {
|
||||
addReplyArrayLen(c, sflush->numRanges);
|
||||
for (int i = 0 ; i < sflush->numRanges ; i++) {
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyLongLong(c, sflush->ranges[i].first);
|
||||
addReplyLongLong(c, sflush->ranges[i].last);
|
||||
}
|
||||
zfree(sflush);
|
||||
}
|
||||
|
||||
/* Partially flush destination DB in a cluster node, based on the slot range.
|
||||
*
|
||||
* Usage: SFLUSH <start-slot> <end slot> [<start-slot> <end slot>]* [SYNC|ASYNC]
|
||||
*
|
||||
* This is an initial implementation of SFLUSH (slots flush) which is limited to
|
||||
* flushing a single shard as a whole, but in the future the same command may be
|
||||
* used to partially flush a shard based on hash slots. Currently only if provided
|
||||
* slots cover entirely the slots of a node, the node will be flushed and the
|
||||
* return value will be pairs of slot ranges. Otherwise, a single empty set will
|
||||
* be returned. If possible, SFLUSH SYNC will be run as blocking ASYNC as an
|
||||
* optimization.
|
||||
*/
|
||||
void sflushCommand(client *c) {
|
||||
int flags = EMPTYDB_NO_FLAGS, argc = c->argc;
|
||||
|
||||
if (server.cluster_enabled == 0) {
|
||||
addReplyError(c,"This instance has cluster support disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
/* check if last argument is SYNC or ASYNC */
|
||||
if (!strcasecmp(c->argv[c->argc-1]->ptr,"sync")) {
|
||||
flags = EMPTYDB_NO_FLAGS;
|
||||
argc--;
|
||||
} else if (!strcasecmp(c->argv[c->argc-1]->ptr,"async")) {
|
||||
flags = EMPTYDB_ASYNC;
|
||||
argc--;
|
||||
} else if (server.lazyfree_lazy_user_flush) {
|
||||
flags = EMPTYDB_ASYNC;
|
||||
}
|
||||
|
||||
/* parse the slot range */
|
||||
if (argc % 2 == 0) {
|
||||
addReplyErrorArity(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Verify <first, last> slot pairs are valid and not overlapping */
|
||||
long long j, first, last;
|
||||
unsigned char slotsToFlushRq[CLUSTER_SLOTS] = {0};
|
||||
for (j = 1; j < argc; j += 2) {
|
||||
/* check if the first slot is valid */
|
||||
if (getLongLongFromObject(c->argv[j], &first) != C_OK || first < 0 || first >= CLUSTER_SLOTS) {
|
||||
addReplyError(c,"Invalid or out of range slot");
|
||||
return;
|
||||
}
|
||||
|
||||
/* check if the last slot is valid */
|
||||
if (getLongLongFromObject(c->argv[j+1], &last) != C_OK || last < 0 || last >= CLUSTER_SLOTS) {
|
||||
addReplyError(c,"Invalid or out of range slot");
|
||||
return;
|
||||
}
|
||||
|
||||
if (first > last) {
|
||||
addReplyErrorFormat(c,"start slot number %lld is greater than end slot number %lld", first, last);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Mark the slots in slotsToFlushRq[] */
|
||||
for (int i = first; i <= last; i++) {
|
||||
if (slotsToFlushRq[i]) {
|
||||
addReplyErrorFormat(c, "Slot %d specified multiple times", i);
|
||||
return;
|
||||
}
|
||||
slotsToFlushRq[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify slotsToFlushRq[] covers ALL slots of myNode. */
|
||||
clusterNode *myNode = getMyClusterNode();
|
||||
/* During iteration trace also the slot range pairs and save in SlotsFlush.
|
||||
* It is allocated on heap since there is a chance that FLUSH SYNC will be
|
||||
* running as blocking ASYNC and only later reply with slot ranges */
|
||||
int capacity = 32; /* Initial capacity */
|
||||
SlotsFlush *sflush = zmalloc(sizeof(SlotsFlush) + sizeof(SlotRange) * capacity);
|
||||
sflush->numRanges = 0;
|
||||
int inSlotRange = 0;
|
||||
for (int i = 0; i < CLUSTER_SLOTS; i++) {
|
||||
if (myNode == getNodeBySlot(i)) {
|
||||
if (!slotsToFlushRq[i]) {
|
||||
addReplySetLen(c, 0); /* Not all slots of mynode got covered. See sflushCommand() comment. */
|
||||
zfree(sflush);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inSlotRange) { /* If start another slot range */
|
||||
sflush->ranges[sflush->numRanges].first = i;
|
||||
inSlotRange = 1;
|
||||
}
|
||||
} else {
|
||||
if (inSlotRange) { /* If end another slot range */
|
||||
sflush->ranges[sflush->numRanges++].last = i - 1;
|
||||
inSlotRange = 0;
|
||||
/* If reached 'sflush' capacity, double the capacity */
|
||||
if (sflush->numRanges >= capacity) {
|
||||
capacity *= 2;
|
||||
sflush = zrealloc(sflush, sizeof(SlotsFlush) + sizeof(SlotRange) * capacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update last pair if last cluster slot is also end of last range */
|
||||
if (inSlotRange) sflush->ranges[sflush->numRanges++].last = CLUSTER_SLOTS - 1;
|
||||
|
||||
/* Flush selected slots. If not flush as blocking async, then reply immediately */
|
||||
if (flushCommandCommon(c, FLUSH_TYPE_SLOTS, flags, sflush) == 0)
|
||||
replySlotsFlushAndFree(c, sflush);
|
||||
}
|
||||
|
||||
/* The READWRITE command just clears the READONLY command state. */
|
||||
void readwriteCommand(client *c) {
|
||||
if (server.cluster_enabled == 0) {
|
||||
|
||||
+20
-1
@@ -97,7 +97,7 @@ int clusterManualFailoverTimeLimit(void);
|
||||
void clusterCommandSlots(client * c);
|
||||
void clusterCommandMyId(client *c);
|
||||
void clusterCommandMyShardId(client *c);
|
||||
void clusterCommandShards(client *c);
|
||||
|
||||
sds clusterGenNodeDescription(client *c, clusterNode *node, int tls_primary);
|
||||
|
||||
int clusterNodeCoversSlot(clusterNode *n, int slot);
|
||||
@@ -142,4 +142,23 @@ int isValidAuxString(char *s, unsigned int length);
|
||||
void migrateCommand(client *c);
|
||||
void clusterCommand(client *c);
|
||||
ConnectionType *connTypeOfCluster(void);
|
||||
|
||||
void clusterGenNodesSlotsInfo(int filter);
|
||||
void clusterFreeNodesSlotsInfo(clusterNode *n);
|
||||
int clusterNodeSlotInfoCount(clusterNode *n);
|
||||
uint16_t clusterNodeSlotInfoEntry(clusterNode *n, int idx);
|
||||
int clusterNodeHasSlotInfo(clusterNode *n);
|
||||
|
||||
int clusterGetShardCount(void);
|
||||
void *clusterGetShardIterator(void);
|
||||
void *clusterNextShardHandle(void *shard_iterator);
|
||||
void clusterFreeShardIterator(void *shard_iterator);
|
||||
int clusterGetShardNodeCount(void *shard);
|
||||
void *clusterShardHandleGetNodeIterator(void *shard);
|
||||
clusterNode *clusterShardNodeIteratorNext(void *node_iterator);
|
||||
void clusterShardNodeIteratorFree(void *node_iterator);
|
||||
clusterNode *clusterShardNodeFirst(void *shard);
|
||||
|
||||
int clusterNodeTcpPort(clusterNode *node);
|
||||
int clusterNodeTlsPort(clusterNode *node);
|
||||
#endif /* __CLUSTER_H */
|
||||
|
||||
+119
-140
@@ -2,8 +2,13 @@
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2024-present, Valkey contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*
|
||||
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -88,6 +93,7 @@ int auxTlsPortPresent(clusterNode *n);
|
||||
static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen);
|
||||
void freeClusterLink(clusterLink *link);
|
||||
int verifyClusterNodeId(const char *name, int length);
|
||||
static void updateShardId(clusterNode *node, const char *shard_id);
|
||||
|
||||
int getNodeDefaultClientPort(clusterNode *n) {
|
||||
return server.tls_cluster ? n->tls_port : n->tcp_port;
|
||||
@@ -198,12 +204,11 @@ int auxShardIdSetter(clusterNode *n, void *value, int length) {
|
||||
return C_ERR;
|
||||
}
|
||||
memcpy(n->shard_id, value, CLUSTER_NAMELEN);
|
||||
/* if n already has replicas, make sure they all agree
|
||||
* on the shard id */
|
||||
/* if n already has replicas, make sure they all use
|
||||
* the primary shard id */
|
||||
for (int i = 0; i < n->numslaves; i++) {
|
||||
if (memcmp(n->slaves[i]->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0) {
|
||||
return C_ERR;
|
||||
}
|
||||
if (memcmp(n->slaves[i]->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0)
|
||||
updateShardId(n->slaves[i], n->shard_id);
|
||||
}
|
||||
clusterAddNodeToShard(value, n);
|
||||
return C_OK;
|
||||
@@ -545,18 +550,12 @@ int clusterLoadConfig(char *filename) {
|
||||
clusterAddNode(master);
|
||||
}
|
||||
/* shard_id can be absent if we are loading a nodes.conf generated
|
||||
* by an older version of Redis; we should follow the primary's
|
||||
* shard_id in this case */
|
||||
if (auxFieldHandlers[af_shard_id].isPresent(n) == 0) {
|
||||
memcpy(n->shard_id, master->shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(master->shard_id, n);
|
||||
} else if (clusterGetNodesInMyShard(master) != NULL &&
|
||||
memcmp(master->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0)
|
||||
{
|
||||
/* If the primary has been added to a shard, make sure this
|
||||
* node has the same persisted shard id as the primary. */
|
||||
goto fmterr;
|
||||
}
|
||||
* by an older version of Redis;
|
||||
* ignore replica's shard_id in the file, only use the primary's.
|
||||
* If replica precedes primary in file, it will be corrected
|
||||
* later by the auxShardIdSetter */
|
||||
memcpy(n->shard_id, master->shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(master->shard_id, n);
|
||||
n->slaveof = master;
|
||||
clusterNodeAddSlave(master,n);
|
||||
} else if (auxFieldHandlers[af_shard_id].isPresent(n) == 0) {
|
||||
@@ -634,6 +633,8 @@ int clusterLoadConfig(char *filename) {
|
||||
}
|
||||
/* Config sanity check */
|
||||
if (server.cluster->myself == NULL) goto fmterr;
|
||||
if (!(myself->flags & (CLUSTER_NODE_MASTER | CLUSTER_NODE_SLAVE))) goto fmterr;
|
||||
if (nodeIsSlave(myself) && myself->slaveof == NULL) goto fmterr;
|
||||
|
||||
zfree(line);
|
||||
fclose(fp);
|
||||
@@ -901,22 +902,39 @@ static void updateAnnouncedHumanNodename(clusterNode *node, char *new) {
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
|
||||
static void assignShardIdToNode(clusterNode *node, const char *shard_id, int flag) {
|
||||
clusterRemoveNodeFromShard(node);
|
||||
memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(shard_id, node);
|
||||
clusterDoBeforeSleep(flag);
|
||||
}
|
||||
|
||||
static void updateShardId(clusterNode *node, const char *shard_id) {
|
||||
if (shard_id && memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
|
||||
clusterRemoveNodeFromShard(node);
|
||||
memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(shard_id, node);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
if (shard_id && myself != node && myself->slaveof == node) {
|
||||
if (memcmp(myself->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
|
||||
/* shard-id can diverge right after a rolling upgrade
|
||||
* from pre-7.2 releases */
|
||||
clusterRemoveNodeFromShard(myself);
|
||||
memcpy(myself->shard_id, shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(shard_id, myself);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
|
||||
assignShardIdToNode(node, shard_id, CLUSTER_TODO_SAVE_CONFIG);
|
||||
|
||||
/* If the replica or master does not support shard-id (old version),
|
||||
* we still need to make our best effort to keep their shard-id consistent.
|
||||
*
|
||||
* 1. Master supports but the replica does not.
|
||||
* We might first update the replica's shard-id to the master's randomly
|
||||
* generated shard-id. Then, when the master's shard-id arrives, we must
|
||||
* also update all its replicas.
|
||||
* 2. If the master does not support but the replica does.
|
||||
* We also need to synchronize the master's shard-id with the replica.
|
||||
* 3. If neither of master and replica supports it.
|
||||
* The master will have a randomly generated shard-id and will update
|
||||
* the replica to match the master's shard-id. */
|
||||
if (node->slaveof == NULL) {
|
||||
for (int i = 0; i < clusterNodeNumSlaves(node); i++) {
|
||||
clusterNode *slavenode = clusterNodeGetSlave(node, i);
|
||||
if (memcmp(slavenode->shard_id, shard_id, CLUSTER_NAMELEN) != 0)
|
||||
assignShardIdToNode(slavenode, shard_id, CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
|
||||
}
|
||||
} else {
|
||||
clusterNode *masternode = node->slaveof;
|
||||
if (memcmp(masternode->shard_id, shard_id, CLUSTER_NAMELEN) != 0)
|
||||
assignShardIdToNode(masternode, shard_id, CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1244,7 +1262,7 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
|
||||
connection *conn = connCreateAccepted(connTypeOfCluster(), cfd, &require_auth);
|
||||
connection *conn = connCreateAccepted(server.el, connTypeOfCluster(), cfd, &require_auth);
|
||||
|
||||
/* Make sure connection is not in an error state */
|
||||
if (connGetState(conn) != CONN_STATE_ACCEPTING) {
|
||||
@@ -2577,9 +2595,6 @@ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) {
|
||||
extensions++;
|
||||
|
||||
if (hdr != NULL) {
|
||||
if (extensions != 0) {
|
||||
hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA;
|
||||
}
|
||||
hdr->extensions = htons(extensions);
|
||||
}
|
||||
|
||||
@@ -2769,6 +2784,9 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
}
|
||||
|
||||
sender = getNodeFromLinkAndMsg(link, hdr);
|
||||
if (sender && (hdr->mflags[0] & CLUSTERMSG_FLAG0_EXT_DATA)) {
|
||||
sender->flags |= CLUSTER_NODE_EXTENSIONS_SUPPORTED;
|
||||
}
|
||||
|
||||
/* Update the last time we saw any data from this node. We
|
||||
* use this in order to avoid detecting a timeout from a node that
|
||||
@@ -3534,6 +3552,8 @@ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen) {
|
||||
/* Set the message flags. */
|
||||
if (clusterNodeIsMaster(myself) && server.cluster->mf_end)
|
||||
hdr->mflags[0] |= CLUSTERMSG_FLAG0_PAUSED;
|
||||
hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA; /* Always make other nodes know that
|
||||
* this node supports extension data. */
|
||||
|
||||
hdr->totlen = htonl(msglen);
|
||||
}
|
||||
@@ -3612,7 +3632,9 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
* to put inside the packet. */
|
||||
estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
|
||||
estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted));
|
||||
estlen += writePingExt(NULL, 0);
|
||||
if (link->node && nodeSupportsExtensions(link->node)) {
|
||||
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);
|
||||
@@ -3682,7 +3704,9 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
|
||||
/* Compute the actual total length and send! */
|
||||
uint32_t totlen = 0;
|
||||
totlen += writePingExt(hdr, gossipcount);
|
||||
if (link->node && nodeSupportsExtensions(link->node)) {
|
||||
totlen += writePingExt(hdr, gossipcount);
|
||||
}
|
||||
totlen += sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||
totlen += (sizeof(clusterMsgDataGossip)*gossipcount);
|
||||
serverAssert(gossipcount < USHRT_MAX);
|
||||
@@ -4559,7 +4583,7 @@ static int clusterNodeCronHandleReconnect(clusterNode *node, mstime_t handshake_
|
||||
|
||||
if (node->link == NULL) {
|
||||
clusterLink *link = createClusterLink(node);
|
||||
link->conn = connCreate(connTypeOfCluster());
|
||||
link->conn = connCreate(server.el, connTypeOfCluster());
|
||||
connSetPrivateData(link->conn, link);
|
||||
if (connConnect(link->conn, node->ip, node->cport, server.bind_source_addr,
|
||||
clusterLinkConnectHandler) == C_ERR) {
|
||||
@@ -5582,113 +5606,68 @@ void clusterUpdateSlots(client *c, unsigned char *slots, int del) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Add detailed information of a node to the output buffer of the given client. */
|
||||
void addNodeDetailsToShardReply(client *c, clusterNode *node) {
|
||||
int reply_count = 0;
|
||||
void *node_replylen = addReplyDeferredLen(c);
|
||||
addReplyBulkCString(c, "id");
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
reply_count++;
|
||||
|
||||
if (node->tcp_port) {
|
||||
addReplyBulkCString(c, "port");
|
||||
addReplyLongLong(c, node->tcp_port);
|
||||
reply_count++;
|
||||
}
|
||||
|
||||
if (node->tls_port) {
|
||||
addReplyBulkCString(c, "tls-port");
|
||||
addReplyLongLong(c, node->tls_port);
|
||||
reply_count++;
|
||||
}
|
||||
|
||||
addReplyBulkCString(c, "ip");
|
||||
addReplyBulkCString(c, node->ip);
|
||||
reply_count++;
|
||||
|
||||
addReplyBulkCString(c, "endpoint");
|
||||
addReplyBulkCString(c, clusterNodePreferredEndpoint(node));
|
||||
reply_count++;
|
||||
|
||||
if (sdslen(node->hostname) != 0) {
|
||||
addReplyBulkCString(c, "hostname");
|
||||
addReplyBulkCBuffer(c, node->hostname, sdslen(node->hostname));
|
||||
reply_count++;
|
||||
}
|
||||
|
||||
long long node_offset;
|
||||
if (node->flags & CLUSTER_NODE_MYSELF) {
|
||||
node_offset = nodeIsSlave(node) ? replicationGetSlaveOffset() : server.master_repl_offset;
|
||||
} else {
|
||||
node_offset = node->repl_offset;
|
||||
}
|
||||
|
||||
addReplyBulkCString(c, "role");
|
||||
addReplyBulkCString(c, nodeIsSlave(node) ? "replica" : "master");
|
||||
reply_count++;
|
||||
|
||||
addReplyBulkCString(c, "replication-offset");
|
||||
addReplyLongLong(c, node_offset);
|
||||
reply_count++;
|
||||
|
||||
addReplyBulkCString(c, "health");
|
||||
const char *health_msg = NULL;
|
||||
if (nodeFailed(node)) {
|
||||
health_msg = "fail";
|
||||
} else if (nodeIsSlave(node) && node_offset == 0) {
|
||||
health_msg = "loading";
|
||||
} else {
|
||||
health_msg = "online";
|
||||
}
|
||||
addReplyBulkCString(c, health_msg);
|
||||
reply_count++;
|
||||
|
||||
setDeferredMapLen(c, node_replylen, reply_count);
|
||||
int clusterGetShardCount(void) {
|
||||
return dictSize(server.cluster->shards);
|
||||
}
|
||||
|
||||
/* Add the shard reply of a single shard based off the given primary node. */
|
||||
void addShardReplyForClusterShards(client *c, list *nodes) {
|
||||
serverAssert(listLength(nodes) > 0);
|
||||
clusterNode *n = listNodeValue(listFirst(nodes));
|
||||
addReplyMapLen(c, 2);
|
||||
addReplyBulkCString(c, "slots");
|
||||
|
||||
/* Use slot_info_pairs from the primary only */
|
||||
n = clusterNodeGetMaster(n);
|
||||
|
||||
if (n->slot_info_pairs != NULL) {
|
||||
serverAssert((n->slot_info_pairs_count % 2) == 0);
|
||||
addReplyArrayLen(c, n->slot_info_pairs_count);
|
||||
for (int i = 0; i < n->slot_info_pairs_count; i++)
|
||||
addReplyLongLong(c, (unsigned long)n->slot_info_pairs[i]);
|
||||
} else {
|
||||
/* If no slot info pair is provided, the node owns no slots */
|
||||
addReplyArrayLen(c, 0);
|
||||
}
|
||||
|
||||
addReplyBulkCString(c, "nodes");
|
||||
addReplyArrayLen(c, listLength(nodes));
|
||||
listIter li;
|
||||
listRewind(nodes, &li);
|
||||
for (listNode *ln = listNext(&li); ln != NULL; ln = listNext(&li)) {
|
||||
clusterNode *n = listNodeValue(ln);
|
||||
addNodeDetailsToShardReply(c, n);
|
||||
clusterFreeNodesSlotsInfo(n);
|
||||
}
|
||||
void *clusterGetShardIterator(void) {
|
||||
return dictGetSafeIterator(server.cluster->shards);
|
||||
}
|
||||
|
||||
/* Add to the output buffer of the given client, an array of slot (start, end)
|
||||
* pair owned by the shard, also the primary and set of replica(s) along with
|
||||
* information about each node. */
|
||||
void clusterCommandShards(client *c) {
|
||||
addReplyArrayLen(c, dictSize(server.cluster->shards));
|
||||
/* This call will add slot_info_pairs to all nodes */
|
||||
clusterGenNodesSlotsInfo(0);
|
||||
dictIterator *di = dictGetSafeIterator(server.cluster->shards);
|
||||
for(dictEntry *de = dictNext(di); de != NULL; de = dictNext(di)) {
|
||||
addShardReplyForClusterShards(c, dictGetVal(de));
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
void *clusterNextShardHandle(void *shard_iterator) {
|
||||
dictEntry *de = dictNext(shard_iterator);
|
||||
if(de == NULL) return NULL;
|
||||
return dictGetVal(de);
|
||||
}
|
||||
|
||||
void clusterFreeShardIterator(void *shard_iterator) {
|
||||
dictReleaseIterator(shard_iterator);
|
||||
}
|
||||
|
||||
int clusterNodeHasSlotInfo(clusterNode *n) {
|
||||
return n->slot_info_pairs != NULL;
|
||||
}
|
||||
|
||||
int clusterNodeSlotInfoCount(clusterNode *n) {
|
||||
return n->slot_info_pairs_count;
|
||||
}
|
||||
|
||||
uint16_t clusterNodeSlotInfoEntry(clusterNode *n, int idx) {
|
||||
return n->slot_info_pairs[idx];
|
||||
}
|
||||
|
||||
int clusterGetShardNodeCount(void *shard) {
|
||||
return listLength((list*)shard);
|
||||
}
|
||||
|
||||
void *clusterShardHandleGetNodeIterator(void *shard) {
|
||||
listIter *li = zmalloc(sizeof(listIter));
|
||||
listRewind((list*)shard, li);
|
||||
return li;
|
||||
}
|
||||
|
||||
void clusterShardNodeIteratorFree(void *node_iterator) {
|
||||
zfree(node_iterator);
|
||||
}
|
||||
|
||||
clusterNode *clusterShardNodeIteratorNext(void *node_iterator) {
|
||||
listNode *item = listNext((listIter*)node_iterator);
|
||||
if (item == NULL) return NULL;
|
||||
return listNodeValue(item);
|
||||
}
|
||||
|
||||
clusterNode *clusterShardNodeFirst(void *shard) {
|
||||
listNode *item = listFirst((list*)shard);
|
||||
if (item == NULL) return NULL;
|
||||
return listNodeValue(item);
|
||||
}
|
||||
|
||||
int clusterNodeTcpPort(clusterNode *node) {
|
||||
return node->tcp_port;
|
||||
}
|
||||
|
||||
int clusterNodeTlsPort(clusterNode *node) {
|
||||
return node->tls_port;
|
||||
}
|
||||
|
||||
sds genClusterInfoString(void) {
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2024-present, Valkey contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*
|
||||
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
*/
|
||||
|
||||
#ifndef CLUSTER_LEGACY_H
|
||||
#define CLUSTER_LEGACY_H
|
||||
|
||||
@@ -51,6 +64,7 @@ typedef struct clusterLink {
|
||||
#define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */
|
||||
#define CLUSTER_NODE_MIGRATE_TO 256 /* Master eligible for replica migration. */
|
||||
#define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failover. */
|
||||
#define CLUSTER_NODE_EXTENSIONS_SUPPORTED 1024 /* This node supports extensions. */
|
||||
#define CLUSTER_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
|
||||
#define nodeIsSlave(n) ((n)->flags & CLUSTER_NODE_SLAVE)
|
||||
@@ -59,6 +73,7 @@ typedef struct clusterLink {
|
||||
#define nodeTimedOut(n) ((n)->flags & CLUSTER_NODE_PFAIL)
|
||||
#define nodeFailed(n) ((n)->flags & CLUSTER_NODE_FAIL)
|
||||
#define nodeCantFailover(n) ((n)->flags & CLUSTER_NODE_NOFAILOVER)
|
||||
#define nodeSupportsExtensions(n) ((n)->flags & CLUSTER_NODE_EXTENSIONS_SUPPORTED)
|
||||
|
||||
/* This structure represent elements of node->fail_reports. */
|
||||
typedef struct clusterNodeFailReport {
|
||||
|
||||
+13
-6
@@ -1239,6 +1239,9 @@ commandHistory CLIENT_LIST_History[] = {
|
||||
{"6.2.0","Added `argv-mem`, `tot-mem`, `laddr` and `redir` fields and the optional `ID` filter."},
|
||||
{"7.0.0","Added `resp`, `multi-mem`, `rbs` and `rbp` fields."},
|
||||
{"7.0.3","Added `ssub` field."},
|
||||
{"7.2.0","Added `lib-name` and `lib-ver` fields."},
|
||||
{"7.4.0","Added `watch` field."},
|
||||
{"8.0.0","Added `io-thread` field."},
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -1546,7 +1549,7 @@ struct COMMAND_STRUCT CLIENT_Subcommands[] = {
|
||||
{MAKE_CMD("id","Returns the unique client ID of the connection.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_ID_History,0,CLIENT_ID_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_ID_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("info","Returns information about the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,0,CLIENT_INFO_Tips,1,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_INFO_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,6,CLIENT_KILL_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args},
|
||||
{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,6,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,2),.args=CLIENT_LIST_Args},
|
||||
{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,9,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,2),.args=CLIENT_LIST_Args},
|
||||
{MAKE_CMD("no-evict","Sets the client eviction mode of the connection.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,0,CLIENT_NO_EVICT_Tips,0,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_NO_EVICT_Keyspecs,0,NULL,1),.args=CLIENT_NO_EVICT_Args},
|
||||
{MAKE_CMD("no-touch","Controls whether commands sent by the client affect the LRU/LFU of accessed keys.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,0,CLIENT_NO_TOUCH_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_NO_TOUCH_Keyspecs,0,NULL,1),.args=CLIENT_NO_TOUCH_Args},
|
||||
{MAKE_CMD("pause","Suspends commands processing.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,1,CLIENT_PAUSE_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_PAUSE_Keyspecs,0,NULL,2),.args=CLIENT_PAUSE_Args},
|
||||
@@ -3778,7 +3781,9 @@ struct COMMAND_ARG HPEXPIRETIME_Args[] = {
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HPTTL tips */
|
||||
#define HPTTL_Tips NULL
|
||||
const char *HPTTL_Tips[] = {
|
||||
"nondeterministic_output",
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
@@ -3956,7 +3961,9 @@ struct COMMAND_ARG HSTRLEN_Args[] = {
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HTTL tips */
|
||||
#define HTTL_Tips NULL
|
||||
const char *HTTL_Tips[] = {
|
||||
"nondeterministic_output",
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
@@ -11044,13 +11051,13 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
||||
{MAKE_CMD("hpexpire","Set expiry for hash field using relative time to expire (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRE_History,0,HPEXPIRE_Tips,0,hpexpireCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRE_Keyspecs,1,NULL,4),.args=HPEXPIRE_Args},
|
||||
{MAKE_CMD("hpexpireat","Set expiry for hash field using an absolute Unix timestamp (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIREAT_History,0,HPEXPIREAT_Tips,0,hpexpireatCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIREAT_Keyspecs,1,NULL,4),.args=HPEXPIREAT_Args},
|
||||
{MAKE_CMD("hpexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in msec.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRETIME_History,0,HPEXPIRETIME_Tips,0,hpexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRETIME_Keyspecs,1,NULL,2),.args=HPEXPIRETIME_Args},
|
||||
{MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,0,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,2),.args=HPTTL_Args},
|
||||
{MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,1,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,2),.args=HPTTL_Args},
|
||||
{MAKE_CMD("hrandfield","Returns one or more random fields from a hash.","O(N) where N is the number of fields returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HRANDFIELD_History,0,HRANDFIELD_Tips,1,hrandfieldCommand,-2,CMD_READONLY,ACL_CATEGORY_HASH,HRANDFIELD_Keyspecs,1,NULL,2),.args=HRANDFIELD_Args},
|
||||
{MAKE_CMD("hscan","Iterates over fields and values of a hash.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSCAN_History,0,HSCAN_Tips,1,hscanCommand,-3,CMD_READONLY,ACL_CATEGORY_HASH,HSCAN_Keyspecs,1,NULL,5),.args=HSCAN_Args},
|
||||
{MAKE_CMD("hset","Creates or modifies the value of a field in a hash.","O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSET_History,1,HSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSET_Keyspecs,1,NULL,2),.args=HSET_Args},
|
||||
{MAKE_CMD("hsetnx","Sets the value of a field in a hash only when the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETNX_History,0,HSETNX_Tips,0,hsetnxCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSETNX_Keyspecs,1,NULL,3),.args=HSETNX_Args},
|
||||
{MAKE_CMD("hstrlen","Returns the length of the value of a field.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSTRLEN_History,0,HSTRLEN_Tips,0,hstrlenCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HSTRLEN_Keyspecs,1,NULL,2),.args=HSTRLEN_Args},
|
||||
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,0,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args},
|
||||
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,1,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args},
|
||||
{MAKE_CMD("hvals","Returns all values in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HVALS_History,0,HVALS_Tips,1,hvalsCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HVALS_Keyspecs,1,NULL,1),.args=HVALS_Args},
|
||||
/* hyperloglog */
|
||||
{MAKE_CMD("pfadd","Adds elements to a HyperLogLog key. Creates the key if it doesn't exist.","O(1) to add every element.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFADD_History,0,PFADD_Tips,0,pfaddCommand,-2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HYPERLOGLOG,PFADD_Keyspecs,1,NULL,2),.args=PFADD_Args},
|
||||
@@ -11141,7 +11148,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
||||
{MAKE_CMD("sintercard","Returns the number of members of the intersect of multiple sets.","O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.","7.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SINTERCARD_History,0,SINTERCARD_Tips,0,sinterCardCommand,-3,CMD_READONLY,ACL_CATEGORY_SET,SINTERCARD_Keyspecs,1,sintercardGetKeys,3),.args=SINTERCARD_Args},
|
||||
{MAKE_CMD("sinterstore","Stores the intersect of multiple sets in a key.","O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SINTERSTORE_History,0,SINTERSTORE_Tips,0,sinterstoreCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SET,SINTERSTORE_Keyspecs,2,NULL,2),.args=SINTERSTORE_Args},
|
||||
{MAKE_CMD("sismember","Determines whether a member belongs to a set.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SISMEMBER_History,0,SISMEMBER_Tips,0,sismemberCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SET,SISMEMBER_Keyspecs,1,NULL,2),.args=SISMEMBER_Args},
|
||||
{MAKE_CMD("smembers","Returns all members of a set.","O(N) where N is the set cardinality.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMEMBERS_History,0,SMEMBERS_Tips,1,sinterCommand,2,CMD_READONLY,ACL_CATEGORY_SET,SMEMBERS_Keyspecs,1,NULL,1),.args=SMEMBERS_Args},
|
||||
{MAKE_CMD("smembers","Returns all members of a set.","O(N) where N is the set cardinality.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMEMBERS_History,0,SMEMBERS_Tips,1,smembersCommand,2,CMD_READONLY,ACL_CATEGORY_SET,SMEMBERS_Keyspecs,1,NULL,1),.args=SMEMBERS_Args},
|
||||
{MAKE_CMD("smismember","Determines whether multiple members belong to a set.","O(N) where N is the number of elements being checked for membership","6.2.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMISMEMBER_History,0,SMISMEMBER_Tips,0,smismemberCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SET,SMISMEMBER_Keyspecs,1,NULL,2),.args=SMISMEMBER_Args},
|
||||
{MAKE_CMD("smove","Moves a member from one set to another.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMOVE_History,0,SMOVE_Tips,0,smoveCommand,4,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SET,SMOVE_Keyspecs,2,NULL,3),.args=SMOVE_Args},
|
||||
{MAKE_CMD("spop","Returns one or more random members from a set after removing them. Deletes the set if the last member was popped.","Without the count argument O(1), otherwise O(N) where N is the value of the passed count.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SPOP_History,1,SPOP_Tips,1,spopCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SET,SPOP_Keyspecs,1,NULL,2),.args=SPOP_Args},
|
||||
|
||||
@@ -31,6 +31,18 @@
|
||||
[
|
||||
"7.0.3",
|
||||
"Added `ssub` field."
|
||||
],
|
||||
[
|
||||
"7.2.0",
|
||||
"Added `lib-name` and `lib-ver` fields."
|
||||
],
|
||||
[
|
||||
"7.4.0",
|
||||
"Added `watch` field."
|
||||
],
|
||||
[
|
||||
"8.0.0",
|
||||
"Added `io-thread` field."
|
||||
]
|
||||
],
|
||||
"command_flags": [
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"command_tips": [
|
||||
"NONDETERMINISTIC_OUTPUT"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"command_tips": [
|
||||
"NONDETERMINISTIC_OUTPUT"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"SFLUSH": {
|
||||
"summary": "Remove all keys from selected range of slots.",
|
||||
"complexity": "O(N)+O(k) where N is the number of keys and k is the number of slots.",
|
||||
"group": "server",
|
||||
"since": "8.0.0",
|
||||
"arity": -3,
|
||||
"function": "sflushCommand",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"EXPERIMENTAL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"KEYSPACE",
|
||||
"DANGEROUS"
|
||||
],
|
||||
"command_tips": [
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "List of slot ranges",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
{
|
||||
"description": "start slot number",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"description": "end slot number",
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "data",
|
||||
"type": "block",
|
||||
"multiple": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "slot-start",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "slot-last",
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flush-type",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "async",
|
||||
"type": "pure-token",
|
||||
"token": "ASYNC"
|
||||
},
|
||||
{
|
||||
"name": "sync",
|
||||
"type": "pure-token",
|
||||
"token": "SYNC"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"group": "set",
|
||||
"since": "1.0.0",
|
||||
"arity": 2,
|
||||
"function": "sinterCommand",
|
||||
"function": "smembersCommand",
|
||||
"command_flags": [
|
||||
"READONLY"
|
||||
],
|
||||
|
||||
+15
-6
@@ -430,6 +430,7 @@ void loadServerConfigFromString(char *config) {
|
||||
{"list-max-ziplist-entries", 2, 2},
|
||||
{"list-max-ziplist-value", 2, 2},
|
||||
{"lua-replicate-commands", 2, 2},
|
||||
{"io-threads-do-reads", 2, 2},
|
||||
{NULL, 0},
|
||||
};
|
||||
char buf[1024];
|
||||
@@ -2502,6 +2503,13 @@ static int updateWatchdogPeriod(const char **err) {
|
||||
}
|
||||
|
||||
static int updateAppendonly(const char **err) {
|
||||
/* If loading flag is set, AOF might have been stopped temporarily, and it
|
||||
* will be restarted depending on server.aof_enabled flag after loading is
|
||||
* completed. So, we just need to update 'server.aof_enabled' which has been
|
||||
* updated already before calling this function. */
|
||||
if (server.loading)
|
||||
return 1;
|
||||
|
||||
if (!server.aof_enabled && server.aof_state != AOF_OFF) {
|
||||
stopAppendOnly();
|
||||
} else if (server.aof_enabled && server.aof_state == AOF_OFF) {
|
||||
@@ -2540,11 +2548,10 @@ static int updateMaxclients(const char **err) {
|
||||
*err = msg;
|
||||
return 0;
|
||||
}
|
||||
if ((unsigned int) aeGetSetSize(server.el) <
|
||||
server.maxclients + CONFIG_FDSET_INCR)
|
||||
{
|
||||
if (aeResizeSetSize(server.el,
|
||||
server.maxclients + CONFIG_FDSET_INCR) == AE_ERR)
|
||||
size_t newsize = server.maxclients + CONFIG_FDSET_INCR;
|
||||
if ((unsigned int) aeGetSetSize(server.el) < newsize) {
|
||||
if (aeResizeSetSize(server.el, newsize) == AE_ERR ||
|
||||
resizeAllIOThreadsEventLoops(newsize) == AE_ERR)
|
||||
{
|
||||
*err = "The event loop API used by Redis is not able to handle the specified number of clients";
|
||||
return 0;
|
||||
@@ -3025,6 +3032,7 @@ static int applyClientMaxMemoryUsage(const char **err) {
|
||||
if (server.maxmemory_clients != 0)
|
||||
initServerClientMemUsageBuckets();
|
||||
|
||||
pauseAllIOThreads();
|
||||
/* When client eviction is enabled update memory buckets for all clients.
|
||||
* When disabled, clear that data structure. */
|
||||
listRewind(server.clients, &li);
|
||||
@@ -3038,6 +3046,7 @@ static int applyClientMaxMemoryUsage(const char **err) {
|
||||
updateClientMemUsageAndBucket(c);
|
||||
}
|
||||
}
|
||||
resumeAllIOThreads();
|
||||
|
||||
if (server.maxmemory_clients == 0)
|
||||
freeServerClientMemUsageBuckets();
|
||||
@@ -3080,7 +3089,7 @@ standardConfig static_configs[] = {
|
||||
createBoolConfig("activedefrag", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.active_defrag_enabled, 0, isValidActiveDefrag, NULL),
|
||||
createBoolConfig("syslog-enabled", NULL, IMMUTABLE_CONFIG, server.syslog_enabled, 0, NULL, NULL),
|
||||
createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, server.cluster_enabled, 0, NULL, NULL),
|
||||
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG | DENY_LOADING_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),
|
||||
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),
|
||||
createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_reads_when_down, 0, NULL, NULL),
|
||||
createBoolConfig("cluster-allow-pubsubshard-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_pubsubshard_when_down, 1, NULL, NULL),
|
||||
createBoolConfig("crash-log-enabled", NULL, MODIFIABLE_CONFIG, server.crashlog_enabled, 1, NULL, updateSighandlerEnabled),
|
||||
|
||||
@@ -32,6 +32,14 @@
|
||||
#define redis_stat stat
|
||||
#endif
|
||||
|
||||
#ifndef CACHE_LINE_SIZE
|
||||
#if defined(__aarch64__) && defined(__APPLE__)
|
||||
#define CACHE_LINE_SIZE 128
|
||||
#else
|
||||
#define CACHE_LINE_SIZE 64
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Test for proc filesystem */
|
||||
#ifdef __linux__
|
||||
#define HAVE_PROC_STAT 1
|
||||
@@ -39,6 +47,7 @@
|
||||
#define HAVE_PROC_SMAPS 1
|
||||
#define HAVE_PROC_SOMAXCONN 1
|
||||
#define HAVE_PROC_OOM_SCORE_ADJ 1
|
||||
#define HAVE_EVENT_FD 1
|
||||
#endif
|
||||
|
||||
/* Test for task_info() */
|
||||
@@ -299,4 +308,15 @@ void setcpuaffinity(const char *cpulist);
|
||||
#define HAVE_FADVISE
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) && ((defined(__GNUC__) && __GNUC__ > 5) || (defined(__clang__)))
|
||||
#if defined(__has_attribute) && __has_attribute(target)
|
||||
#define HAVE_POPCNT
|
||||
#define ATTRIBUTE_TARGET_POPCNT __attribute__((target("popcnt")))
|
||||
#else
|
||||
#define ATTRIBUTE_TARGET_POPCNT
|
||||
#endif
|
||||
#else
|
||||
#define ATTRIBUTE_TARGET_POPCNT
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+4
-4
@@ -156,14 +156,14 @@ void connTypeCleanupAll(void) {
|
||||
}
|
||||
|
||||
/* walk all the connection types until has pending data */
|
||||
int connTypeHasPendingData(void) {
|
||||
int connTypeHasPendingData(struct aeEventLoop *el) {
|
||||
ConnectionType *ct;
|
||||
int type;
|
||||
int ret = 0;
|
||||
|
||||
for (type = 0; type < CONN_TYPE_MAX; type++) {
|
||||
ct = connTypes[type];
|
||||
if (ct && ct->has_pending_data && (ret = ct->has_pending_data())) {
|
||||
if (ct && ct->has_pending_data && (ret = ct->has_pending_data(el))) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@ int connTypeHasPendingData(void) {
|
||||
}
|
||||
|
||||
/* walk all the connection types and process pending data for each connection type */
|
||||
int connTypeProcessPendingData(void) {
|
||||
int connTypeProcessPendingData(struct aeEventLoop *el) {
|
||||
ConnectionType *ct;
|
||||
int type;
|
||||
int ret = 0;
|
||||
@@ -180,7 +180,7 @@ int connTypeProcessPendingData(void) {
|
||||
for (type = 0; type < CONN_TYPE_MAX; type++) {
|
||||
ct = connTypes[type];
|
||||
if (ct && ct->process_pending_data) {
|
||||
ret += ct->process_pending_data();
|
||||
ret += ct->process_pending_data(el);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+37
-10
@@ -60,8 +60,8 @@ typedef struct ConnectionType {
|
||||
int (*listen)(connListener *listener);
|
||||
|
||||
/* create/shutdown/close connection */
|
||||
connection* (*conn_create)(void);
|
||||
connection* (*conn_create_accepted)(int fd, void *priv);
|
||||
connection* (*conn_create)(struct aeEventLoop *el);
|
||||
connection* (*conn_create_accepted)(struct aeEventLoop *el, int fd, void *priv);
|
||||
void (*shutdown)(struct connection *conn);
|
||||
void (*close)(struct connection *conn);
|
||||
|
||||
@@ -81,9 +81,13 @@ typedef struct ConnectionType {
|
||||
ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||
ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||
|
||||
/* event loop */
|
||||
void (*unbind_event_loop)(struct connection *conn);
|
||||
int (*rebind_event_loop)(struct connection *conn, aeEventLoop *el);
|
||||
|
||||
/* pending data */
|
||||
int (*has_pending_data)(void);
|
||||
int (*process_pending_data)(void);
|
||||
int (*has_pending_data)(struct aeEventLoop *el);
|
||||
int (*process_pending_data)(struct aeEventLoop *el);
|
||||
|
||||
/* TLS specified methods */
|
||||
sds (*get_peer_cert)(struct connection *conn);
|
||||
@@ -98,6 +102,7 @@ struct connection {
|
||||
short int refs;
|
||||
unsigned short int iovcnt;
|
||||
void *private_data;
|
||||
struct aeEventLoop *el;
|
||||
ConnectionCallbackFunc conn_handler;
|
||||
ConnectionCallbackFunc write_handler;
|
||||
ConnectionCallbackFunc read_handler;
|
||||
@@ -319,6 +324,28 @@ static inline int connHasReadHandler(connection *conn) {
|
||||
return conn->read_handler != NULL;
|
||||
}
|
||||
|
||||
/* Returns true if the connection is bound to an event loop */
|
||||
static inline int connHasEventLoop(connection *conn) {
|
||||
return conn->el != NULL;
|
||||
}
|
||||
|
||||
/* Unbind the current event loop from the connection, so that it can be
|
||||
* rebind to a different event loop in the future. */
|
||||
static inline void connUnbindEventLoop(connection *conn) {
|
||||
if (conn->el == NULL) return;
|
||||
connSetReadHandler(conn, NULL);
|
||||
connSetWriteHandler(conn, NULL);
|
||||
if (conn->type->unbind_event_loop)
|
||||
conn->type->unbind_event_loop(conn);
|
||||
conn->el = NULL;
|
||||
}
|
||||
|
||||
/* Rebind the connection to another event loop, read/write handlers must not
|
||||
* be installed in the current event loop */
|
||||
static inline int connRebindEventLoop(connection *conn, aeEventLoop *el) {
|
||||
return conn->type->rebind_event_loop(conn, el);
|
||||
}
|
||||
|
||||
/* Associate a private data pointer with the connection */
|
||||
static inline void connSetPrivateData(connection *conn, void *data) {
|
||||
conn->private_data = data;
|
||||
@@ -379,14 +406,14 @@ ConnectionType *connectionTypeUnix(void);
|
||||
int connectionIndexByType(const char *typename);
|
||||
|
||||
/* Create a connection of specified type */
|
||||
static inline connection *connCreate(ConnectionType *ct) {
|
||||
return ct->conn_create();
|
||||
static inline connection *connCreate(struct aeEventLoop *el, ConnectionType *ct) {
|
||||
return ct->conn_create(el);
|
||||
}
|
||||
|
||||
/* Create an accepted connection of specified type.
|
||||
* priv is connection type specified argument */
|
||||
static inline connection *connCreateAccepted(ConnectionType *ct, int fd, void *priv) {
|
||||
return ct->conn_create_accepted(fd, priv);
|
||||
static inline connection *connCreateAccepted(struct aeEventLoop *el, ConnectionType *ct, int fd, void *priv) {
|
||||
return ct->conn_create_accepted(el, fd, priv);
|
||||
}
|
||||
|
||||
/* Configure a connection type. A typical case is to configure TLS.
|
||||
@@ -400,10 +427,10 @@ static inline int connTypeConfigure(ConnectionType *ct, void *priv, int reconfig
|
||||
void connTypeCleanupAll(void);
|
||||
|
||||
/* Test all the connection type has pending data or not. */
|
||||
int connTypeHasPendingData(void);
|
||||
int connTypeHasPendingData(struct aeEventLoop *el);
|
||||
|
||||
/* walk all the connection types and process pending data for each connection type */
|
||||
int connTypeProcessPendingData(void);
|
||||
int connTypeProcessPendingData(struct aeEventLoop *el);
|
||||
|
||||
/* Listen on an initialized listener */
|
||||
static inline int connListen(connListener *listener) {
|
||||
|
||||
@@ -21,9 +21,12 @@
|
||||
* C-level DB API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
static_assert(MAX_KEYSIZES_TYPES == OBJ_TYPE_BASIC_MAX, "Must be equal");
|
||||
|
||||
/* Flags for expireIfNeeded */
|
||||
#define EXPIRE_FORCE_DELETE_EXPIRED 1
|
||||
#define EXPIRE_AVOID_DELETE_EXPIRED 2
|
||||
#define EXPIRE_ALLOW_ACCESS_EXPIRED 4
|
||||
|
||||
/* Return values for expireIfNeeded */
|
||||
typedef enum {
|
||||
@@ -45,10 +48,55 @@ void updateLFU(robj *val) {
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | counter;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update histogram of keys-sizes
|
||||
*
|
||||
* It is used to track the distribution of key sizes in the dataset. It is updated
|
||||
* every time key's length is modified. Available to user via INFO command.
|
||||
*
|
||||
* The histogram is a base-2 logarithmic histogram, with 64 bins. The i'th bin
|
||||
* represents the number of keys with a size in the range 2^i and 2^(i+1)
|
||||
* exclusive. oldLen/newLen must be smaller than 2^48, and if their value
|
||||
* equals 0, it means that the key is being created/deleted, respectively. Each
|
||||
* data type has its own histogram and it is per database (In addition, there is
|
||||
* histogram per slot for future cluster use).
|
||||
*
|
||||
* Examples to LEN values and corresponding bins in histogram:
|
||||
* [1,2)->0 [2,4)->1 [4,8)->2 [8,16)->3
|
||||
*/
|
||||
void updateKeysizesHist(redisDb *db, int didx, uint32_t type, uint64_t oldLen, uint64_t newLen) {
|
||||
if(unlikely(type >= OBJ_TYPE_BASIC_MAX))
|
||||
return;
|
||||
|
||||
kvstoreDictMetadata *dictMeta = kvstoreGetDictMetadata(db->keys, didx);
|
||||
kvstoreMetadata *kvstoreMeta = kvstoreGetMetadata(db->keys);
|
||||
|
||||
if (oldLen != 0) {
|
||||
int old_bin = log2ceil(oldLen);
|
||||
debugServerAssertWithInfo(server.current_client, NULL, old_bin < MAX_KEYSIZES_BINS);
|
||||
/* If following a key deletion it is last one in slot's dict, then
|
||||
* slot's dict might get released as well. Verify if metadata is not NULL. */
|
||||
if(dictMeta) dictMeta->keysizes_hist[type][old_bin]--;
|
||||
kvstoreMeta->keysizes_hist[type][old_bin]--;
|
||||
}
|
||||
|
||||
if (newLen != 0) {
|
||||
int new_bin = log2ceil(newLen);
|
||||
debugServerAssertWithInfo(server.current_client, NULL, new_bin < MAX_KEYSIZES_BINS);
|
||||
/* If following a key deletion it is last one in slot's dict, then
|
||||
* slot's dict might get released as well. Verify if metadata is not NULL. */
|
||||
if(dictMeta) dictMeta->keysizes_hist[type][new_bin]++;
|
||||
kvstoreMeta->keysizes_hist[type][new_bin]++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lookup a key for read or write operations, or return NULL if the key is not
|
||||
* found in the specified DB. This function implements the functionality of
|
||||
* lookupKeyRead(), lookupKeyWrite() and their ...WithFlags() variants.
|
||||
*
|
||||
* 'deref' is an optional output dictEntry reference argument, to get the
|
||||
* associated dictEntry* of the key in case the key is found.
|
||||
*
|
||||
* Side-effects of calling this function:
|
||||
*
|
||||
* 1. A key gets expired if it reached it's TTL.
|
||||
@@ -72,7 +120,7 @@ void updateLFU(robj *val) {
|
||||
* Even if the key expiry is master-driven, we can correctly report a key is
|
||||
* expired on replicas even if the master is lagging expiring our key via DELs
|
||||
* in the replication link. */
|
||||
robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
robj *lookupKey(redisDb *db, robj *key, int flags, dictEntry **deref) {
|
||||
dictEntry *de = dbFind(db, key->ptr);
|
||||
robj *val = NULL;
|
||||
if (de) {
|
||||
@@ -91,6 +139,8 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
expire_flags |= EXPIRE_FORCE_DELETE_EXPIRED;
|
||||
if (flags & LOOKUP_NOEXPIRE)
|
||||
expire_flags |= EXPIRE_AVOID_DELETE_EXPIRED;
|
||||
if (flags & LOOKUP_ACCESS_EXPIRED)
|
||||
expire_flags |= EXPIRE_ALLOW_ACCESS_EXPIRED;
|
||||
if (expireIfNeeded(db, key, expire_flags) != KEY_VALID) {
|
||||
/* The key is no longer valid. */
|
||||
val = NULL;
|
||||
@@ -102,7 +152,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
* Don't do it if we have a saving child, as this will trigger
|
||||
* a copy on write madness. */
|
||||
if (server.current_client && server.current_client->flags & CLIENT_NO_TOUCH &&
|
||||
server.current_client->cmd->proc != touchCommand)
|
||||
server.executing_client->cmd->proc != touchCommand)
|
||||
flags |= LOOKUP_NOTOUCH;
|
||||
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
@@ -123,6 +173,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
/* TODO: Use separate misses stats and notify event for WRITE */
|
||||
}
|
||||
|
||||
if (val && deref) *deref = de;
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -137,7 +188,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
* the key. */
|
||||
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
serverAssert(!(flags & LOOKUP_WRITE));
|
||||
return lookupKey(db, key, flags);
|
||||
return lookupKey(db, key, flags, NULL);
|
||||
}
|
||||
|
||||
/* Like lookupKeyReadWithFlags(), but does not use any flag, which is the
|
||||
@@ -153,13 +204,20 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
|
||||
* Returns the linked value object if the key exists or NULL if the key
|
||||
* does not exist in the specified DB. */
|
||||
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
|
||||
return lookupKey(db, key, flags | LOOKUP_WRITE);
|
||||
return lookupKey(db, key, flags | LOOKUP_WRITE, NULL);
|
||||
}
|
||||
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
|
||||
}
|
||||
|
||||
/* Like lookupKeyWrite(), but accepts an optional dictEntry input,
|
||||
* which can be used if we already have one, thus saving the dbFind call.
|
||||
*/
|
||||
robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref) {
|
||||
return lookupKey(db, key, LOOKUP_NONE | LOOKUP_WRITE, deref);
|
||||
}
|
||||
|
||||
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
|
||||
robj *o = lookupKeyRead(c->db, key);
|
||||
if (!o) addReplyOrErrorObject(c, reply);
|
||||
@@ -191,6 +249,7 @@ static dictEntry *dbAddInternal(redisDb *db, robj *key, robj *val, int update_if
|
||||
kvstoreDictSetVal(db->keys, slot, de, val);
|
||||
signalKeyAsReady(db, key, val->type);
|
||||
notifyKeyspaceEvent(NOTIFY_NEW,"new",key,db->id);
|
||||
updateKeysizesHist(db, slot, val->type, 0, getObjectLength(val)); /* add hist */
|
||||
return de;
|
||||
}
|
||||
|
||||
@@ -236,6 +295,7 @@ int dbAddRDBLoad(redisDb *db, sds key, robj *val) {
|
||||
int slot = getKeySlot(key);
|
||||
dictEntry *de = kvstoreDictAddRaw(db->keys, slot, key, NULL);
|
||||
if (de == NULL) return 0;
|
||||
updateKeysizesHist(db, slot, val->type, 0, getObjectLength(val)); /* add hist */
|
||||
initObjectLRUOrLFU(val);
|
||||
kvstoreDictSetVal(db->keys, slot, de, val);
|
||||
return 1;
|
||||
@@ -259,6 +319,9 @@ static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEnt
|
||||
serverAssertWithInfo(NULL,key,de != NULL);
|
||||
robj *old = dictGetVal(de);
|
||||
|
||||
/* Remove old key from keysizes histogram */
|
||||
updateKeysizesHist(db, slot, old->type, getObjectLength(old), 0); /* remove hist */
|
||||
|
||||
val->lru = old->lru;
|
||||
|
||||
if (overwrite) {
|
||||
@@ -277,6 +340,9 @@ static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEnt
|
||||
}
|
||||
kvstoreDictSetVal(db->keys, slot, de, val);
|
||||
|
||||
/* Add new key to keysizes histogram */
|
||||
updateKeysizesHist(db, slot, val->type, 0, getObjectLength(val));
|
||||
|
||||
/* if hash with HFEs, take care to remove from global HFE DS */
|
||||
if (old->type == OBJ_HASH)
|
||||
hashTypeRemoveFromExpires(&db->hexpires, old);
|
||||
@@ -294,6 +360,14 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) {
|
||||
dbSetValue(db, key, val, 0, NULL);
|
||||
}
|
||||
|
||||
/* Replace an existing key with a new value, we just replace value and don't
|
||||
* emit any events.
|
||||
* The dictEntry input is optional, can be used if we already have one.
|
||||
*/
|
||||
void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de) {
|
||||
dbSetValue(db, key, val, 0, de);
|
||||
}
|
||||
|
||||
/* High level Set operation. This function can be used in order to set
|
||||
* a key, whatever it was existing or not, to a new object.
|
||||
*
|
||||
@@ -308,6 +382,12 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) {
|
||||
* The client 'c' argument may be set to NULL if the operation is performed
|
||||
* in a context where there is no clear client performing the operation. */
|
||||
void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
|
||||
setKeyWithDictEntry(c,db,key,val,flags,NULL);
|
||||
}
|
||||
|
||||
/* Like setKey(), but accepts an optional dictEntry input,
|
||||
* which can be used if we already have one, thus saving the dictFind call. */
|
||||
void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de) {
|
||||
int keyfound = 0;
|
||||
|
||||
if (flags & SETKEY_ALREADY_EXIST)
|
||||
@@ -322,7 +402,7 @@ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
|
||||
} else if (keyfound<0) {
|
||||
dbAddInternal(db,key,val,1);
|
||||
} else {
|
||||
dbSetValue(db,key,val,1,NULL);
|
||||
dbSetValue(db,key,val,1,de);
|
||||
}
|
||||
incrRefCount(val);
|
||||
if (!(flags & SETKEY_KEEPTTL)) removeExpire(db,key);
|
||||
@@ -347,23 +427,22 @@ robj *dbRandomKey(redisDb *db) {
|
||||
|
||||
key = dictGetKey(de);
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
if (dbFindExpires(db, key)) {
|
||||
if (allvolatile && server.masterhost && --maxtries == 0) {
|
||||
/* If the DB is composed only of keys with an expire set,
|
||||
* it could happen that all the keys are already logically
|
||||
* expired in the slave, so the function cannot stop because
|
||||
* expireIfNeeded() is false, nor it can stop because
|
||||
* dictGetFairRandomKey() returns NULL (there are keys to return).
|
||||
* To prevent the infinite loop we do some tries, but if there
|
||||
* are the conditions for an infinite loop, eventually we
|
||||
* return a key name that may be already expired. */
|
||||
return keyobj;
|
||||
}
|
||||
if (expireIfNeeded(db,keyobj,0) != KEY_VALID) {
|
||||
decrRefCount(keyobj);
|
||||
continue; /* search for another key. This expired. */
|
||||
}
|
||||
if (allvolatile && server.masterhost && --maxtries == 0) {
|
||||
/* If the DB is composed only of keys with an expire set,
|
||||
* it could happen that all the keys are already logically
|
||||
* expired in the slave, so the function cannot stop because
|
||||
* expireIfNeeded() is false, nor it can stop because
|
||||
* dictGetFairRandomKey() returns NULL (there are keys to return).
|
||||
* To prevent the infinite loop we do some tries, but if there
|
||||
* are the conditions for an infinite loop, eventually we
|
||||
* return a key name that may be already expired. */
|
||||
return keyobj;
|
||||
}
|
||||
if (expireIfNeeded(db,keyobj,0) != KEY_VALID) {
|
||||
decrRefCount(keyobj);
|
||||
continue; /* search for another key. This expired. */
|
||||
}
|
||||
|
||||
return keyobj;
|
||||
}
|
||||
}
|
||||
@@ -377,6 +456,9 @@ int dbGenericDelete(redisDb *db, robj *key, int async, int flags) {
|
||||
if (de) {
|
||||
robj *val = dictGetVal(de);
|
||||
|
||||
/* remove key from histogram */
|
||||
updateKeysizesHist(db, slot, val->type, getObjectLength(val), 0);
|
||||
|
||||
/* If hash object with expiry on fields, remove it from HFE DS of DB */
|
||||
if (val->type == OBJ_HASH)
|
||||
hashTypeRemoveFromExpires(&db->hexpires, val);
|
||||
@@ -452,12 +534,18 @@ int dbDelete(redisDb *db, robj *key) {
|
||||
* using an sdscat() call to append some data, or anything else.
|
||||
*/
|
||||
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
return dbUnshareStringValueWithDictEntry(db,key,o,NULL);
|
||||
}
|
||||
|
||||
/* Like dbUnshareStringValue(), but accepts a optional dictEntry,
|
||||
* which can be used if we already have one, thus saving the dbFind call. */
|
||||
robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de) {
|
||||
serverAssert(o->type == OBJ_STRING);
|
||||
if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbReplaceValue(db,key,o);
|
||||
dbReplaceValueWithDictEntry(db,key,o,de);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
@@ -566,7 +654,8 @@ redisDb *initTempDb(void) {
|
||||
redisDb *tempDb = zcalloc(sizeof(redisDb)*server.dbnum);
|
||||
for (int i=0; i<server.dbnum; i++) {
|
||||
tempDb[i].id = i;
|
||||
tempDb[i].keys = kvstoreCreate(&dbDictType, slot_count_bits, flags);
|
||||
tempDb[i].keys = kvstoreCreate(&dbDictType, slot_count_bits,
|
||||
flags | KVSTORE_ALLOC_META_KEYS_HIST);
|
||||
tempDb[i].expires = kvstoreCreate(&dbExpiresDictType, slot_count_bits, flags);
|
||||
tempDb[i].hexpires = ebCreate();
|
||||
}
|
||||
@@ -575,11 +664,11 @@ redisDb *initTempDb(void) {
|
||||
}
|
||||
|
||||
/* Discard tempDb, this can be slow (similar to FLUSHALL), but it's always async. */
|
||||
void discardTempDb(redisDb *tempDb, void(callback)(dict*)) {
|
||||
void discardTempDb(redisDb *tempDb) {
|
||||
int async = 1;
|
||||
|
||||
/* Release temp DBs. */
|
||||
emptyDbStructure(tempDb, -1, async, callback);
|
||||
emptyDbStructure(tempDb, -1, async, NULL);
|
||||
for (int i=0; i<server.dbnum; i++) {
|
||||
/* Destroy global HFE DS before deleting the hashes since ebuckets DS is
|
||||
* embedded in the stored objects. */
|
||||
@@ -696,9 +785,12 @@ void flushAllDataAndResetRDB(int flags) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Optimized FLUSHALL\FLUSHDB SYNC command finished to run by lazyfree thread */
|
||||
void flushallSyncBgDone(uint64_t client_id) {
|
||||
|
||||
/* CB function on blocking ASYNC FLUSH completion
|
||||
*
|
||||
* Utilized by commands SFLUSH, FLUSHALL and FLUSHDB.
|
||||
*/
|
||||
void flushallSyncBgDone(uint64_t client_id, void *sflush) {
|
||||
SlotsFlush *slotsFlush = sflush;
|
||||
client *c = lookupClientByID(client_id);
|
||||
|
||||
/* Verify that client still exists */
|
||||
@@ -711,8 +803,11 @@ void flushallSyncBgDone(uint64_t client_id) {
|
||||
/* Don't update blocked_us since command was processed in bg by lazy_free thread */
|
||||
updateStatsOnUnblock(c, 0 /*blocked_us*/, elapsedUs(c->bstate.lazyfreeStartTime), 0);
|
||||
|
||||
/* lazyfree bg job always succeed */
|
||||
addReply(c, shared.ok);
|
||||
/* Only SFLUSH command pass pointer to `SlotsFlush` */
|
||||
if (slotsFlush)
|
||||
replySlotsFlushAndFree(c, slotsFlush);
|
||||
else
|
||||
addReply(c, shared.ok);
|
||||
|
||||
/* mark client as unblocked */
|
||||
unblockClient(c, 1);
|
||||
@@ -727,10 +822,17 @@ void flushallSyncBgDone(uint64_t client_id) {
|
||||
server.current_client = old_client;
|
||||
}
|
||||
|
||||
void flushCommandCommon(client *c, int isFlushAll) {
|
||||
int blocking_async = 0; /* FLUSHALL\FLUSHDB SYNC opt to run as blocking ASYNC */
|
||||
int flags;
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
/* Common flush command implementation for FLUSHALL and FLUSHDB.
|
||||
*
|
||||
* Return 1 indicates that flush SYNC is actually running in bg as blocking ASYNC
|
||||
* Return 0 otherwise
|
||||
*
|
||||
* sflush - provided only by SFLUSH command, otherwise NULL. Will be used on
|
||||
* completion to reply with the slots flush result. Ownership is passed
|
||||
* to the completion job in case of `blocking_async`.
|
||||
*/
|
||||
int flushCommandCommon(client *c, int type, int flags, SlotsFlush *sflush) {
|
||||
int blocking_async = 0; /* Flush SYNC option to run as blocking ASYNC */
|
||||
|
||||
/* in case of SYNC, check if we can optimize and run it in bg as blocking ASYNC */
|
||||
if ((!(flags & EMPTYDB_ASYNC)) && (!(c->flags & CLIENT_AVOID_BLOCKING_ASYNC_FLUSH))) {
|
||||
@@ -739,7 +841,7 @@ void flushCommandCommon(client *c, int isFlushAll) {
|
||||
blocking_async = 1;
|
||||
}
|
||||
|
||||
if (isFlushAll)
|
||||
if (type == FLUSH_TYPE_ALL)
|
||||
flushAllDataAndResetRDB(flags | EMPTYDB_NOFUNCTIONS);
|
||||
else
|
||||
server.dirty += emptyData(c->db->id,flags | EMPTYDB_NOFUNCTIONS,NULL);
|
||||
@@ -757,10 +859,9 @@ void flushCommandCommon(client *c, int isFlushAll) {
|
||||
|
||||
c->bstate.timeout = 0;
|
||||
blockClient(c,BLOCKED_LAZYFREE);
|
||||
bioCreateCompRq(BIO_WORKER_LAZY_FREE, flushallSyncBgDone, c->id);
|
||||
} else {
|
||||
addReply(c, shared.ok);
|
||||
bioCreateCompRq(BIO_WORKER_LAZY_FREE, flushallSyncBgDone, c->id, sflush);
|
||||
}
|
||||
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||
@@ -768,7 +869,7 @@ void flushCommandCommon(client *c, int isFlushAll) {
|
||||
*
|
||||
* Take care purge only FLUSHDB for sync flow. FLUSHALL sync flow already
|
||||
* applied at flushAllDataAndResetRDB. Async flow will apply only later on */
|
||||
if ((!isFlushAll) && (!(flags & EMPTYDB_ASYNC))) {
|
||||
if ((type != FLUSH_TYPE_ALL) && (!(flags & EMPTYDB_ASYNC))) {
|
||||
/* Only clear the current thread cache.
|
||||
* Ignore the return call since this will fail if the tcache is disabled. */
|
||||
je_mallctl("thread.tcache.flush", NULL, NULL, NULL, 0);
|
||||
@@ -776,20 +877,32 @@ void flushCommandCommon(client *c, int isFlushAll) {
|
||||
jemalloc_purge();
|
||||
}
|
||||
#endif
|
||||
return blocking_async;
|
||||
}
|
||||
|
||||
/* FLUSHALL [SYNC|ASYNC]
|
||||
*
|
||||
* Flushes the whole server data set. */
|
||||
void flushallCommand(client *c) {
|
||||
flushCommandCommon(c, 1);
|
||||
int flags;
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
|
||||
/* If FLUSH SYNC isn't running as blocking async, then reply */
|
||||
if (flushCommandCommon(c, FLUSH_TYPE_ALL, flags, NULL) == 0)
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
|
||||
/* FLUSHDB [SYNC|ASYNC]
|
||||
*
|
||||
* Flushes the currently SELECTed Redis DB. */
|
||||
void flushdbCommand(client *c) {
|
||||
flushCommandCommon(c, 0);
|
||||
int flags;
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
|
||||
/* If FLUSH SYNC isn't running as blocking async, then reply */
|
||||
if (flushCommandCommon(c, FLUSH_TYPE_DB,flags, NULL) == 0)
|
||||
addReply(c, shared.ok);
|
||||
|
||||
}
|
||||
|
||||
/* This command implements DEL and UNLINK. */
|
||||
@@ -944,11 +1057,10 @@ void scanCallback(void *privdata, const dictEntry *de) {
|
||||
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. */
|
||||
void *keyStr = dictGetKey(de);
|
||||
@@ -1095,9 +1207,8 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
|
||||
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; */
|
||||
return;
|
||||
}
|
||||
i+= 2;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "novalues")) {
|
||||
@@ -1194,50 +1305,95 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
|
||||
}
|
||||
} while (cursor && maxiterations-- && data.sampled < count);
|
||||
} else if (o->type == OBJ_SET) {
|
||||
unsigned long array_reply_len = 0;
|
||||
void *replylen = NULL;
|
||||
listRelease(keys);
|
||||
char *str;
|
||||
char buf[LONG_STR_SIZE];
|
||||
size_t len;
|
||||
int64_t llele;
|
||||
/* Reply to the client. */
|
||||
addReplyArrayLen(c, 2);
|
||||
/* Cursor is always 0 given we iterate over all set */
|
||||
addReplyBulkLongLong(c,0);
|
||||
/* If there is no pattern the length is the entire set size, otherwise we defer the reply size */
|
||||
if (use_pattern)
|
||||
replylen = addReplyDeferredLen(c);
|
||||
else {
|
||||
array_reply_len = setTypeSize(o);
|
||||
addReplyArrayLen(c, array_reply_len);
|
||||
}
|
||||
|
||||
setTypeIterator *si = setTypeInitIterator(o);
|
||||
unsigned long cur_length = 0;
|
||||
while (setTypeNext(si, &str, &len, &llele) != -1) {
|
||||
if (str == NULL) {
|
||||
len = ll2string(buf, sizeof(buf), llele);
|
||||
}
|
||||
char *key = str ? str : buf;
|
||||
if (use_pattern && !stringmatchlen(pat, sdslen(pat), key, len, 0)) {
|
||||
if (use_pattern && !stringmatchlen(pat, patlen, key, len, 0)) {
|
||||
continue;
|
||||
}
|
||||
listAddNodeTail(keys, sdsnewlen(key, len));
|
||||
addReplyBulkCBuffer(c, key, len);
|
||||
cur_length++;
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
cursor = 0;
|
||||
if (use_pattern)
|
||||
setDeferredArrayLen(c,replylen,cur_length);
|
||||
else
|
||||
serverAssert(cur_length == array_reply_len); /* fail on corrupt data */
|
||||
return;
|
||||
} else if ((o->type == OBJ_HASH || o->type == OBJ_ZSET) &&
|
||||
o->encoding == OBJ_ENCODING_LISTPACK)
|
||||
{
|
||||
unsigned char *p = lpFirst(o->ptr);
|
||||
unsigned char *str;
|
||||
int64_t len;
|
||||
unsigned long array_reply_len = 0;
|
||||
unsigned char intbuf[LP_INTBUF_SIZE];
|
||||
void *replylen = NULL;
|
||||
listRelease(keys);
|
||||
|
||||
/* Reply to the client. */
|
||||
addReplyArrayLen(c, 2);
|
||||
/* Cursor is always 0 given we iterate over all set */
|
||||
addReplyBulkLongLong(c,0);
|
||||
/* If there is no pattern the length is the entire set size, otherwise we defer the reply size */
|
||||
if (use_pattern)
|
||||
replylen = addReplyDeferredLen(c);
|
||||
else {
|
||||
array_reply_len = o->type == OBJ_HASH ? hashTypeLength(o, 0) : zsetLength(o);
|
||||
if (!no_values) {
|
||||
array_reply_len *= 2;
|
||||
}
|
||||
addReplyArrayLen(c, array_reply_len);
|
||||
}
|
||||
unsigned long cur_length = 0;
|
||||
while(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)) {
|
||||
if (use_pattern && !stringmatchlen(pat, patlen, (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));
|
||||
addReplyBulkCBuffer(c, str, len);
|
||||
cur_length++;
|
||||
/* add value object */
|
||||
if (!no_values) {
|
||||
str = lpGet(p, &len, intbuf);
|
||||
listAddNodeTail(keys, sdsnewlen(str, len));
|
||||
addReplyBulkCBuffer(c, str, len);
|
||||
cur_length++;
|
||||
}
|
||||
p = lpNext(o->ptr, p);
|
||||
}
|
||||
cursor = 0;
|
||||
if (use_pattern)
|
||||
setDeferredArrayLen(c,replylen,cur_length);
|
||||
else
|
||||
serverAssert(cur_length == array_reply_len); /* fail on corrupt data */
|
||||
return;
|
||||
} else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_LISTPACK_EX) {
|
||||
int64_t len;
|
||||
long long expire_at;
|
||||
@@ -1245,6 +1401,16 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
|
||||
unsigned char *p = lpFirst(lp);
|
||||
unsigned char *str, *val;
|
||||
unsigned char intbuf[LP_INTBUF_SIZE];
|
||||
void *replylen = NULL;
|
||||
|
||||
listRelease(keys);
|
||||
/* Reply to the client. */
|
||||
addReplyArrayLen(c, 2);
|
||||
/* Cursor is always 0 given we iterate over all set */
|
||||
addReplyBulkLongLong(c,0);
|
||||
/* In the case of OBJ_ENCODING_LISTPACK_EX we always defer the reply size given some fields might be expired */
|
||||
replylen = addReplyDeferredLen(c);
|
||||
unsigned long cur_length = 0;
|
||||
|
||||
while (p) {
|
||||
str = lpGet(p, &len, intbuf);
|
||||
@@ -1255,7 +1421,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
|
||||
serverAssert(p && lpGetIntegerValue(p, &expire_at));
|
||||
|
||||
if (hashTypeIsExpired(o, expire_at) ||
|
||||
(use_pattern && !stringmatchlen(pat, sdslen(pat), (char *)str, len, 0)))
|
||||
(use_pattern && !stringmatchlen(pat, patlen, (char *)str, len, 0)))
|
||||
{
|
||||
/* jump to the next key/val pair */
|
||||
p = lpNext(lp, p);
|
||||
@@ -1263,15 +1429,18 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
|
||||
}
|
||||
|
||||
/* add key object */
|
||||
listAddNodeTail(keys, sdsnewlen(str, len));
|
||||
addReplyBulkCBuffer(c, str, len);
|
||||
cur_length++;
|
||||
/* add value object */
|
||||
if (!no_values) {
|
||||
str = lpGet(val, &len, intbuf);
|
||||
listAddNodeTail(keys, sdsnewlen(str, len));
|
||||
addReplyBulkCBuffer(c, str, len);
|
||||
cur_length++;
|
||||
}
|
||||
p = lpNext(lp, p);
|
||||
}
|
||||
cursor = 0;
|
||||
setDeferredArrayLen(c,replylen,cur_length);
|
||||
return;
|
||||
} else {
|
||||
serverPanic("Not handled encoding in SCAN.");
|
||||
}
|
||||
@@ -1285,16 +1454,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
|
||||
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) != KEY_VALID) {
|
||||
if (expireIfNeeded(c->db, &kobj, 0)) {
|
||||
listDelNode(keys, ln);
|
||||
}
|
||||
}
|
||||
@@ -1839,16 +1999,23 @@ int removeExpire(redisDb *db, robj *key) {
|
||||
return kvstoreDictDelete(db->expires, getKeySlot(key->ptr), key->ptr) == DICT_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Set an expire to the specified key. If the expire is set in the context
|
||||
* of an user calling a command 'c' is the client, otherwise 'c' is set
|
||||
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
|
||||
* after which the key will no longer be considered valid. */
|
||||
void setExpire(client *c, redisDb *db, robj *key, long long when) {
|
||||
dictEntry *kde, *de, *existing;
|
||||
setExpireWithDictEntry(c,db,key,when,NULL);
|
||||
}
|
||||
|
||||
/* Like setExpire(), but accepts an optional dictEntry input,
|
||||
* which can be used if we already have one, thus saving the kvstoreDictFind call. */
|
||||
void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde) {
|
||||
dictEntry *de, *existing;
|
||||
|
||||
/* Reuse the sds from the main dict in the expire dict */
|
||||
int slot = getKeySlot(key->ptr);
|
||||
kde = kvstoreDictFind(db->keys, slot, key->ptr);
|
||||
if (!kde) kde = kvstoreDictFind(db->keys, slot, key->ptr);
|
||||
serverAssertWithInfo(NULL,key,kde != NULL);
|
||||
de = kvstoreDictAddRaw(db->expires, slot, dictGetKey(kde), &existing);
|
||||
if (existing) {
|
||||
@@ -1873,17 +2040,72 @@ long long getExpire(redisDb *db, robj *key) {
|
||||
return dictGetSignedIntegerVal(de);
|
||||
}
|
||||
|
||||
/* Delete the specified expired key and propagate expire. */
|
||||
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) {
|
||||
mstime_t expire_latency;
|
||||
latencyStartMonitor(expire_latency);
|
||||
dbGenericDelete(db,keyobj,server.lazyfree_lazy_expire,DB_FLAG_KEY_EXPIRED);
|
||||
latencyEndMonitor(expire_latency);
|
||||
latencyAddSampleIfNeeded("expire-del",expire_latency);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,"expired",keyobj,db->id);
|
||||
|
||||
/* Delete the specified expired or evicted key and propagate to replicas.
|
||||
* Currently notify_type can only be NOTIFY_EXPIRED or NOTIFY_EVICTED,
|
||||
* and it affects other aspects like the latency monitor event name and,
|
||||
* which config to look for lazy free, stats var to increment, and so on.
|
||||
*
|
||||
* key_mem_freed is an out parameter which contains the estimated
|
||||
* amount of memory freed due to the trimming (may be NULL) */
|
||||
static void deleteKeyAndPropagate(redisDb *db, robj *keyobj, int notify_type, long long *key_mem_freed) {
|
||||
mstime_t latency;
|
||||
int del_flag = notify_type == NOTIFY_EXPIRED ? DB_FLAG_KEY_EXPIRED : DB_FLAG_KEY_EVICTED;
|
||||
int lazy_flag = notify_type == NOTIFY_EXPIRED ? server.lazyfree_lazy_expire : server.lazyfree_lazy_eviction;
|
||||
char *latency_name = notify_type == NOTIFY_EXPIRED ? "expire-del" : "evict-del";
|
||||
char *notify_name = notify_type == NOTIFY_EXPIRED ? "expired" : "evicted";
|
||||
|
||||
/* The key needs to be converted from static to heap before deleted */
|
||||
int static_key = keyobj->refcount == OBJ_STATIC_REFCOUNT;
|
||||
if (static_key) {
|
||||
keyobj = createStringObject(keyobj->ptr, sdslen(keyobj->ptr));
|
||||
}
|
||||
|
||||
serverLog(LL_DEBUG,"key %s %s: deleting it", (char*)keyobj->ptr, notify_type == NOTIFY_EXPIRED ? "expired" : "evicted");
|
||||
|
||||
/* We compute the amount of memory freed by db*Delete() alone.
|
||||
* It is possible that actually the memory needed to propagate
|
||||
* the DEL in AOF and replication link is greater than the one
|
||||
* we are freeing removing the key, but we can't account for
|
||||
* that otherwise we would never exit the loop.
|
||||
*
|
||||
* Same for CSC invalidation messages generated by signalModifiedKey.
|
||||
*
|
||||
* AOF and Output buffer memory will be freed eventually so
|
||||
* we only care about memory used by the key space.
|
||||
*
|
||||
* The code here used to first propagate and then record delta
|
||||
* using only zmalloc_used_memory but in CRDT we can't do that
|
||||
* so we use freeMemoryGetNotCountedMemory to avoid counting
|
||||
* AOF and slave buffers */
|
||||
if (key_mem_freed) *key_mem_freed = (long long) zmalloc_used_memory() - freeMemoryGetNotCountedMemory();
|
||||
latencyStartMonitor(latency);
|
||||
dbGenericDelete(db, keyobj, lazy_flag, del_flag);
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded(latency_name, latency);
|
||||
if (key_mem_freed) *key_mem_freed -= (long long) zmalloc_used_memory() - freeMemoryGetNotCountedMemory();
|
||||
|
||||
notifyKeyspaceEvent(notify_type, notify_name,keyobj, db->id);
|
||||
signalModifiedKey(NULL, db, keyobj);
|
||||
propagateDeletion(db,keyobj,server.lazyfree_lazy_expire);
|
||||
server.stat_expiredkeys++;
|
||||
propagateDeletion(db, keyobj, lazy_flag);
|
||||
|
||||
if (notify_type == NOTIFY_EXPIRED)
|
||||
server.stat_expiredkeys++;
|
||||
else
|
||||
server.stat_evictedkeys++;
|
||||
|
||||
if (static_key)
|
||||
decrRefCount(keyobj);
|
||||
}
|
||||
|
||||
/* Delete the specified expired key and propagate. */
|
||||
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) {
|
||||
deleteKeyAndPropagate(db, keyobj, NOTIFY_EXPIRED, NULL);
|
||||
}
|
||||
|
||||
/* Delete the specified evicted key and propagate. */
|
||||
void deleteEvictedKeyAndPropagate(redisDb *db, robj *keyobj, long long *key_mem_freed) {
|
||||
deleteKeyAndPropagate(db, keyobj, NOTIFY_EVICTED, key_mem_freed);
|
||||
}
|
||||
|
||||
/* Propagate an implicit key deletion into replicas and the AOF file.
|
||||
@@ -1966,14 +2188,17 @@ int keyIsExpired(redisDb *db, robj *key) {
|
||||
*
|
||||
* On the other hand, if you just want expiration check, but need to avoid
|
||||
* the actual key deletion and propagation of the deletion, use the
|
||||
* EXPIRE_AVOID_DELETE_EXPIRED flag.
|
||||
* EXPIRE_AVOID_DELETE_EXPIRED flag. If also needed to read expired key (that
|
||||
* hasn't being deleted yet) then use EXPIRE_ALLOW_ACCESS_EXPIRED.
|
||||
*
|
||||
* The return value of the function is KEY_VALID if the key is still valid.
|
||||
* The function returns KEY_EXPIRED if the key is expired BUT not deleted,
|
||||
* or returns KEY_DELETED if the key is expired and deleted. */
|
||||
keyStatus expireIfNeeded(redisDb *db, robj *key, int flags) {
|
||||
if (server.lazy_expire_disabled) return KEY_VALID;
|
||||
if (!keyIsExpired(db,key)) return KEY_VALID;
|
||||
if ((server.allow_access_expired) ||
|
||||
(flags & EXPIRE_ALLOW_ACCESS_EXPIRED) ||
|
||||
(!keyIsExpired(db,key)))
|
||||
return KEY_VALID;
|
||||
|
||||
/* If we are running in the context of a replica, instead of
|
||||
* evicting the expired key from the database, we return ASAP:
|
||||
@@ -2003,16 +2228,9 @@ keyStatus 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 KEY_EXPIRED;
|
||||
|
||||
/* 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 KEY_DELETED;
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -15,6 +15,7 @@
|
||||
#include "bio.h"
|
||||
#include "quicklist.h"
|
||||
#include "fpconv_dtoa.h"
|
||||
#include "fast_float_strtod.h"
|
||||
#include "cluster.h"
|
||||
#include "threads_mngr.h"
|
||||
#include "script.h"
|
||||
@@ -567,6 +568,7 @@ NULL
|
||||
addReplyError(c,"Error trying to load the RDB dump, check server logs.");
|
||||
return;
|
||||
}
|
||||
applyAppendOnlyConfig(); /* Check if AOF config was changed while loading */
|
||||
serverLog(LL_NOTICE,"DB reloaded by DEBUG RELOAD");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
|
||||
@@ -582,6 +584,7 @@ NULL
|
||||
addReplyError(c, "Error trying to load the AOF files, check server logs.");
|
||||
return;
|
||||
}
|
||||
applyAppendOnlyConfig(); /* Check if AOF config was changed while loading */
|
||||
server.dirty = 0; /* Prevent AOF / replication */
|
||||
serverLog(LL_NOTICE,"Append Only File loaded by DEBUG LOADAOF");
|
||||
addReply(c,shared.ok);
|
||||
@@ -831,7 +834,7 @@ NULL
|
||||
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false");
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
|
||||
double dtime = strtod(c->argv[2]->ptr,NULL);
|
||||
double dtime = fast_float_strtod(c->argv[2]->ptr,NULL);
|
||||
long long utime = dtime*1000000;
|
||||
struct timespec tv;
|
||||
|
||||
@@ -2390,6 +2393,8 @@ void removeSigSegvHandlers(void) {
|
||||
}
|
||||
|
||||
void printCrashReport(void) {
|
||||
server.crashing = 1;
|
||||
|
||||
/* Log INFO and CLIENT LIST */
|
||||
logServerInfo();
|
||||
|
||||
|
||||
+34
-3
@@ -54,6 +54,17 @@ void* activeDefragAlloc(void *ptr) {
|
||||
return newptr;
|
||||
}
|
||||
|
||||
/* Raw memory allocation for defrag, avoid using tcache. */
|
||||
void *activeDefragAllocRaw(size_t size) {
|
||||
return zmalloc_no_tcache(size);
|
||||
}
|
||||
|
||||
/* Raw memory free for defrag, avoid using tcache. */
|
||||
void activeDefragFreeRaw(void *ptr) {
|
||||
zfree_no_tcache(ptr);
|
||||
server.stat_active_defrag_hits++;
|
||||
}
|
||||
|
||||
/*Defrag helper for sds strings
|
||||
*
|
||||
* returns NULL in case the allocation wasn't moved.
|
||||
@@ -718,8 +729,9 @@ void defragStream(redisDb *db, dictEntry *kde) {
|
||||
void defragModule(redisDb *db, dictEntry *kde) {
|
||||
robj *obj = dictGetVal(kde);
|
||||
serverAssert(obj->type == OBJ_MODULE);
|
||||
|
||||
if (!moduleDefragValue(dictGetKey(kde), obj, db->id))
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj, dictGetKey(kde));
|
||||
if (!moduleDefragValue(&keyobj, obj, db->id))
|
||||
defragLater(db, kde);
|
||||
}
|
||||
|
||||
@@ -929,7 +941,9 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime, int
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
return scanLaterStreamListpacks(ob, cursor, endtime);
|
||||
} else if (ob->type == OBJ_MODULE) {
|
||||
return moduleLateDefrag(dictGetKey(de), ob, cursor, endtime, dbid);
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj, dictGetKey(de));
|
||||
return moduleLateDefrag(&keyobj, ob, cursor, endtime, dbid);
|
||||
} else {
|
||||
*cursor = 0; /* object type may have changed since we schedule it for later */
|
||||
}
|
||||
@@ -1078,6 +1092,7 @@ void activeDefragCycle(void) {
|
||||
slot = -1;
|
||||
defrag_later_item_in_progress = 0;
|
||||
db = NULL;
|
||||
moduleDefragEnd();
|
||||
goto update_metrics;
|
||||
}
|
||||
return;
|
||||
@@ -1119,6 +1134,10 @@ void activeDefragCycle(void) {
|
||||
break; /* this will exit the function and we'll continue on the next cycle */
|
||||
}
|
||||
|
||||
if (current_db == -1) {
|
||||
moduleDefragStart();
|
||||
}
|
||||
|
||||
/* Move on to next database, and stop if we reached the last one. */
|
||||
if (++current_db >= server.dbnum) {
|
||||
/* defrag other items not part of the db / keys */
|
||||
@@ -1140,6 +1159,8 @@ void activeDefragCycle(void) {
|
||||
db = NULL;
|
||||
server.active_defrag_running = 0;
|
||||
|
||||
moduleDefragEnd();
|
||||
|
||||
computeDefragCycles(); /* if another scan is needed, start it right away */
|
||||
if (server.active_defrag_running != 0 && ustime() < endtime)
|
||||
continue;
|
||||
@@ -1259,6 +1280,16 @@ void *activeDefragAlloc(void *ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *activeDefragAllocRaw(size_t size) {
|
||||
/* fallback to regular allocation */
|
||||
return zmalloc(size);
|
||||
}
|
||||
|
||||
void activeDefragFreeRaw(void *ptr) {
|
||||
/* fallback to regular free */
|
||||
zfree(ptr);
|
||||
}
|
||||
|
||||
robj *activeDefragStringOb(robj *ob) {
|
||||
UNUSED(ob);
|
||||
return NULL;
|
||||
|
||||
+12
@@ -257,6 +257,18 @@ dictStats* dictGetStatsHt(dict *d, int htidx, int full);
|
||||
void dictCombineStats(dictStats *from, dictStats *into);
|
||||
void dictFreeStats(dictStats *stats);
|
||||
|
||||
#define dictForEach(d, ty, m, ...) do { \
|
||||
dictIterator *di = dictGetIterator(d); \
|
||||
dictEntry *de; \
|
||||
while ((de = dictNext(di)) != NULL) { \
|
||||
ty *m = dictGetVal(de); \
|
||||
do { \
|
||||
__VA_ARGS__ \
|
||||
} while(0); \
|
||||
} \
|
||||
dictReleaseIterator(di); \
|
||||
} while(0);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int dictTest(int argc, char *argv[], int flags);
|
||||
#endif
|
||||
|
||||
+144
-2
@@ -1876,6 +1876,105 @@ uint64_t ebGetExpireTime(EbucketsType *type, eItem item) {
|
||||
return ebGetMetaExpTime(meta);
|
||||
}
|
||||
|
||||
/* Init ebuckets iterator
|
||||
*
|
||||
* This is a non-safe iterator. Any modification to ebuckets will invalidate the
|
||||
* iterator. Calling this function takes care to reference the first item
|
||||
* in ebuckets with minimal expiration time. If no items to iterate, then
|
||||
* iter->currItem will be NULL and iter->itemsCurrBucket will be set to 0.
|
||||
*/
|
||||
void ebStart(EbucketsIterator *iter, ebuckets eb, EbucketsType *type) {
|
||||
iter->eb = eb;
|
||||
iter->type = type;
|
||||
iter->isRax = 0;
|
||||
|
||||
if (ebIsEmpty(eb)) {
|
||||
iter->currItem = NULL;
|
||||
iter->itemsCurrBucket = 0;
|
||||
} else if (ebIsList(eb)) {
|
||||
iter->currItem = ebGetListPtr(type, eb);
|
||||
iter->itemsCurrBucket = type->getExpireMeta(iter->currItem)->numItems;
|
||||
} else {
|
||||
rax *rax = ebGetRaxPtr(eb);
|
||||
raxStart(&iter->raxIter, rax);
|
||||
raxSeek(&iter->raxIter, "^", NULL, 0);
|
||||
raxNext(&iter->raxIter);
|
||||
FirstSegHdr *firstSegHdr = iter->raxIter.data;
|
||||
iter->itemsCurrBucket = firstSegHdr->totalItems;
|
||||
iter->currItem = firstSegHdr->head;
|
||||
iter->isRax = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Advance iterator to the next item
|
||||
*
|
||||
* Returns:
|
||||
* - 0 if the end of ebuckets has been reached, setting `iter->currItem`
|
||||
* to NULL.
|
||||
* - 1 otherwise, updating `iter->currItem` to the next item.
|
||||
*/
|
||||
int ebNext(EbucketsIterator *iter) {
|
||||
if (iter->currItem == NULL)
|
||||
return 0;
|
||||
|
||||
eItem item = iter->currItem;
|
||||
ExpireMeta *meta = iter->type->getExpireMeta(item);
|
||||
if (iter->isRax) {
|
||||
if (meta->lastItemBucket) {
|
||||
if (raxNext(&iter->raxIter)) {
|
||||
FirstSegHdr *firstSegHdr = iter->raxIter.data;
|
||||
iter->currItem = firstSegHdr->head;
|
||||
iter->itemsCurrBucket = firstSegHdr->totalItems;
|
||||
} else {
|
||||
iter->currItem = NULL;
|
||||
}
|
||||
} else if (meta->lastInSegment) {
|
||||
NextSegHdr *nextSegHdr = meta->next;
|
||||
iter->currItem = nextSegHdr->head;
|
||||
} else {
|
||||
iter->currItem = meta->next;
|
||||
}
|
||||
} else {
|
||||
iter->currItem = meta->next;
|
||||
}
|
||||
|
||||
if (iter->currItem == NULL) {
|
||||
iter->itemsCurrBucket = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Advance the iterator to the next bucket
|
||||
*
|
||||
* Returns:
|
||||
* - 0 if no more ebuckets are available, setting `iter->currItem` to NULL
|
||||
* and `iter->itemsCurrBucket` to 0.
|
||||
* - 1 otherwise, updating `iter->currItem` and `iter->itemsCurrBucket` for the
|
||||
* next ebucket.
|
||||
*/
|
||||
int ebNextBucket(EbucketsIterator *iter) {
|
||||
if (iter->currItem == NULL)
|
||||
return 0;
|
||||
|
||||
if ((iter->isRax) && (raxNext(&iter->raxIter))) {
|
||||
FirstSegHdr *currSegHdr = iter->raxIter.data;
|
||||
iter->currItem = currSegHdr->head;
|
||||
iter->itemsCurrBucket = currSegHdr->totalItems;
|
||||
} else {
|
||||
iter->currItem = NULL;
|
||||
iter->itemsCurrBucket = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Stop and cleanup the ebuckets iterator */
|
||||
void ebStop(EbucketsIterator *iter) {
|
||||
if (iter->isRax)
|
||||
raxStop(&iter->raxIter);
|
||||
}
|
||||
|
||||
/*** Unit tests ***/
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
@@ -2117,6 +2216,50 @@ int ebucketsTest(int argc, char **argv, int flags) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST("basic iterator test") {
|
||||
MyItem *items[100];
|
||||
for (uint32_t numItems = 0 ; numItems < ARRAY_SIZE(items) ; ++numItems) {
|
||||
ebuckets eb = NULL;
|
||||
EbucketsIterator iter;
|
||||
|
||||
/* Create and add items to ebuckets */
|
||||
for (uint32_t i = 0; i < numItems; i++) {
|
||||
items[i] = zmalloc(sizeof(MyItem));
|
||||
ebAdd(&eb, &myEbucketsType, items[i], i);
|
||||
}
|
||||
|
||||
/* iterate items */
|
||||
ebStart(&iter, eb, &myEbucketsType);
|
||||
for (uint32_t i = 0; i < numItems; i++) {
|
||||
assert(iter.currItem == items[i]);
|
||||
int res = ebNext(&iter);
|
||||
if (i+1<numItems) {
|
||||
assert(res == 1);
|
||||
assert(iter.currItem != NULL);
|
||||
} else {
|
||||
assert(res == 0);
|
||||
assert(iter.currItem == NULL);
|
||||
}
|
||||
}
|
||||
ebStop(&iter);
|
||||
|
||||
/* iterate buckets */
|
||||
ebStart(&iter, eb, &myEbucketsType);
|
||||
uint32_t countItems = 0;
|
||||
|
||||
uint32_t countBuckets = 0;
|
||||
while (1) {
|
||||
countItems += iter.itemsCurrBucket;
|
||||
if (!ebNextBucket(&iter)) break;
|
||||
countBuckets++;
|
||||
}
|
||||
ebStop(&iter);
|
||||
assert(countItems == numItems);
|
||||
if (numItems>=8) assert(numItems/8 >= countBuckets);
|
||||
ebDestroy(&eb, &myEbucketsType, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
TEST("list - Create a single item, get TTL, and remove") {
|
||||
MyItem *singleItem = zmalloc(sizeof(MyItem));
|
||||
ebuckets eb = NULL;
|
||||
@@ -2146,9 +2289,8 @@ int ebucketsTest(int argc, char **argv, int flags) {
|
||||
assert(ebRemove(&eb, &myEbucketsType, items[i]));
|
||||
}
|
||||
|
||||
for (int i = 0 ; i < EB_LIST_MAX_ITEMS ; i++) {
|
||||
for (int i = 0 ; i < EB_LIST_MAX_ITEMS ; i++)
|
||||
zfree(items[i]);
|
||||
}
|
||||
|
||||
ebDestroy(&eb, &myEbucketsType, NULL);
|
||||
}
|
||||
|
||||
@@ -255,6 +255,20 @@ typedef struct ExpireInfo {
|
||||
EB_EXPIRE_TIME_INVALID if none left. */
|
||||
} ExpireInfo;
|
||||
|
||||
/* Iterator to traverse ebuckets items */
|
||||
typedef struct EbucketsIterator {
|
||||
/* private data of iterator */
|
||||
ebuckets eb;
|
||||
EbucketsType *type;
|
||||
raxIterator raxIter;
|
||||
int isRax;
|
||||
|
||||
/* public read only */
|
||||
eItem currItem; /* Current item ref. Use ebGetMetaExpTime()
|
||||
on `currItem` to get expiration time.*/
|
||||
uint64_t itemsCurrBucket; /* Number of items in current bucket. */
|
||||
} EbucketsIterator;
|
||||
|
||||
/* ebuckets API */
|
||||
|
||||
static inline ebuckets ebCreate(void) { return NULL; } /* Empty ebuckets */
|
||||
@@ -281,6 +295,14 @@ int ebAdd(ebuckets *eb, EbucketsType *type, eItem item, uint64_t expireTime);
|
||||
|
||||
uint64_t ebGetExpireTime(EbucketsType *type, eItem item);
|
||||
|
||||
void ebStart(EbucketsIterator *iter, ebuckets eb, EbucketsType *type);
|
||||
|
||||
void ebStop(EbucketsIterator *iter);
|
||||
|
||||
int ebNext(EbucketsIterator *iter);
|
||||
|
||||
int ebNextBucket(EbucketsIterator *iter);
|
||||
|
||||
typedef eItem (ebDefragFunction)(const eItem item);
|
||||
eItem ebDefragItem(ebuckets *eb, EbucketsType *type, eItem item, ebDefragFunction *fn);
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/* eventnotifier.c -- An event notifier based on eventfd or pipe.
|
||||
*
|
||||
* Copyright (c) 2024-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*/
|
||||
|
||||
#include "eventnotifier.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#ifdef HAVE_EVENT_FD
|
||||
#include <sys/eventfd.h>
|
||||
#endif
|
||||
|
||||
#include "anet.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
eventNotifier* createEventNotifier(void) {
|
||||
eventNotifier *en = zmalloc(sizeof(eventNotifier));
|
||||
if (!en) return NULL;
|
||||
|
||||
#ifdef HAVE_EVENT_FD
|
||||
if ((en->efd = eventfd(0, EFD_NONBLOCK| EFD_CLOEXEC)) != -1) {
|
||||
return en;
|
||||
}
|
||||
#else
|
||||
if (anetPipe(en->pipefd, O_CLOEXEC|O_NONBLOCK, O_CLOEXEC|O_NONBLOCK) != -1) {
|
||||
return en;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Clean up if error. */
|
||||
zfree(en);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int getReadEventFd(struct eventNotifier *en) {
|
||||
#ifdef HAVE_EVENT_FD
|
||||
return en->efd;
|
||||
#else
|
||||
return en->pipefd[0];
|
||||
#endif
|
||||
}
|
||||
|
||||
int getWriteEventFd(struct eventNotifier *en) {
|
||||
#ifdef HAVE_EVENT_FD
|
||||
return en->efd;
|
||||
#else
|
||||
return en->pipefd[1];
|
||||
#endif
|
||||
}
|
||||
|
||||
int triggerEventNotifier(struct eventNotifier *en) {
|
||||
#ifdef HAVE_EVENT_FD
|
||||
uint64_t u = 1;
|
||||
if (write(en->efd, &u, sizeof(uint64_t)) == -1) {
|
||||
return EN_ERR;
|
||||
}
|
||||
#else
|
||||
char buf[1] = {'R'};
|
||||
if (write(en->pipefd[1], buf, 1) == -1) {
|
||||
return EN_ERR;
|
||||
}
|
||||
#endif
|
||||
return EN_OK;
|
||||
}
|
||||
|
||||
int handleEventNotifier(struct eventNotifier *en) {
|
||||
#ifdef HAVE_EVENT_FD
|
||||
uint64_t u;
|
||||
if (read(en->efd, &u, sizeof(uint64_t)) == -1) {
|
||||
return EN_ERR;
|
||||
}
|
||||
#else
|
||||
char buf[1];
|
||||
if (read(en->pipefd[0], buf, 1) == -1) {
|
||||
return EN_ERR;
|
||||
}
|
||||
#endif
|
||||
return EN_OK;
|
||||
}
|
||||
|
||||
void freeEventNotifier(struct eventNotifier *en) {
|
||||
#ifdef HAVE_EVENT_FD
|
||||
close(en->efd);
|
||||
#else
|
||||
close(en->pipefd[0]);
|
||||
close(en->pipefd[1]);
|
||||
#endif
|
||||
|
||||
/* Free memory */
|
||||
zfree(en);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* eventnotifier.h -- An event notifier based on eventfd or pipe.
|
||||
*
|
||||
* Copyright (c) 2024-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*/
|
||||
|
||||
#ifndef EVENTNOTIFIER_H
|
||||
#define EVENTNOTIFIER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#define EN_OK 0
|
||||
#define EN_ERR -1
|
||||
|
||||
typedef struct eventNotifier {
|
||||
#ifdef HAVE_EVENT_FD
|
||||
int efd;
|
||||
#else
|
||||
int pipefd[2];
|
||||
#endif
|
||||
} eventNotifier;
|
||||
|
||||
eventNotifier* createEventNotifier(void);
|
||||
int getReadEventFd(struct eventNotifier *en);
|
||||
int getWriteEventFd(struct eventNotifier *en);
|
||||
int triggerEventNotifier(struct eventNotifier *en);
|
||||
int handleEventNotifier(struct eventNotifier *en);
|
||||
void freeEventNotifier(struct eventNotifier *en);
|
||||
|
||||
#endif
|
||||
+10
-27
@@ -525,8 +525,7 @@ int performEvictions(void) {
|
||||
int keys_freed = 0;
|
||||
size_t mem_reported, mem_tofree;
|
||||
long long mem_freed; /* May be negative */
|
||||
mstime_t latency, eviction_latency;
|
||||
long long delta;
|
||||
mstime_t latency;
|
||||
int slaves = listLength(server.slaves);
|
||||
int result = EVICT_FAIL;
|
||||
|
||||
@@ -659,34 +658,18 @@ int performEvictions(void) {
|
||||
|
||||
/* Finally remove the selected key. */
|
||||
if (bestkey) {
|
||||
long long key_mem_freed;
|
||||
db = server.db+bestdbid;
|
||||
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
|
||||
/* We compute the amount of memory freed by db*Delete() alone.
|
||||
* It is possible that actually the memory needed to propagate
|
||||
* the DEL in AOF and replication link is greater than the one
|
||||
* we are freeing removing the key, but we can't account for
|
||||
* that otherwise we would never exit the loop.
|
||||
*
|
||||
* Same for CSC invalidation messages generated by signalModifiedKey.
|
||||
*
|
||||
* AOF and Output buffer memory will be freed eventually so
|
||||
* we only care about memory used by the key space. */
|
||||
|
||||
enterExecutionUnit(1, 0);
|
||||
delta = (long long) zmalloc_used_memory();
|
||||
latencyStartMonitor(eviction_latency);
|
||||
dbGenericDelete(db,keyobj,server.lazyfree_lazy_eviction,DB_FLAG_KEY_EVICTED);
|
||||
latencyEndMonitor(eviction_latency);
|
||||
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
|
||||
delta -= (long long) zmalloc_used_memory();
|
||||
mem_freed += delta;
|
||||
server.stat_evictedkeys++;
|
||||
signalModifiedKey(NULL,db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
|
||||
keyobj, db->id);
|
||||
propagateDeletion(db,keyobj,server.lazyfree_lazy_eviction);
|
||||
exitExecutionUnit();
|
||||
postExecutionUnitOperations();
|
||||
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
|
||||
deleteEvictedKeyAndPropagate(db, keyobj, &key_mem_freed);
|
||||
decrRefCount(keyobj);
|
||||
exitExecutionUnit();
|
||||
/* Propagate the DEL command */
|
||||
postExecutionUnitOperations();
|
||||
|
||||
mem_freed += key_mem_freed;
|
||||
keys_freed++;
|
||||
|
||||
if (keys_freed % 16 == 0) {
|
||||
|
||||
+12
-22
@@ -36,17 +36,18 @@ static double avg_ttl_factor[16] = {0.98, 0.9604, 0.941192, 0.922368, 0.903921,
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
long long t = dictGetSignedIntegerVal(de);
|
||||
if (now > t) {
|
||||
enterExecutionUnit(1, 0);
|
||||
sds key = dictGetKey(de);
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
deleteExpiredKeyAndPropagate(db,keyobj);
|
||||
decrRefCount(keyobj);
|
||||
exitExecutionUnit();
|
||||
return 1;
|
||||
} else {
|
||||
if (now < t)
|
||||
return 0;
|
||||
}
|
||||
|
||||
enterExecutionUnit(1, 0);
|
||||
sds key = dictGetKey(de);
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
deleteExpiredKeyAndPropagate(db,keyobj);
|
||||
decrRefCount(keyobj);
|
||||
exitExecutionUnit();
|
||||
/* Propagate the DEL command */
|
||||
postExecutionUnitOperations();
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Try to expire a few timed out keys. The algorithm used is adaptive and
|
||||
@@ -113,8 +114,6 @@ void expireScanCallback(void *privdata, const dictEntry *const_de) {
|
||||
long long ttl = dictGetSignedIntegerVal(de) - data->now;
|
||||
if (activeExpireCycleTryExpire(data->db, de, data->now)) {
|
||||
data->expired++;
|
||||
/* Propagate the DEL command */
|
||||
postExecutionUnitOperations();
|
||||
}
|
||||
if (ttl > 0) {
|
||||
/* We want the average TTL of keys yet not expired. */
|
||||
@@ -465,16 +464,7 @@ void expireSlaveKeys(void) {
|
||||
if ((dbids & 1) != 0) {
|
||||
redisDb *db = server.db+dbid;
|
||||
dictEntry *expire = dbFindExpires(db, keyname);
|
||||
int expired = 0;
|
||||
|
||||
if (expire &&
|
||||
activeExpireCycleTryExpire(server.db+dbid,expire,start))
|
||||
{
|
||||
expired = 1;
|
||||
/* Propagate the DEL (writable replicas do not propagate anything to other replicas,
|
||||
* but they might propagate to AOF) and trigger module hooks. */
|
||||
postExecutionUnitOperations();
|
||||
}
|
||||
int expired = expire && activeExpireCycleTryExpire(server.db+dbid,expire,start);
|
||||
|
||||
/* If the key was not expired in this DB, we need to set the
|
||||
* corresponding bit in the new bitmap we set as value.
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ void functionsLibCtxClear(functionsLibCtx *lib_ctx) {
|
||||
stats->n_lib = 0;
|
||||
}
|
||||
dictReleaseIterator(iter);
|
||||
curr_functions_lib_ctx->cache_memory = 0;
|
||||
lib_ctx->cache_memory = 0;
|
||||
}
|
||||
|
||||
void functionsLibCtxClearCurrent(int async) {
|
||||
|
||||
@@ -796,8 +796,8 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
|
||||
if (withcoords) {
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyHumanLongDouble(c, gp->longitude);
|
||||
addReplyHumanLongDouble(c, gp->latitude);
|
||||
addReplyDouble(c,gp->longitude);
|
||||
addReplyDouble(c,gp->latitude);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -959,8 +959,8 @@ void geoposCommand(client *c) {
|
||||
continue;
|
||||
}
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyHumanLongDouble(c,xy[0]);
|
||||
addReplyHumanLongDouble(c,xy[1]);
|
||||
addReplyDouble(c,xy[0]);
|
||||
addReplyDouble(c,xy[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-12
@@ -429,7 +429,7 @@ uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) {
|
||||
* of the pattern 000..1 of the element hash. As a side effect 'regp' is
|
||||
* set to the register index this element hashes to. */
|
||||
int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
|
||||
uint64_t hash, bit, index;
|
||||
uint64_t hash, index;
|
||||
int count;
|
||||
|
||||
/* Count the number of zeroes starting from bit HLL_REGISTERS
|
||||
@@ -439,21 +439,14 @@ int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
|
||||
* Note that the final "1" ending the sequence of zeroes must be
|
||||
* included in the count, so if we find "001" the count is 3, and
|
||||
* the smallest count possible is no zeroes at all, just a 1 bit
|
||||
* at the first position, that is a count of 1.
|
||||
*
|
||||
* This may sound like inefficient, but actually in the average case
|
||||
* there are high probabilities to find a 1 after a few iterations. */
|
||||
* at the first position, that is a count of 1. */
|
||||
hash = MurmurHash64A(ele,elesize,0xadc83b19ULL);
|
||||
index = hash & HLL_P_MASK; /* Register index. */
|
||||
hash >>= HLL_P; /* Remove bits used to address the register. */
|
||||
hash |= ((uint64_t)1<<HLL_Q); /* Make sure the loop terminates
|
||||
and count will be <= Q+1. */
|
||||
bit = 1;
|
||||
count = 1; /* Initialized to 1 since we count the "00000...1" pattern. */
|
||||
while((hash & bit) == 0) {
|
||||
count++;
|
||||
bit <<= 1;
|
||||
}
|
||||
|
||||
count = __builtin_ctzll(hash) + 1;
|
||||
*regp = (int) index;
|
||||
return count;
|
||||
}
|
||||
@@ -648,7 +641,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
* for future reallocates on incremental growth. But we do not allocate more than
|
||||
* 'server.hll_sparse_max_bytes' bytes for the sparse representation.
|
||||
* If the available size of hyperloglog sds string is not enough for the increment
|
||||
* we need, we promote the hypreloglog to dense representation in 'step 3'.
|
||||
* we need, we promote the hyperloglog to dense representation in 'step 3'.
|
||||
*/
|
||||
if (sdsalloc(o->ptr) < server.hll_sparse_max_bytes && sdsavail(o->ptr) < 3) {
|
||||
size_t newlen = sdslen(o->ptr) + 3;
|
||||
|
||||
+631
@@ -0,0 +1,631 @@
|
||||
/* iothread.c -- The threaded io implementation.
|
||||
*
|
||||
* Copyright (c) 2024-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/* IO threads. */
|
||||
static IOThread IOThreads[IO_THREADS_MAX_NUM];
|
||||
|
||||
/* For main thread */
|
||||
static list *mainThreadPendingClientsToIOThreads[IO_THREADS_MAX_NUM]; /* Clients to IO threads */
|
||||
static list *mainThreadProcessingClients[IO_THREADS_MAX_NUM]; /* Clients in processing */
|
||||
static list *mainThreadPendingClients[IO_THREADS_MAX_NUM]; /* Pending clients from IO threads */
|
||||
static pthread_mutex_t mainThreadPendingClientsMutexes[IO_THREADS_MAX_NUM]; /* Mutex for pending clients */
|
||||
static eventNotifier* mainThreadPendingClientsNotifiers[IO_THREADS_MAX_NUM]; /* Notifier for pending clients */
|
||||
|
||||
/* When IO threads read a complete query of clients or want to free clients, it
|
||||
* should remove it from its clients list and put the client in the list to main
|
||||
* thread, we will send these clients to main thread in IOThreadBeforeSleep. */
|
||||
void enqueuePendingClientsToMainThread(client *c, int unbind) {
|
||||
/* If the IO thread may no longer manage it, such as closing client, we should
|
||||
* unbind client from event loop, so main thread doesn't need to do it costly. */
|
||||
if (unbind) connUnbindEventLoop(c->conn);
|
||||
/* Just skip if it already is transferred. */
|
||||
if (c->io_thread_client_list_node) {
|
||||
listDelNode(IOThreads[c->tid].clients, c->io_thread_client_list_node);
|
||||
c->io_thread_client_list_node = NULL;
|
||||
/* Disable read and write to avoid race when main thread processes. */
|
||||
c->io_flags &= ~(CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED);
|
||||
listAddNodeTail(IOThreads[c->tid].pending_clients_to_main_thread, c);
|
||||
}
|
||||
}
|
||||
|
||||
/* Unbind connection of client from io thread event loop, write and read handlers
|
||||
* also be removed, ensures that we can operate the client safely. */
|
||||
void unbindClientFromIOThreadEventLoop(client *c) {
|
||||
serverAssert(c->tid != IOTHREAD_MAIN_THREAD_ID &&
|
||||
c->running_tid == IOTHREAD_MAIN_THREAD_ID);
|
||||
if (!connHasEventLoop(c->conn)) return;
|
||||
/* As calling in main thread, we should pause the io thread to make it safe. */
|
||||
pauseIOThread(c->tid);
|
||||
connUnbindEventLoop(c->conn);
|
||||
resumeIOThread(c->tid);
|
||||
}
|
||||
|
||||
/* When main thread is processing a client from IO thread, and wants to keep it,
|
||||
* we should unbind connection of client from io thread event loop first,
|
||||
* and then bind the client connection into server's event loop. */
|
||||
void keepClientInMainThread(client *c) {
|
||||
serverAssert(c->tid != IOTHREAD_MAIN_THREAD_ID &&
|
||||
c->running_tid == IOTHREAD_MAIN_THREAD_ID);
|
||||
/* IO thread no longer manage it. */
|
||||
server.io_threads_clients_num[c->tid]--;
|
||||
/* Unbind connection of client from io thread event loop. */
|
||||
unbindClientFromIOThreadEventLoop(c);
|
||||
/* Let main thread to run it, rebind event loop and read handler */
|
||||
connRebindEventLoop(c->conn, server.el);
|
||||
connSetReadHandler(c->conn, readQueryFromClient);
|
||||
c->io_flags |= CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED;
|
||||
c->running_tid = IOTHREAD_MAIN_THREAD_ID;
|
||||
c->tid = IOTHREAD_MAIN_THREAD_ID;
|
||||
/* Main thread starts to manage it. */
|
||||
server.io_threads_clients_num[c->tid]++;
|
||||
}
|
||||
|
||||
/* If the client is managed by IO thread, we should fetch it from IO thread
|
||||
* and then main thread will can process it. Just like IO Thread transfers
|
||||
* the client to the main thread for processing. */
|
||||
void fetchClientFromIOThread(client *c) {
|
||||
serverAssert(c->tid != IOTHREAD_MAIN_THREAD_ID &&
|
||||
c->running_tid != IOTHREAD_MAIN_THREAD_ID);
|
||||
pauseIOThread(c->tid);
|
||||
/* Remove the client from clients list of IO thread or main thread. */
|
||||
if (c->io_thread_client_list_node) {
|
||||
listDelNode(IOThreads[c->tid].clients, c->io_thread_client_list_node);
|
||||
c->io_thread_client_list_node = NULL;
|
||||
} else {
|
||||
list *clients[5] = {
|
||||
IOThreads[c->tid].pending_clients,
|
||||
IOThreads[c->tid].pending_clients_to_main_thread,
|
||||
mainThreadPendingClients[c->tid],
|
||||
mainThreadProcessingClients[c->tid],
|
||||
mainThreadPendingClientsToIOThreads[c->tid]
|
||||
};
|
||||
for (int i = 0; i < 5; i++) {
|
||||
listNode *ln = listSearchKey(clients[i], c);
|
||||
if (ln) {
|
||||
listDelNode(clients[i], ln);
|
||||
/* Client only can be in one client list. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Unbind connection of client from io thread event loop. */
|
||||
connUnbindEventLoop(c->conn);
|
||||
/* Now main thread can process it. */
|
||||
c->running_tid = IOTHREAD_MAIN_THREAD_ID;
|
||||
resumeIOThread(c->tid);
|
||||
}
|
||||
|
||||
/* For some clients, we must handle them in the main thread, since there is
|
||||
* data race to be processed in IO threads.
|
||||
*
|
||||
* - Close ASAP, we must free the client in main thread.
|
||||
* - Replica, pubsub, monitor, blocked, tracking clients, main thread may
|
||||
* directly write them a reply when conditions are met.
|
||||
* - Script command with debug may operate connection directly. */
|
||||
int isClientMustHandledByMainThread(client *c) {
|
||||
if (c->flags & (CLIENT_CLOSE_ASAP | CLIENT_MASTER | CLIENT_SLAVE |
|
||||
CLIENT_PUBSUB | CLIENT_MONITOR | CLIENT_BLOCKED |
|
||||
CLIENT_UNBLOCKED | CLIENT_TRACKING | CLIENT_LUA_DEBUG |
|
||||
CLIENT_LUA_DEBUG_SYNC))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* When the main thread accepts a new client or transfers clients to IO threads,
|
||||
* it assigns the client to the IO thread with the fewest clients. */
|
||||
void assignClientToIOThread(client *c) {
|
||||
serverAssert(c->tid == IOTHREAD_MAIN_THREAD_ID);
|
||||
/* Find the IO thread with the fewest clients. */
|
||||
int min_id = 0;
|
||||
int min = INT_MAX;
|
||||
for (int i = 1; i < server.io_threads_num; i++) {
|
||||
if (server.io_threads_clients_num[i] < min) {
|
||||
min = server.io_threads_clients_num[i];
|
||||
min_id = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Assign the client to the IO thread. */
|
||||
server.io_threads_clients_num[c->tid]--;
|
||||
c->tid = min_id;
|
||||
c->running_tid = min_id;
|
||||
server.io_threads_clients_num[min_id]++;
|
||||
|
||||
/* Unbind connection of client from main thread event loop, disable read and
|
||||
* write, and then put it in the list, main thread will send these clients
|
||||
* to IO thread in beforeSleep. */
|
||||
connUnbindEventLoop(c->conn);
|
||||
c->io_flags &= ~(CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED);
|
||||
listAddNodeTail(mainThreadPendingClientsToIOThreads[c->tid], c);
|
||||
}
|
||||
|
||||
/* If updating maxclients config, we not only resize the event loop of main thread
|
||||
* but also resize the event loop of all io threads, and if one thread is failed,
|
||||
* it is failed totally, since a fd can be distributed into any IO thread. */
|
||||
int resizeAllIOThreadsEventLoops(size_t newsize) {
|
||||
int result = AE_OK;
|
||||
if (server.io_threads_num <= 1) return result;
|
||||
|
||||
/* To make context safe. */
|
||||
pauseAllIOThreads();
|
||||
for (int i = 1; i < server.io_threads_num; i++) {
|
||||
IOThread *t = &IOThreads[i];
|
||||
if (aeResizeSetSize(t->el, newsize) == AE_ERR)
|
||||
result = AE_ERR;
|
||||
}
|
||||
resumeAllIOThreads();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* In the main thread, we may want to operate data of io threads, maybe uninstall
|
||||
* event handler, access query/output buffer or resize event loop, we need a clean
|
||||
* and safe context to do that. We pause io thread in IOThreadBeforeSleep, do some
|
||||
* jobs and then resume it. To avoid thread suspended, we use busy waiting to confirm
|
||||
* the target status. Besides we use atomic variable to make sure memory visibility
|
||||
* and ordering.
|
||||
*
|
||||
* Make sure that only the main thread can call these function,
|
||||
* - pauseIOThread, resumeIOThread
|
||||
* - pauseAllIOThreads, resumeAllIOThreads
|
||||
* - pauseIOThreadsRange, resumeIOThreadsRange
|
||||
*
|
||||
* The main thread will pause the io thread, and then wait for the io thread to
|
||||
* be paused. The io thread will check the paused status in IOThreadBeforeSleep,
|
||||
* and then pause itself.
|
||||
*
|
||||
* The main thread will resume the io thread, and then wait for the io thread to
|
||||
* be resumed. The io thread will check the paused status in IOThreadBeforeSleep,
|
||||
* and then resume itself.
|
||||
*/
|
||||
|
||||
/* We may pause the same io thread nestedly, so we need to record the times of
|
||||
* pausing, and only when the times of pausing is 0, we can pause the io thread,
|
||||
* and only when the times of pausing is 1, we can resume the io thread. */
|
||||
static int PausedIOThreads[IO_THREADS_MAX_NUM] = {0};
|
||||
|
||||
/* Pause the specific range of io threads, and wait for them to be paused. */
|
||||
void pauseIOThreadsRange(int start, int end) {
|
||||
if (server.io_threads_num <= 1) return;
|
||||
serverAssert(start >= 1 && end < server.io_threads_num && start <= end);
|
||||
serverAssert(pthread_equal(pthread_self(), server.main_thread_id));
|
||||
|
||||
/* Try to make all io threads paused in parallel */
|
||||
for (int i = start; i <= end; i++) {
|
||||
PausedIOThreads[i]++;
|
||||
/* Skip if already paused */
|
||||
if (PausedIOThreads[i] > 1) continue;
|
||||
|
||||
int paused;
|
||||
atomicGetWithSync(IOThreads[i].paused, paused);
|
||||
/* Don't support to call reentrant */
|
||||
serverAssert(paused == IO_THREAD_UNPAUSED);
|
||||
atomicSetWithSync(IOThreads[i].paused, IO_THREAD_PAUSING);
|
||||
/* Just notify io thread, no actual job, since io threads check paused
|
||||
* status in IOThreadBeforeSleep, so just wake it up if polling wait. */
|
||||
triggerEventNotifier(IOThreads[i].pending_clients_notifier);
|
||||
}
|
||||
|
||||
/* Wait for all io threads paused */
|
||||
for (int i = start; i <= end; i++) {
|
||||
if (PausedIOThreads[i] > 1) continue;
|
||||
int paused = IO_THREAD_PAUSING;
|
||||
while (paused != IO_THREAD_PAUSED) {
|
||||
atomicGetWithSync(IOThreads[i].paused, paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Resume the specific range of io threads, and wait for them to be resumed. */
|
||||
void resumeIOThreadsRange(int start, int end) {
|
||||
if (server.io_threads_num <= 1) return;
|
||||
serverAssert(start >= 1 && end < server.io_threads_num && start <= end);
|
||||
serverAssert(pthread_equal(pthread_self(), server.main_thread_id));
|
||||
|
||||
for (int i = start; i <= end; i++) {
|
||||
serverAssert(PausedIOThreads[i] > 0);
|
||||
PausedIOThreads[i]--;
|
||||
if (PausedIOThreads[i] > 0) continue;
|
||||
|
||||
int paused;
|
||||
/* Check if it is paused, since we must call 'pause' and
|
||||
* 'resume' in pairs */
|
||||
atomicGetWithSync(IOThreads[i].paused, paused);
|
||||
serverAssert(paused == IO_THREAD_PAUSED);
|
||||
/* Resume */
|
||||
atomicSetWithSync(IOThreads[i].paused, IO_THREAD_RESUMING);
|
||||
while (paused != IO_THREAD_UNPAUSED) {
|
||||
atomicGetWithSync(IOThreads[i].paused, paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The IO thread checks whether it is being paused, and if so, it pauses itself
|
||||
* and waits for resuming, corresponding to the pause/resumeIOThread* functions.
|
||||
* Currently, this is only called in IOThreadBeforeSleep, as there are no pending
|
||||
* I/O events at this point, with a clean context. */
|
||||
void handlePauseAndResume(IOThread *t) {
|
||||
int paused;
|
||||
/* Check if i am being paused. */
|
||||
atomicGetWithSync(t->paused, paused);
|
||||
if (paused == IO_THREAD_PAUSING) {
|
||||
atomicSetWithSync(t->paused, IO_THREAD_PAUSED);
|
||||
/* Wait for resuming */
|
||||
while (paused != IO_THREAD_RESUMING) {
|
||||
atomicGetWithSync(t->paused, paused);
|
||||
}
|
||||
atomicSetWithSync(t->paused, IO_THREAD_UNPAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pause the specific io thread, and wait for it to be paused. */
|
||||
void pauseIOThread(int id) {
|
||||
pauseIOThreadsRange(id, id);
|
||||
}
|
||||
|
||||
/* Resume the specific io thread, and wait for it to be resumed. */
|
||||
void resumeIOThread(int id) {
|
||||
resumeIOThreadsRange(id, id);
|
||||
}
|
||||
|
||||
/* Pause all io threads, and wait for them to be paused. */
|
||||
void pauseAllIOThreads(void) {
|
||||
pauseIOThreadsRange(1, server.io_threads_num-1);
|
||||
}
|
||||
|
||||
/* Resume all io threads, and wait for them to be resumed. */
|
||||
void resumeAllIOThreads(void) {
|
||||
resumeIOThreadsRange(1, server.io_threads_num-1);
|
||||
}
|
||||
|
||||
/* Add the pending clients to the list of IO threads, and trigger an event to
|
||||
* notify io threads to handle. */
|
||||
int sendPendingClientsToIOThreads(void) {
|
||||
int processed = 0;
|
||||
for (int i = 1; i < server.io_threads_num; i++) {
|
||||
int len = listLength(mainThreadPendingClientsToIOThreads[i]);
|
||||
if (len > 0) {
|
||||
IOThread *t = &IOThreads[i];
|
||||
pthread_mutex_lock(&t->pending_clients_mutex);
|
||||
listJoin(t->pending_clients, mainThreadPendingClientsToIOThreads[i]);
|
||||
pthread_mutex_unlock(&t->pending_clients_mutex);
|
||||
/* Trigger an event, maybe an error is returned when buffer is full
|
||||
* if using pipe, but no worry, io thread will handle all clients
|
||||
* in list when receiving a notification. */
|
||||
triggerEventNotifier(t->pending_clients_notifier);
|
||||
}
|
||||
processed += len;
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
extern int ProcessingEventsWhileBlocked;
|
||||
|
||||
/* The main thread processes the clients from IO threads, these clients may have
|
||||
* a complete command to execute or need to be freed. Note that IO threads never
|
||||
* free client since this operation access much server data.
|
||||
*
|
||||
* Please notice that this function may be called reentrantly, i,e, the same goes
|
||||
* for handleClientsFromIOThread and processClientsOfAllIOThreads. For example,
|
||||
* when processing script command, it may call processEventsWhileBlocked to
|
||||
* process new events, if the clients with fired events from the same io thread,
|
||||
* it may call this function reentrantly. */
|
||||
void processClientsFromIOThread(IOThread *t) {
|
||||
listNode *node = NULL;
|
||||
|
||||
while (listLength(mainThreadProcessingClients[t->id])) {
|
||||
/* Each time we pop up only the first client to process to guarantee
|
||||
* reentrancy safety. */
|
||||
if (node) zfree(node);
|
||||
node = listFirst(mainThreadProcessingClients[t->id]);
|
||||
listUnlinkNode(mainThreadProcessingClients[t->id], node);
|
||||
client *c = listNodeValue(node);
|
||||
|
||||
/* Make sure the client is readable or writable in io thread to
|
||||
* avoid data race. */
|
||||
serverAssert(!(c->io_flags & (CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED)));
|
||||
serverAssert(!(c->flags & CLIENT_CLOSE_ASAP));
|
||||
|
||||
/* Let main thread to run it, set running thread id first. */
|
||||
c->running_tid = IOTHREAD_MAIN_THREAD_ID;
|
||||
|
||||
/* If a read error occurs, handle it in the main thread first, since we
|
||||
* want to print logs about client information before freeing. */
|
||||
if (c->read_error) handleClientReadError(c);
|
||||
|
||||
/* The client is asked to close in IO thread. */
|
||||
if (c->io_flags & CLIENT_IO_CLOSE_ASAP) {
|
||||
freeClient(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Update the client in the mem usage */
|
||||
updateClientMemUsageAndBucket(c);
|
||||
|
||||
/* Process the pending command and input buffer. */
|
||||
if (!c->read_error && c->io_flags & CLIENT_IO_PENDING_COMMAND) {
|
||||
c->flags |= CLIENT_PENDING_COMMAND;
|
||||
if (processPendingCommandAndInputBuffer(c) == C_ERR) {
|
||||
/* If the client is no longer valid, it must be freed safely. */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* We may have pending replies if io thread may not finish writing
|
||||
* reply to client, so we did not put the client in pending write
|
||||
* queue. And we should do that first since we may keep the client
|
||||
* in main thread instead of returning to io threads. */
|
||||
if (!(c->flags & CLIENT_PENDING_WRITE) && clientHasPendingReplies(c))
|
||||
putClientInPendingWriteQueue(c);
|
||||
|
||||
/* The client only can be processed in the main thread, otherwise data
|
||||
* race will happen, since we may touch client's data in main thread. */
|
||||
if (isClientMustHandledByMainThread(c)) {
|
||||
keepClientInMainThread(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Remove this client from pending write clients queue of main thread,
|
||||
* And some clients may do not have reply if CLIENT REPLY OFF/SKIP. */
|
||||
if (c->flags & CLIENT_PENDING_WRITE) {
|
||||
c->flags &= ~CLIENT_PENDING_WRITE;
|
||||
listUnlinkNode(server.clients_pending_write, &c->clients_pending_write_node);
|
||||
}
|
||||
c->running_tid = c->tid;
|
||||
listLinkNodeHead(mainThreadPendingClientsToIOThreads[c->tid], node);
|
||||
node = NULL;
|
||||
}
|
||||
if (node) zfree(node);
|
||||
|
||||
/* Trigger the io thread to handle these clients ASAP to make them processed
|
||||
* in parallel.
|
||||
*
|
||||
* If AOF fsync policy is always, we should not let io thread handle these
|
||||
* clients now since we don't flush AOF buffer to file and sync yet.
|
||||
* So these clients will be delayed to send io threads in beforeSleep after
|
||||
* flushAppendOnlyFile.
|
||||
*
|
||||
* If we are in processEventsWhileBlocked, we don't send clients to io threads
|
||||
* now, we want to update server.events_processed_while_blocked accurately. */
|
||||
if (listLength(mainThreadPendingClientsToIOThreads[t->id]) &&
|
||||
server.aof_fsync != AOF_FSYNC_ALWAYS &&
|
||||
!ProcessingEventsWhileBlocked)
|
||||
{
|
||||
pthread_mutex_lock(&(t->pending_clients_mutex));
|
||||
listJoin(t->pending_clients, mainThreadPendingClientsToIOThreads[t->id]);
|
||||
pthread_mutex_unlock(&(t->pending_clients_mutex));
|
||||
triggerEventNotifier(t->pending_clients_notifier);
|
||||
}
|
||||
}
|
||||
|
||||
/* When the io thread finishes processing the client with the read event, it will
|
||||
* notify the main thread through event triggering in IOThreadBeforeSleep. The main
|
||||
* thread handles the event through this function. */
|
||||
void handleClientsFromIOThread(struct aeEventLoop *el, int fd, void *ptr, int mask) {
|
||||
UNUSED(el);
|
||||
UNUSED(mask);
|
||||
|
||||
IOThread *t = ptr;
|
||||
|
||||
/* Handle fd event first. */
|
||||
serverAssert(fd == getReadEventFd(mainThreadPendingClientsNotifiers[t->id]));
|
||||
handleEventNotifier(mainThreadPendingClientsNotifiers[t->id]);
|
||||
|
||||
/* Get the list of clients to process. */
|
||||
pthread_mutex_lock(&mainThreadPendingClientsMutexes[t->id]);
|
||||
listJoin(mainThreadProcessingClients[t->id], mainThreadPendingClients[t->id]);
|
||||
pthread_mutex_unlock(&mainThreadPendingClientsMutexes[t->id]);
|
||||
if (listLength(mainThreadProcessingClients[t->id]) == 0) return;
|
||||
|
||||
/* Process the clients from IO threads. */
|
||||
processClientsFromIOThread(t);
|
||||
}
|
||||
|
||||
/* In the new threaded io design, one thread may process multiple clients, so when
|
||||
* an io thread notifies the main thread of an event, there may be multiple clients
|
||||
* with commands that need to be processed. But in the event handler function
|
||||
* handleClientsFromIOThread may be blocked when processing the specific command,
|
||||
* the previous clients can not get a reply, and the subsequent clients can not be
|
||||
* processed, so we need to handle this scenario in beforeSleep. The function is to
|
||||
* process the commands of subsequent clients from io threads. And another function
|
||||
* sendPendingClientsToIOThreads make sure clients from io thread can get replies.
|
||||
* See also beforeSleep. */
|
||||
void processClientsOfAllIOThreads(void) {
|
||||
for (int i = 1; i < server.io_threads_num; i++) {
|
||||
processClientsFromIOThread(&IOThreads[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* After the main thread processes the clients, it will send the clients back to
|
||||
* io threads to handle, and fire an event, the io thread handles the event by
|
||||
* this function. If the client is not binded to the event loop, we should bind
|
||||
* it first and install read handler, and we don't uninstall client read handler
|
||||
* unless freeing client. If the client has pending reply, we just reply to client
|
||||
* first, and then install write handler if needed. */
|
||||
void handleClientsFromMainThread(struct aeEventLoop *ae, int fd, void *ptr, int mask) {
|
||||
UNUSED(ae);
|
||||
UNUSED(mask);
|
||||
|
||||
IOThread *t = ptr;
|
||||
|
||||
/* Handle fd event first. */
|
||||
serverAssert(fd == getReadEventFd(t->pending_clients_notifier));
|
||||
handleEventNotifier(t->pending_clients_notifier);
|
||||
|
||||
pthread_mutex_lock(&t->pending_clients_mutex);
|
||||
listJoin(t->processing_clients, t->pending_clients);
|
||||
pthread_mutex_unlock(&t->pending_clients_mutex);
|
||||
if (listLength(t->processing_clients) == 0) return;
|
||||
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(t->processing_clients, &li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = listNodeValue(ln);
|
||||
serverAssert(!(c->io_flags & (CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED)));
|
||||
/* Main thread must handle clients with CLIENT_CLOSE_ASAP flag, since
|
||||
* we only set io_flags when clients in io thread are freed ASAP. */
|
||||
serverAssert(!(c->flags & CLIENT_CLOSE_ASAP));
|
||||
|
||||
/* Link client in IO thread clients list first. */
|
||||
serverAssert(c->io_thread_client_list_node == NULL);
|
||||
listAddNodeTail(t->clients, c);
|
||||
c->io_thread_client_list_node = listLast(t->clients);
|
||||
|
||||
/* The client is asked to close, we just let main thread free it. */
|
||||
if (c->io_flags & CLIENT_IO_CLOSE_ASAP) {
|
||||
enqueuePendingClientsToMainThread(c, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Enable read and write and reset some flags. */
|
||||
c->io_flags |= CLIENT_IO_READ_ENABLED | CLIENT_IO_WRITE_ENABLED;
|
||||
c->io_flags &= ~CLIENT_IO_PENDING_COMMAND;
|
||||
|
||||
/* Only bind once, we never remove read handler unless freeing client. */
|
||||
if (!connHasEventLoop(c->conn)) {
|
||||
connRebindEventLoop(c->conn, t->el);
|
||||
serverAssert(!connHasReadHandler(c->conn));
|
||||
connSetReadHandler(c->conn, readQueryFromClient);
|
||||
}
|
||||
|
||||
/* If the client has pending replies, write replies to client. */
|
||||
if (clientHasPendingReplies(c)) {
|
||||
writeToClient(c, 0);
|
||||
if (!(c->io_flags & CLIENT_IO_CLOSE_ASAP) && clientHasPendingReplies(c)) {
|
||||
connSetWriteHandler(c->conn, sendReplyToClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
listEmpty(t->processing_clients);
|
||||
}
|
||||
|
||||
void IOThreadBeforeSleep(struct aeEventLoop *el) {
|
||||
IOThread *t = el->privdata[0];
|
||||
|
||||
/* Handle pending data(typical TLS). */
|
||||
connTypeProcessPendingData(el);
|
||||
|
||||
/* If any connection type(typical TLS) still has pending unread data don't sleep at all. */
|
||||
aeSetDontWait(el, connTypeHasPendingData(el));
|
||||
|
||||
/* Check if i am being paused, pause myself and resume. */
|
||||
handlePauseAndResume(t);
|
||||
|
||||
/* Check if there are clients to be processed in main thread, and then join
|
||||
* them to the list of main thread. */
|
||||
if (listLength(t->pending_clients_to_main_thread) > 0) {
|
||||
pthread_mutex_lock(&mainThreadPendingClientsMutexes[t->id]);
|
||||
listJoin(mainThreadPendingClients[t->id], t->pending_clients_to_main_thread);
|
||||
pthread_mutex_unlock(&mainThreadPendingClientsMutexes[t->id]);
|
||||
/* Trigger an event, maybe an error is returned when buffer is full
|
||||
* if using pipe, but no worry, main thread will handle all clients
|
||||
* in list when receiving a notification. */
|
||||
triggerEventNotifier(mainThreadPendingClientsNotifiers[t->id]);
|
||||
}
|
||||
}
|
||||
|
||||
/* The main function of IO thread, it will run an event loop. The mian thread
|
||||
* and IO thread will communicate through event notifier. */
|
||||
void *IOThreadMain(void *ptr) {
|
||||
IOThread *t = ptr;
|
||||
char thdname[16];
|
||||
snprintf(thdname, sizeof(thdname), "io_thd_%d", t->id);
|
||||
redis_set_thread_title(thdname);
|
||||
redisSetCpuAffinity(server.server_cpulist);
|
||||
makeThreadKillable();
|
||||
aeSetBeforeSleepProc(t->el, IOThreadBeforeSleep);
|
||||
aeMain(t->el);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Initialize the data structures needed for threaded I/O. */
|
||||
void initThreadedIO(void) {
|
||||
if (server.io_threads_num <= 1) return;
|
||||
|
||||
server.io_threads_active = 1;
|
||||
|
||||
if (server.io_threads_num > IO_THREADS_MAX_NUM) {
|
||||
serverLog(LL_WARNING,"Fatal: too many I/O threads configured. "
|
||||
"The maximum number is %d.", IO_THREADS_MAX_NUM);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Spawn and initialize the I/O threads. */
|
||||
for (int i = 1; i < server.io_threads_num; i++) {
|
||||
IOThread *t = &IOThreads[i];
|
||||
t->id = i;
|
||||
t->el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
|
||||
t->el->privdata[0] = t;
|
||||
t->pending_clients = listCreate();
|
||||
t->processing_clients = listCreate();
|
||||
t->pending_clients_to_main_thread = listCreate();
|
||||
t->clients = listCreate();
|
||||
atomicSetWithSync(t->paused, IO_THREAD_UNPAUSED);
|
||||
|
||||
pthread_mutexattr_t *attr = NULL;
|
||||
#if defined(__linux__) && defined(__GLIBC__)
|
||||
attr = zmalloc(sizeof(pthread_mutexattr_t));
|
||||
pthread_mutexattr_init(attr);
|
||||
pthread_mutexattr_settype(attr, PTHREAD_MUTEX_ADAPTIVE_NP);
|
||||
#endif
|
||||
pthread_mutex_init(&t->pending_clients_mutex, attr);
|
||||
|
||||
t->pending_clients_notifier = createEventNotifier();
|
||||
if (aeCreateFileEvent(t->el, getReadEventFd(t->pending_clients_notifier),
|
||||
AE_READABLE, handleClientsFromMainThread, t) != AE_OK)
|
||||
{
|
||||
serverLog(LL_WARNING, "Fatal: Can't register file event for IO thread notifications.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Create IO thread */
|
||||
if (pthread_create(&t->tid, NULL, IOThreadMain, (void*)t) != 0) {
|
||||
serverLog(LL_WARNING, "Fatal: Can't initialize IO thread.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* For main thread */
|
||||
mainThreadPendingClientsToIOThreads[i] = listCreate();
|
||||
mainThreadPendingClients[i] = listCreate();
|
||||
mainThreadProcessingClients[i] = listCreate();
|
||||
pthread_mutex_init(&mainThreadPendingClientsMutexes[i], attr);
|
||||
mainThreadPendingClientsNotifiers[i] = createEventNotifier();
|
||||
if (aeCreateFileEvent(server.el, getReadEventFd(mainThreadPendingClientsNotifiers[i]),
|
||||
AE_READABLE, handleClientsFromIOThread, t) != AE_OK)
|
||||
{
|
||||
serverLog(LL_WARNING, "Fatal: Can't register file event for main thread notifications.");
|
||||
exit(1);
|
||||
}
|
||||
if (attr) zfree(attr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Kill the IO threads, TODO: release the applied resources. */
|
||||
void killIOThreads(void) {
|
||||
if (server.io_threads_num <= 1) return;
|
||||
|
||||
int err, j;
|
||||
for (j = 1; j < server.io_threads_num; j++) {
|
||||
if (IOThreads[j].tid == pthread_self()) continue;
|
||||
if (IOThreads[j].tid && pthread_cancel(IOThreads[j].tid) == 0) {
|
||||
if ((err = pthread_join(IOThreads[j].tid,NULL)) != 0) {
|
||||
serverLog(LL_WARNING,
|
||||
"IO thread(tid:%lu) can not be joined: %s",
|
||||
(unsigned long)IOThreads[j].tid, strerror(err));
|
||||
} else {
|
||||
serverLog(LL_WARNING,
|
||||
"IO thread(tid:%lu) terminated",(unsigned long)IOThreads[j].tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+85
-15
@@ -42,6 +42,7 @@ struct _kvstore {
|
||||
unsigned long long *dict_size_index; /* Binary indexed tree (BIT) that describes cumulative key frequencies up until given dict-index. */
|
||||
size_t overhead_hashtable_lut; /* The overhead of all dictionaries. */
|
||||
size_t overhead_hashtable_rehashing; /* The overhead of dictionaries rehashing. */
|
||||
void *metadata[]; /* conditionally allocated based on "flags" */
|
||||
};
|
||||
|
||||
/* Structure for kvstore iterator that allows iterating across multiple dicts. */
|
||||
@@ -59,10 +60,17 @@ struct _kvstoreDictIterator {
|
||||
dictIterator di;
|
||||
};
|
||||
|
||||
/* Dict metadata for database, used for record the position in rehashing list. */
|
||||
/* Basic metadata allocated per dict */
|
||||
typedef struct {
|
||||
listNode *rehashing_node; /* list node in rehashing list */
|
||||
} kvstoreDictMetadata;
|
||||
} kvstoreDictMetaBase;
|
||||
|
||||
/* Conditionally metadata allocated per dict (specifically for keysizes histogram) */
|
||||
typedef struct {
|
||||
kvstoreDictMetaBase base; /* must be first in struct ! */
|
||||
/* External metadata */
|
||||
kvstoreDictMetadata meta;
|
||||
} kvstoreDictMetaEx;
|
||||
|
||||
/**********************************/
|
||||
/*** Helpers **********************/
|
||||
@@ -124,7 +132,9 @@ static void cumulativeKeyCountAdd(kvstore *kvs, int didx, long delta) {
|
||||
|
||||
dict *d = kvstoreGetDict(kvs, didx);
|
||||
size_t dsize = dictSize(d);
|
||||
int non_empty_dicts_delta = dsize == 1? 1 : dsize == 0? -1 : 0;
|
||||
/* Increment if dsize is 1 and delta is positive (first element inserted, dict becomes non-empty).
|
||||
* Decrement if dsize is 0 (dict becomes empty). */
|
||||
int non_empty_dicts_delta = (dsize == 1 && delta > 0) ? 1 : (dsize == 0) ? -1 : 0;
|
||||
kvs->non_empty_dicts += non_empty_dicts_delta;
|
||||
|
||||
/* BIT does not need to be calculated when there's only one dict. */
|
||||
@@ -182,7 +192,7 @@ static void freeDictIfNeeded(kvstore *kvs, int didx) {
|
||||
* If there's one dict, bucket count can be retrieved directly from single dict bucket. */
|
||||
static void kvstoreDictRehashingStarted(dict *d) {
|
||||
kvstore *kvs = d->type->userdata;
|
||||
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(d);
|
||||
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(d);
|
||||
listAddNodeTail(kvs->rehashing, d);
|
||||
metadata->rehashing_node = listLast(kvs->rehashing);
|
||||
|
||||
@@ -199,7 +209,7 @@ static void kvstoreDictRehashingStarted(dict *d) {
|
||||
* the old ht size of the dictionary from the total sum of buckets for a DB. */
|
||||
static void kvstoreDictRehashingCompleted(dict *d) {
|
||||
kvstore *kvs = d->type->userdata;
|
||||
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(d);
|
||||
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(d);
|
||||
if (metadata->rehashing_node) {
|
||||
listDelNode(kvs->rehashing, metadata->rehashing_node);
|
||||
metadata->rehashing_node = NULL;
|
||||
@@ -212,10 +222,15 @@ static void kvstoreDictRehashingCompleted(dict *d) {
|
||||
kvs->overhead_hashtable_rehashing -= from;
|
||||
}
|
||||
|
||||
/* Returns the size of the DB dict metadata in bytes. */
|
||||
static size_t kvstoreDictMetadataSize(dict *d) {
|
||||
/* Returns the size of the DB dict base metadata in bytes. */
|
||||
static size_t kvstoreDictMetaBaseSize(dict *d) {
|
||||
UNUSED(d);
|
||||
return sizeof(kvstoreDictMetadata);
|
||||
return sizeof(kvstoreDictMetaBase);
|
||||
}
|
||||
/* Returns the size of the DB dict extended metadata in bytes. */
|
||||
static size_t kvstoreDictMetadataExtendSize(dict *d) {
|
||||
UNUSED(d);
|
||||
return sizeof(kvstoreDictMetaEx);
|
||||
}
|
||||
|
||||
/**********************************/
|
||||
@@ -230,7 +245,13 @@ kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags) {
|
||||
* for the dict cursor, see kvstoreScan */
|
||||
assert(num_dicts_bits <= 16);
|
||||
|
||||
kvstore *kvs = zcalloc(sizeof(*kvs));
|
||||
/* Calc kvstore size */
|
||||
size_t kvsize = sizeof(kvstore);
|
||||
/* Conditionally calc also histogram size */
|
||||
if (flags & KVSTORE_ALLOC_META_KEYS_HIST)
|
||||
kvsize += sizeof(kvstoreMetadata);
|
||||
|
||||
kvstore *kvs = zcalloc(kvsize);
|
||||
memcpy(&kvs->dtype, type, sizeof(kvs->dtype));
|
||||
kvs->flags = flags;
|
||||
|
||||
@@ -241,7 +262,10 @@ kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags) {
|
||||
assert(!type->rehashingStarted);
|
||||
assert(!type->rehashingCompleted);
|
||||
kvs->dtype.userdata = kvs;
|
||||
kvs->dtype.dictMetadataBytes = kvstoreDictMetadataSize;
|
||||
if (flags & KVSTORE_ALLOC_META_KEYS_HIST)
|
||||
kvs->dtype.dictMetadataBytes = kvstoreDictMetadataExtendSize;
|
||||
else
|
||||
kvs->dtype.dictMetadataBytes = kvstoreDictMetaBaseSize;
|
||||
kvs->dtype.rehashingStarted = kvstoreDictRehashingStarted;
|
||||
kvs->dtype.rehashingCompleted = kvstoreDictRehashingCompleted;
|
||||
|
||||
@@ -261,7 +285,6 @@ kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags) {
|
||||
kvs->bucket_count = 0;
|
||||
kvs->overhead_hashtable_lut = 0;
|
||||
kvs->overhead_hashtable_rehashing = 0;
|
||||
|
||||
return kvs;
|
||||
}
|
||||
|
||||
@@ -270,9 +293,13 @@ void kvstoreEmpty(kvstore *kvs, void(callback)(dict*)) {
|
||||
dict *d = kvstoreGetDict(kvs, didx);
|
||||
if (!d)
|
||||
continue;
|
||||
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(d);
|
||||
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(d);
|
||||
if (metadata->rehashing_node)
|
||||
metadata->rehashing_node = NULL;
|
||||
if (kvs->flags & KVSTORE_ALLOC_META_KEYS_HIST) {
|
||||
kvstoreDictMetaEx *metaExt = (kvstoreDictMetaEx *) metadata;
|
||||
memset(&metaExt->meta.keysizes_hist, 0, sizeof(metaExt->meta.keysizes_hist));
|
||||
}
|
||||
dictEmpty(d, callback);
|
||||
freeDictIfNeeded(kvs, didx);
|
||||
}
|
||||
@@ -294,7 +321,7 @@ void kvstoreRelease(kvstore *kvs) {
|
||||
dict *d = kvstoreGetDict(kvs, didx);
|
||||
if (!d)
|
||||
continue;
|
||||
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(d);
|
||||
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(d);
|
||||
if (metadata->rehashing_node)
|
||||
metadata->rehashing_node = NULL;
|
||||
dictRelease(d);
|
||||
@@ -328,11 +355,15 @@ unsigned long kvstoreBuckets(kvstore *kvs) {
|
||||
|
||||
size_t kvstoreMemUsage(kvstore *kvs) {
|
||||
size_t mem = sizeof(*kvs);
|
||||
size_t metaSize = sizeof(kvstoreDictMetaBase);
|
||||
|
||||
if (kvs->flags & KVSTORE_ALLOC_META_KEYS_HIST)
|
||||
metaSize = sizeof(kvstoreDictMetaEx);
|
||||
|
||||
unsigned long long keys_count = kvstoreSize(kvs);
|
||||
mem += keys_count * dictEntryMemUsage() +
|
||||
kvstoreBuckets(kvs) * sizeof(dictEntry*) +
|
||||
kvs->allocated_dicts * (sizeof(dict) + kvstoreDictMetadataSize(NULL));
|
||||
kvs->allocated_dicts * (sizeof(dict) + metaSize);
|
||||
|
||||
/* Values are dict* shared with kvs->dicts */
|
||||
mem += listLength(kvs->rehashing) * sizeof(listNode);
|
||||
@@ -783,7 +814,7 @@ void kvstoreDictLUTDefrag(kvstore *kvs, kvstoreDictLUTDefragFunction *defragfn)
|
||||
|
||||
/* After defragmenting the dict, update its corresponding
|
||||
* rehashing node in the kvstore's rehashing list. */
|
||||
kvstoreDictMetadata *metadata = (kvstoreDictMetadata *)dictMetadata(*d);
|
||||
kvstoreDictMetaBase *metadata = (kvstoreDictMetaBase *)dictMetadata(*d);
|
||||
if (metadata->rehashing_node)
|
||||
metadata->rehashing_node->value = *d;
|
||||
}
|
||||
@@ -854,6 +885,19 @@ int kvstoreDictDelete(kvstore *kvs, int didx, const void *key) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
kvstoreDictMetadata *kvstoreGetDictMetadata(kvstore *kvs, int didx) {
|
||||
dict *d = kvstoreGetDict(kvs, didx);
|
||||
if ((!d) || (!(kvs->flags & KVSTORE_ALLOC_META_KEYS_HIST)))
|
||||
return NULL;
|
||||
|
||||
kvstoreDictMetaEx *metadata = (kvstoreDictMetaEx *)dictMetadata(d);
|
||||
return &(metadata->meta);
|
||||
}
|
||||
|
||||
kvstoreMetadata *kvstoreGetMetadata(kvstore *kvs) {
|
||||
return (kvstoreMetadata *) &kvs->metadata;
|
||||
}
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
@@ -1026,6 +1070,32 @@ int kvstoreTest(int argc, char **argv, int flags) {
|
||||
kvstoreRelease(kvs);
|
||||
}
|
||||
|
||||
TEST("Verify non-empty dict count is correctly updated") {
|
||||
kvstore *kvs = kvstoreCreate(&KvstoreDictTestType, 2,
|
||||
KVSTORE_ALLOCATE_DICTS_ON_DEMAND | KVSTORE_ALLOC_META_KEYS_HIST);
|
||||
for (int idx = 0; idx < 4; idx++) {
|
||||
for (i = 0; i < 16; i++) {
|
||||
de = kvstoreDictAddRaw(kvs, idx, stringFromInt(i), NULL);
|
||||
assert(de != NULL);
|
||||
/* When the first element is inserted, the number of non-empty dictionaries is increased by 1. */
|
||||
if (i == 0) assert(kvstoreNumNonEmptyDicts(kvs) == idx + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Step by step, clear all dictionaries and ensure non-empty dict count is updated */
|
||||
for (int idx = 0; idx < 4; idx++) {
|
||||
kvs_di = kvstoreGetDictSafeIterator(kvs, idx);
|
||||
while((de = kvstoreDictIteratorNext(kvs_di)) != NULL) {
|
||||
key = dictGetKey(de);
|
||||
assert(kvstoreDictDelete(kvs, idx, key) == DICT_OK);
|
||||
/* When the dictionary is emptied, the number of non-empty dictionaries is reduced by 1. */
|
||||
if (kvstoreDictSize(kvs, idx) == 0) assert(kvstoreNumNonEmptyDicts(kvs) == 3 - idx);
|
||||
}
|
||||
kvstoreReleaseDictIterator(kvs_di);
|
||||
}
|
||||
kvstoreRelease(kvs);
|
||||
}
|
||||
|
||||
kvstoreRelease(kvs1);
|
||||
kvstoreRelease(kvs2);
|
||||
return 0;
|
||||
|
||||
@@ -4,6 +4,21 @@
|
||||
#include "dict.h"
|
||||
#include "adlist.h"
|
||||
|
||||
/* maximum number of bins of keysizes histogram */
|
||||
#define MAX_KEYSIZES_BINS 48
|
||||
#define MAX_KEYSIZES_TYPES 5 /* static_assert at db.c verifies == OBJ_TYPE_BASIC_MAX */
|
||||
|
||||
/* When creating kvstore with flag `KVSTORE_ALLOC_META_KEYS_HIST`, then kvstore
|
||||
* alloc and memset struct kvstoreMetadata on init, yet, managed outside kvstore */
|
||||
typedef struct {
|
||||
uint64_t keysizes_hist[MAX_KEYSIZES_TYPES][MAX_KEYSIZES_BINS];
|
||||
} kvstoreMetadata;
|
||||
|
||||
/* Like kvstoreMetadata, this one per dict */
|
||||
typedef struct {
|
||||
uint64_t keysizes_hist[MAX_KEYSIZES_TYPES][MAX_KEYSIZES_BINS];
|
||||
} kvstoreDictMetadata;
|
||||
|
||||
typedef struct _kvstore kvstore;
|
||||
typedef struct _kvstoreIterator kvstoreIterator;
|
||||
typedef struct _kvstoreDictIterator kvstoreDictIterator;
|
||||
@@ -13,6 +28,7 @@ typedef int (kvstoreExpandShouldSkipDictIndex)(int didx);
|
||||
|
||||
#define KVSTORE_ALLOCATE_DICTS_ON_DEMAND (1<<0)
|
||||
#define KVSTORE_FREE_EMPTY_DICTS (1<<1)
|
||||
#define KVSTORE_ALLOC_META_KEYS_HIST (1<<2) /* Alloc keysizes histogram */
|
||||
kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags);
|
||||
void kvstoreEmpty(kvstore *kvs, void(callback)(dict*));
|
||||
void kvstoreRelease(kvstore *kvs);
|
||||
@@ -71,6 +87,8 @@ void kvstoreDictSetVal(kvstore *kvs, int didx, dictEntry *de, void *val);
|
||||
dictEntry *kvstoreDictTwoPhaseUnlinkFind(kvstore *kvs, int didx, const void *key, dictEntry ***plink, int *table_index);
|
||||
void kvstoreDictTwoPhaseUnlinkFree(kvstore *kvs, int didx, dictEntry *he, dictEntry **plink, int table_index);
|
||||
int kvstoreDictDelete(kvstore *kvs, int didx, const void *key);
|
||||
kvstoreDictMetadata *kvstoreGetDictMetadata(kvstore *kvs, int didx);
|
||||
kvstoreMetadata *kvstoreGetMetadata(kvstore *kvs);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int kvstoreTest(int argc, char *argv[], int flags);
|
||||
|
||||
+1
-1
@@ -207,7 +207,7 @@ void emptyDbAsync(redisDb *db) {
|
||||
}
|
||||
kvstore *oldkeys = db->keys, *oldexpires = db->expires;
|
||||
ebuckets oldHfe = db->hexpires;
|
||||
db->keys = kvstoreCreate(&dbDictType, slot_count_bits, flags);
|
||||
db->keys = kvstoreCreate(&dbDictType, slot_count_bits, flags | KVSTORE_ALLOC_META_KEYS_HIST);
|
||||
db->expires = kvstoreCreate(&dbExpiresDictType, slot_count_bits, flags);
|
||||
db->hexpires = ebCreate();
|
||||
atomicIncr(lazyfree_objects, kvstoreSize(oldkeys));
|
||||
|
||||
+10
-4
@@ -687,8 +687,8 @@ int lpGetIntegerValue(unsigned char *p, long long *lval) {
|
||||
* will be returned. 'user' is passed to this callback.
|
||||
* Skip 'skip' entries between every comparison.
|
||||
* Returns NULL when the field could not be found. */
|
||||
unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
|
||||
void *user, lpCmp cmp, unsigned int skip)
|
||||
static inline unsigned char *lpFindCbInternal(unsigned char *lp, unsigned char *p,
|
||||
void *user, lpCmp cmp, unsigned int skip)
|
||||
{
|
||||
int skipcnt = 0;
|
||||
unsigned char *value;
|
||||
@@ -707,7 +707,7 @@ unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
|
||||
assert(p >= lp + LP_HDR_SIZE && p + entry_size < lp + lp_bytes);
|
||||
}
|
||||
|
||||
if (cmp(lp, p, user, value, ll) == 0)
|
||||
if (unlikely(cmp(lp, p, user, value, ll) == 0))
|
||||
return p;
|
||||
|
||||
/* Reset skip count */
|
||||
@@ -734,6 +734,12 @@ unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned char *lpFindCb(unsigned char *lp, unsigned char *p,
|
||||
void *user, lpCmp cmp, unsigned int skip)
|
||||
{
|
||||
return lpFindCbInternal(lp, p, user, cmp, skip);
|
||||
}
|
||||
|
||||
struct lpFindArg {
|
||||
unsigned char *s; /* Item to search */
|
||||
uint32_t slen; /* Item len */
|
||||
@@ -787,7 +793,7 @@ unsigned char *lpFind(unsigned char *lp, unsigned char *p, unsigned char *s,
|
||||
.s = s,
|
||||
.slen = slen
|
||||
};
|
||||
return lpFindCb(lp, p, &arg, lpFindCmp, skip);
|
||||
return lpFindCbInternal(lp, p, &arg, lpFindCmp, skip);
|
||||
}
|
||||
|
||||
/* Insert, delete or replace the specified string element 'elestr' of length
|
||||
|
||||
+94
-30
@@ -2287,6 +2287,8 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
|
||||
module->options = 0;
|
||||
module->info_cb = 0;
|
||||
module->defrag_cb = 0;
|
||||
module->defrag_start_cb = 0;
|
||||
module->defrag_end_cb = 0;
|
||||
module->loadmod = NULL;
|
||||
module->num_commands_with_acl_categories = 0;
|
||||
module->onload = 1;
|
||||
@@ -4068,7 +4070,8 @@ static void moduleInitKeyTypeSpecific(RedisModuleKey *key) {
|
||||
* * REDISMODULE_OPEN_KEY_NONOTIFY - Don't trigger keyspace event on key misses.
|
||||
* * REDISMODULE_OPEN_KEY_NOSTATS - Don't update keyspace hits/misses counters.
|
||||
* * REDISMODULE_OPEN_KEY_NOEXPIRE - Avoid deleting lazy expired keys.
|
||||
* * REDISMODULE_OPEN_KEY_NOEFFECTS - Avoid any effects from fetching the key. */
|
||||
* * REDISMODULE_OPEN_KEY_NOEFFECTS - Avoid any effects from fetching the key.
|
||||
* * REDISMODULE_OPEN_KEY_ACCESS_EXPIRED - Access expired keys that have not yet been deleted */
|
||||
RedisModuleKey *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
|
||||
RedisModuleKey *kp;
|
||||
robj *value;
|
||||
@@ -4078,6 +4081,7 @@ RedisModuleKey *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
|
||||
flags |= (mode & REDISMODULE_OPEN_KEY_NOSTATS? LOOKUP_NOSTATS: 0);
|
||||
flags |= (mode & REDISMODULE_OPEN_KEY_NOEXPIRE? LOOKUP_NOEXPIRE: 0);
|
||||
flags |= (mode & REDISMODULE_OPEN_KEY_NOEFFECTS? LOOKUP_NOEFFECTS: 0);
|
||||
flags |= (mode & REDISMODULE_OPEN_KEY_ACCESS_EXPIRED ? (LOOKUP_ACCESS_EXPIRED) : 0);
|
||||
|
||||
if (mode & REDISMODULE_WRITE) {
|
||||
value = lookupKeyWriteWithFlags(ctx->client->db,keyname, flags);
|
||||
@@ -4167,15 +4171,7 @@ int RM_KeyType(RedisModuleKey *key) {
|
||||
* If the key pointer is NULL or the key is empty, zero is returned. */
|
||||
size_t RM_ValueLength(RedisModuleKey *key) {
|
||||
if (key == NULL || key->value == NULL) return 0;
|
||||
switch(key->value->type) {
|
||||
case OBJ_STRING: return stringObjectLen(key->value);
|
||||
case OBJ_LIST: return listTypeLength(key->value);
|
||||
case OBJ_SET: return setTypeSize(key->value);
|
||||
case OBJ_ZSET: return zsetLength(key->value);
|
||||
case OBJ_HASH: return hashTypeLength(key->value, 0); /* OPEN: To subtract expired fields? */
|
||||
case OBJ_STREAM: return streamLength(key->value);
|
||||
default: return 0;
|
||||
}
|
||||
return getObjectLength(key->value);
|
||||
}
|
||||
|
||||
/* If the key is open for writing, remove it, and setup the key to
|
||||
@@ -4274,7 +4270,7 @@ int RM_SetAbsExpire(RedisModuleKey *key, mstime_t expire) {
|
||||
void RM_ResetDataset(int restart_aof, int async) {
|
||||
if (restart_aof && server.aof_state != AOF_OFF) stopAppendOnly();
|
||||
flushAllDataAndResetRDB((async? EMPTYDB_ASYNC: EMPTYDB_NO_FLAGS) | EMPTYDB_NOFUNCTIONS);
|
||||
if (server.aof_enabled && restart_aof) restartAOFAfterSYNC();
|
||||
if (server.aof_enabled && restart_aof) startAppendOnlyWithRetry();
|
||||
}
|
||||
|
||||
/* Returns the number of keys in the current db. */
|
||||
@@ -5376,6 +5372,9 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) {
|
||||
va_list ap;
|
||||
if (key->value && key->value->type != OBJ_HASH) return REDISMODULE_ERR;
|
||||
|
||||
if (key->mode & REDISMODULE_OPEN_KEY_ACCESS_EXPIRED)
|
||||
hfeFlags = HFE_LAZY_ACCESS_EXPIRED; /* allow read also expired fields */
|
||||
|
||||
va_start(ap, flags);
|
||||
while(1) {
|
||||
RedisModuleString *field, **valueptr;
|
||||
@@ -8842,9 +8841,9 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
|
||||
* it will not be notified about it. */
|
||||
int prev_active = sub->active;
|
||||
sub->active = 1;
|
||||
server.lazy_expire_disabled++;
|
||||
server.allow_access_expired++;
|
||||
sub->notify_callback(&ctx, type, event, key);
|
||||
server.lazy_expire_disabled--;
|
||||
server.allow_access_expired--;
|
||||
sub->active = prev_active;
|
||||
moduleFreeContext(&ctx);
|
||||
}
|
||||
@@ -9671,6 +9670,12 @@ RedisModuleString *RM_GetModuleUserACLString(RedisModuleUser *user) {
|
||||
* The returned string must be released with RedisModule_FreeString() or by
|
||||
* enabling automatic memory management. */
|
||||
RedisModuleString *RM_GetCurrentUserName(RedisModuleCtx *ctx) {
|
||||
/* Sometimes, the user isn't passed along the call stack or isn't
|
||||
* even set, so we need to check for the members to avoid crashes. */
|
||||
if (ctx->client == NULL || ctx->client->user == NULL || ctx->client->user->name == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return RM_CreateString(ctx,ctx->client->user->name,sdslen(ctx->client->user->name));
|
||||
}
|
||||
|
||||
@@ -11085,8 +11090,9 @@ static void moduleScanKeyCallback(void *privdata, const dictEntry *de) {
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
sds val = dictGetVal(de);
|
||||
|
||||
/* If field is expired, then ignore */
|
||||
if (hfieldIsExpired(key))
|
||||
/* If field is expired and not indicated to access expired, then ignore */
|
||||
if ((!(data->key->mode & REDISMODULE_OPEN_KEY_ACCESS_EXPIRED)) &&
|
||||
(hfieldIsExpired(key)))
|
||||
return;
|
||||
|
||||
field = createStringObject(key, hfieldlen(key));
|
||||
@@ -11222,7 +11228,8 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
|
||||
p = lpNext(lp, p);
|
||||
|
||||
/* Skip expired fields */
|
||||
if (hashTypeIsExpired(o, vllExpire))
|
||||
if ((!(key->mode & REDISMODULE_OPEN_KEY_ACCESS_EXPIRED)) &&
|
||||
(hashTypeIsExpired(o, vllExpire)))
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -11881,7 +11888,7 @@ void processModuleLoadingProgressEvent(int is_aof) {
|
||||
/* When a key is deleted (in dbAsyncDelete/dbSyncDelete/setKey), it
|
||||
* will be called to tell the module which key is about to be released. */
|
||||
void moduleNotifyKeyUnlink(robj *key, robj *val, int dbid, int flags) {
|
||||
server.lazy_expire_disabled++;
|
||||
server.allow_access_expired++;
|
||||
int subevent = REDISMODULE_SUBEVENT_KEY_DELETED;
|
||||
if (flags & DB_FLAG_KEY_EXPIRED) {
|
||||
subevent = REDISMODULE_SUBEVENT_KEY_EXPIRED;
|
||||
@@ -11904,7 +11911,7 @@ void moduleNotifyKeyUnlink(robj *key, robj *val, int dbid, int flags) {
|
||||
mt->unlink(key,mv->value);
|
||||
}
|
||||
}
|
||||
server.lazy_expire_disabled--;
|
||||
server.allow_access_expired--;
|
||||
}
|
||||
|
||||
/* Return the free_effort of the module, it will automatically choose to call
|
||||
@@ -12463,10 +12470,15 @@ void modulePipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
/* Helper function for the MODULE and HELLO command: send the list of the
|
||||
* loaded modules to the client. */
|
||||
void addReplyLoadedModules(client *c) {
|
||||
const long ln = dictSize(modules);
|
||||
/* In case no module is load we avoid iterator creation */
|
||||
addReplyArrayLen(c,ln);
|
||||
if (ln == 0) {
|
||||
return;
|
||||
}
|
||||
dictIterator *di = dictGetIterator(modules);
|
||||
dictEntry *de;
|
||||
|
||||
addReplyArrayLen(c,dictSize(modules));
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
sds name = dictGetKey(de);
|
||||
struct RedisModule *module = dictGetVal(de);
|
||||
@@ -13070,7 +13082,7 @@ int RM_RdbLoad(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) {
|
||||
int ret = rdbLoad(stream->data.filename,NULL,RDBFLAGS_NONE);
|
||||
|
||||
if (server.current_client) unprotectClient(server.current_client);
|
||||
if (server.aof_state != AOF_OFF) startAppendOnly();
|
||||
if (server.aof_enabled) startAppendOnlyWithRetry();
|
||||
|
||||
if (ret != RDB_OK) {
|
||||
errno = (ret == RDB_NOT_EXIST) ? ENOENT : EIO;
|
||||
@@ -13451,6 +13463,16 @@ int RM_RegisterDefragFunc(RedisModuleCtx *ctx, RedisModuleDefragFunc cb) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Register a defrag callbacks that will be called when defrag operation starts and ends.
|
||||
*
|
||||
* The callbacks are the same as `RM_RegisterDefragFunc` but the user
|
||||
* can also assume the callbacks are called when the defrag operation starts and ends. */
|
||||
int RM_RegisterDefragCallbacks(RedisModuleCtx *ctx, RedisModuleDefragFunc start, RedisModuleDefragFunc end) {
|
||||
ctx->module->defrag_start_cb = start;
|
||||
ctx->module->defrag_end_cb = end;
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* When the data type defrag callback iterates complex structures, this
|
||||
* function should be called periodically. A zero (false) return
|
||||
* indicates the callback may continue its work. A non-zero value (true)
|
||||
@@ -13529,6 +13551,30 @@ void *RM_DefragAlloc(RedisModuleDefragCtx *ctx, void *ptr) {
|
||||
return activeDefragAlloc(ptr);
|
||||
}
|
||||
|
||||
/* Allocate memory for defrag purposes
|
||||
*
|
||||
* On the common cases user simply want to reallocate a pointer with a single
|
||||
* owner. For such usecase RM_DefragAlloc is enough. But on some usecases the user
|
||||
* might want to replace a pointer with multiple owners in different keys.
|
||||
* In such case, an in place replacement can not work because the other key still
|
||||
* keep a pointer to the old value.
|
||||
*
|
||||
* RM_DefragAllocRaw and RM_DefragFreeRaw allows to control when the memory
|
||||
* for defrag purposes will be allocated and when it will be freed,
|
||||
* allow to support more complex defrag usecases. */
|
||||
void *RM_DefragAllocRaw(RedisModuleDefragCtx *ctx, size_t size) {
|
||||
UNUSED(ctx);
|
||||
return activeDefragAllocRaw(size);
|
||||
}
|
||||
|
||||
/* Free memory for defrag purposes
|
||||
*
|
||||
* See RM_DefragAllocRaw for more information. */
|
||||
void RM_DefragFreeRaw(RedisModuleDefragCtx *ctx, void *ptr) {
|
||||
UNUSED(ctx);
|
||||
activeDefragFreeRaw(ptr);
|
||||
}
|
||||
|
||||
/* Defrag a RedisModuleString previously allocated by RM_Alloc, RM_Calloc, etc.
|
||||
* See RM_DefragAlloc() for more information on how the defragmentation process
|
||||
* works.
|
||||
@@ -13610,17 +13656,32 @@ int moduleDefragValue(robj *key, robj *value, int dbid) {
|
||||
|
||||
/* Call registered module API defrag functions */
|
||||
void moduleDefragGlobals(void) {
|
||||
dictIterator *di = dictGetIterator(modules);
|
||||
dictEntry *de;
|
||||
dictForEach(modules, struct RedisModule, module,
|
||||
if (module->defrag_cb) {
|
||||
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
|
||||
module->defrag_cb(&defrag_ctx);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct RedisModule *module = dictGetVal(de);
|
||||
if (!module->defrag_cb)
|
||||
continue;
|
||||
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
|
||||
module->defrag_cb(&defrag_ctx);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
/* Call registered module API defrag start functions */
|
||||
void moduleDefragStart(void) {
|
||||
dictForEach(modules, struct RedisModule, module,
|
||||
if (module->defrag_start_cb) {
|
||||
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
|
||||
module->defrag_start_cb(&defrag_ctx);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* Call registered module API defrag end functions */
|
||||
void moduleDefragEnd(void) {
|
||||
dictForEach(modules, struct RedisModule, module,
|
||||
if (module->defrag_end_cb) {
|
||||
RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1};
|
||||
module->defrag_end_cb(&defrag_ctx);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* Returns the name of the key currently being processed.
|
||||
@@ -13980,7 +14041,10 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(GetCurrentCommandName);
|
||||
REGISTER_API(GetTypeMethodVersion);
|
||||
REGISTER_API(RegisterDefragFunc);
|
||||
REGISTER_API(RegisterDefragCallbacks);
|
||||
REGISTER_API(DefragAlloc);
|
||||
REGISTER_API(DefragAllocRaw);
|
||||
REGISTER_API(DefragFreeRaw);
|
||||
REGISTER_API(DefragRedisModuleString);
|
||||
REGISTER_API(DefragShouldStop);
|
||||
REGISTER_API(DefragCursorSet);
|
||||
|
||||
+7
-1
@@ -355,7 +355,12 @@ int isWatchedKeyExpired(client *c) {
|
||||
}
|
||||
|
||||
/* "Touch" a key, so that if this key is being WATCHed by some client the
|
||||
* next EXEC will fail. */
|
||||
* next EXEC will fail.
|
||||
*
|
||||
* Sanitizer suppression: IO threads also read c->flags, but never modify
|
||||
* it or read the CLIENT_DIRTY_CAS bit, main thread just only modifies
|
||||
* this bit, so there is actually no real data race. */
|
||||
REDIS_NO_SANITIZE("thread")
|
||||
void touchWatchedKey(redisDb *db, robj *key) {
|
||||
list *clients;
|
||||
listIter li;
|
||||
@@ -404,6 +409,7 @@ void touchWatchedKey(redisDb *db, robj *key) {
|
||||
* replaced_with: for SWAPDB, the WATCH should be invalidated if
|
||||
* the key exists in either of them, and skipped only if it
|
||||
* doesn't exist in both. */
|
||||
REDIS_NO_SANITIZE("thread")
|
||||
void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
+352
-507
File diff suppressed because it is too large
Load Diff
@@ -680,6 +680,18 @@ robj *tryObjectEncoding(robj *o) {
|
||||
return tryObjectEncodingEx(o, 1);
|
||||
}
|
||||
|
||||
size_t getObjectLength(robj *o) {
|
||||
switch(o->type) {
|
||||
case OBJ_STRING: return stringObjectLen(o);
|
||||
case OBJ_LIST: return listTypeLength(o);
|
||||
case OBJ_SET: return setTypeSize(o);
|
||||
case OBJ_ZSET: return zsetLength(o);
|
||||
case OBJ_HASH: return hashTypeLength(o, 0);
|
||||
case OBJ_STREAM: return streamLength(o);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
|
||||
+9
-2
@@ -1244,10 +1244,17 @@ int quicklistDelRange(quicklist *quicklist, const long start,
|
||||
|
||||
/* compare between a two entries */
|
||||
int quicklistCompare(quicklistEntry* entry, unsigned char *p2, const size_t p2_len) {
|
||||
if (unlikely(QL_NODE_IS_PLAIN(entry->node))) {
|
||||
if (entry->value) {
|
||||
return ((entry->sz == p2_len) && (memcmp(entry->value, p2, p2_len) == 0));
|
||||
} else {
|
||||
/* We use string2ll() to get an integer representation of the
|
||||
* string 'p2' and compare it to 'entry->longval', it's much
|
||||
* faster than convert integer to string and comparing. */
|
||||
long long sval;
|
||||
if (string2ll((const char*)p2, p2_len, &sval))
|
||||
return entry->longval == sval;
|
||||
}
|
||||
return lpCompare(entry->zi, p2, p2_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns a quicklist iterator 'iter'. After the initialization every
|
||||
|
||||
@@ -2332,6 +2332,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
|
||||
rdbReportCorruptRDB("invalid expireAt time: %llu",
|
||||
(unsigned long long) expireAt);
|
||||
decrRefCount(o);
|
||||
if (dupSearchDict != NULL) dictRelease(dupSearchDict);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -3161,7 +3162,13 @@ emptykey:
|
||||
/* Mark that we are loading in the global state and setup the fields
|
||||
* needed to provide loading stats. */
|
||||
void startLoading(size_t size, int rdbflags, int async) {
|
||||
/* Load the DB */
|
||||
loadingSetFlags(NULL, size, async);
|
||||
loadingFireEvent(rdbflags);
|
||||
}
|
||||
|
||||
/* Initialize stats, set loading flags and filename if provided. */
|
||||
void loadingSetFlags(char *filename, size_t size, int async) {
|
||||
rdbFileBeingLoaded = filename;
|
||||
server.loading = 1;
|
||||
if (async == 1) server.async_loading = 1;
|
||||
server.loading_start_time = time(NULL);
|
||||
@@ -3171,7 +3178,9 @@ void startLoading(size_t size, int rdbflags, int async) {
|
||||
server.rdb_last_load_keys_expired = 0;
|
||||
server.rdb_last_load_keys_loaded = 0;
|
||||
blockingOperationStarts();
|
||||
}
|
||||
|
||||
void loadingFireEvent(int rdbflags) {
|
||||
/* Fire the loading modules start event. */
|
||||
int subevent;
|
||||
if (rdbflags & RDBFLAGS_AOF_PREAMBLE)
|
||||
@@ -3187,8 +3196,8 @@ void startLoading(size_t size, int rdbflags, int async) {
|
||||
* needed to provide loading stats.
|
||||
* 'filename' is optional and used for rdb-check on error */
|
||||
void startLoadingFile(size_t size, char* filename, int rdbflags) {
|
||||
rdbFileBeingLoaded = filename;
|
||||
startLoading(size, rdbflags, 0);
|
||||
loadingSetFlags(filename, size, 0);
|
||||
loadingFireEvent(rdbflags);
|
||||
}
|
||||
|
||||
/* Refresh the absolute loading progress info */
|
||||
@@ -3702,14 +3711,21 @@ eoferr:
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
|
||||
return rdbLoadWithEmptyFunc(filename, rsi, rdbflags, NULL);
|
||||
}
|
||||
|
||||
/* Like rdbLoadRio() but takes a filename instead of a rio stream. The
|
||||
* filename is open for reading and a rio stream object created in order
|
||||
* to do the actual loading. Moreover the ETA displayed in the INFO
|
||||
* output is initialized and finalized.
|
||||
*
|
||||
* If you pass an 'rsi' structure initialized with RDB_SAVE_INFO_INIT, the
|
||||
* loading code will fill the information fields in the structure. */
|
||||
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
|
||||
* loading code will fill the information fields in the structure.
|
||||
*
|
||||
* If emptyDbFunc is not NULL, it will be called to flush old db or to
|
||||
* discard partial db on error. */
|
||||
int rdbLoadWithEmptyFunc(char *filename, rdbSaveInfo *rsi, int rdbflags, void (*emptyDbFunc)(void)) {
|
||||
FILE *fp;
|
||||
rio rdb;
|
||||
int retval;
|
||||
@@ -3727,12 +3743,20 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
|
||||
if (fstat(fileno(fp), &sb) == -1)
|
||||
sb.st_size = 0;
|
||||
|
||||
startLoadingFile(sb.st_size, filename, rdbflags);
|
||||
loadingSetFlags(filename, sb.st_size, 0);
|
||||
/* Note that inside loadingSetFlags(), server.loading is set.
|
||||
* emptyDbCallback() may yield back to event-loop to reply -LOADING. */
|
||||
if (emptyDbFunc)
|
||||
emptyDbFunc(); /* Flush existing db. */
|
||||
loadingFireEvent(rdbflags);
|
||||
rioInitWithFile(&rdb,fp);
|
||||
|
||||
retval = rdbLoadRio(&rdb,rdbflags,rsi);
|
||||
|
||||
fclose(fp);
|
||||
if (retval != C_OK && emptyDbFunc)
|
||||
emptyDbFunc(); /* Clean up partial db. */
|
||||
|
||||
stopLoading(retval==C_OK);
|
||||
/* Reclaim the cache backed by rdb */
|
||||
if (retval == C_OK && !(rdbflags & RDBFLAGS_KEEP_CACHE)) {
|
||||
|
||||
@@ -136,6 +136,7 @@ uint64_t rdbLoadLen(rio *rdb, int *isencoded);
|
||||
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
||||
int rdbSaveObjectType(rio *rdb, robj *o);
|
||||
int rdbLoadObjectType(rio *rdb);
|
||||
int rdbLoadWithEmptyFunc(char *filename, rdbSaveInfo *rsi, int rdbflags, void (*emptyDbFunc)(void));
|
||||
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags);
|
||||
int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi, int rdbflags);
|
||||
int rdbSaveToSlavesSockets(int req, rdbSaveInfo *rsi);
|
||||
|
||||
@@ -3264,6 +3264,9 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) {
|
||||
config.cluster_reissue_command = 0;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
/* Reset dbnum after reconnecting so we can re-select the previous db in cliSelect(). */
|
||||
config.dbnum = 0;
|
||||
cliSelect();
|
||||
}
|
||||
config.cluster_reissue_command = 0;
|
||||
if (config.cluster_send_asking) {
|
||||
@@ -3691,6 +3694,8 @@ static int evalMode(int argc, char **argv) {
|
||||
/* Call it */
|
||||
int eval_ldb = config.eval_ldb; /* Save it, may be reverted. */
|
||||
retval = issueCommand(argc+3-got_comma, argv2);
|
||||
for (j = 0; j < argc+3-got_comma; j++) sdsfree(argv2[j]);
|
||||
zfree(argv2);
|
||||
if (eval_ldb) {
|
||||
if (!config.eval_ldb) {
|
||||
/* If the debugging session ended immediately, there was an
|
||||
@@ -6076,6 +6081,7 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
|
||||
if (!clusterManagerCheckRedisReply(n, reply, NULL)) {
|
||||
fixed = -1;
|
||||
if (reply) freeReplyObject(reply);
|
||||
if (slot_nodes) listRelease(slot_nodes);
|
||||
goto cleanup;
|
||||
}
|
||||
assert(reply->type == REDIS_REPLY_ARRAY);
|
||||
|
||||
+9
-1
@@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
@@ -46,8 +47,15 @@ void _serverAssert(const char *estr, const char *file, int line) {
|
||||
}
|
||||
|
||||
void _serverPanic(const char *file, int line, const char *msg, ...) {
|
||||
va_list ap;
|
||||
char fmtmsg[256];
|
||||
|
||||
va_start(ap,msg);
|
||||
vsnprintf(fmtmsg,sizeof(fmtmsg),msg,ap);
|
||||
va_end(ap);
|
||||
|
||||
fprintf(stderr, "------------------------------------------------");
|
||||
fprintf(stderr, "!!! Software Failure. Press left mouse button to continue");
|
||||
fprintf(stderr, "Guru Meditation: %s #%s:%d",msg,file,line);
|
||||
fprintf(stderr, "Guru Meditation: %s #%s:%d",fmtmsg,file,line);
|
||||
abort();
|
||||
}
|
||||
|
||||
+10
-1
@@ -60,10 +60,13 @@ typedef long long ustime_t;
|
||||
#define REDISMODULE_OPEN_KEY_NOEXPIRE (1<<19)
|
||||
/* Avoid any effects from fetching the key */
|
||||
#define REDISMODULE_OPEN_KEY_NOEFFECTS (1<<20)
|
||||
/* Allow access expired key that haven't deleted yet */
|
||||
#define REDISMODULE_OPEN_KEY_ACCESS_EXPIRED (1<<21)
|
||||
|
||||
/* Mask of all REDISMODULE_OPEN_KEY_* values. Any new mode should be added to this list.
|
||||
* Should not be used directly by the module, use RM_GetOpenKeyModesAll instead.
|
||||
* Located here so when we will add new modes we will not forget to update it. */
|
||||
#define _REDISMODULE_OPEN_KEY_ALL REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH | REDISMODULE_OPEN_KEY_NONOTIFY | REDISMODULE_OPEN_KEY_NOSTATS | REDISMODULE_OPEN_KEY_NOEXPIRE | REDISMODULE_OPEN_KEY_NOEFFECTS
|
||||
#define _REDISMODULE_OPEN_KEY_ALL REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH | REDISMODULE_OPEN_KEY_NONOTIFY | REDISMODULE_OPEN_KEY_NOSTATS | REDISMODULE_OPEN_KEY_NOEXPIRE | REDISMODULE_OPEN_KEY_NOEFFECTS | REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
|
||||
|
||||
/* List push and pop */
|
||||
#define REDISMODULE_LIST_HEAD 0
|
||||
@@ -1296,7 +1299,10 @@ REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisMod
|
||||
REDISMODULE_API int *(*RedisModule_GetCommandKeysWithFlags)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys, int **out_flags) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char *(*RedisModule_GetCurrentCommandName)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_RegisterDefragFunc)(RedisModuleCtx *ctx, RedisModuleDefragFunc func) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_RegisterDefragCallbacks)(RedisModuleCtx *ctx, RedisModuleDefragFunc start, RedisModuleDefragFunc end) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void *(*RedisModule_DefragAllocRaw)(RedisModuleDefragCtx *ctx, size_t size) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_DefragFreeRaw)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString *(*RedisModule_DefragRedisModuleString)(RedisModuleDefragCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DefragShouldStop)(RedisModuleDefragCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DefragCursorSet)(RedisModuleDefragCtx *ctx, unsigned long cursor) REDISMODULE_ATTR;
|
||||
@@ -1662,7 +1668,10 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(GetCommandKeysWithFlags);
|
||||
REDISMODULE_GET_API(GetCurrentCommandName);
|
||||
REDISMODULE_GET_API(RegisterDefragFunc);
|
||||
REDISMODULE_GET_API(RegisterDefragCallbacks);
|
||||
REDISMODULE_GET_API(DefragAlloc);
|
||||
REDISMODULE_GET_API(DefragAllocRaw);
|
||||
REDISMODULE_GET_API(DefragFreeRaw);
|
||||
REDISMODULE_GET_API(DefragRedisModuleString);
|
||||
REDISMODULE_GET_API(DefragShouldStop);
|
||||
REDISMODULE_GET_API(DefragCursorSet);
|
||||
|
||||
+46
-37
@@ -3,13 +3,15 @@
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2024-present, Valkey contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*
|
||||
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
*/
|
||||
|
||||
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include "bio.h"
|
||||
@@ -1736,12 +1738,24 @@ void replicationSendNewlineToMaster(void) {
|
||||
}
|
||||
|
||||
/* Callback used by emptyData() while flushing away old data to load
|
||||
* the new dataset received by the master and by discardTempDb()
|
||||
* after loading succeeded or failed. */
|
||||
* the new dataset received by the master or to clear partial db if loading
|
||||
* fails. */
|
||||
void replicationEmptyDbCallback(dict *d) {
|
||||
UNUSED(d);
|
||||
if (server.repl_state == REPL_STATE_TRANSFER)
|
||||
replicationSendNewlineToMaster();
|
||||
|
||||
processEventsWhileBlocked();
|
||||
}
|
||||
|
||||
/* Function to flush old db or the partial db on error. */
|
||||
static void rdbLoadEmptyDbFunc(void) {
|
||||
serverAssert(server.loading);
|
||||
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
|
||||
int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
|
||||
EMPTYDB_NO_FLAGS;
|
||||
emptyData(-1, empty_db_flags, replicationEmptyDbCallback);
|
||||
}
|
||||
|
||||
/* Once we have a link with the master and the synchronization was
|
||||
@@ -1765,6 +1779,9 @@ void replicationCreateMasterClient(connection *conn, int dbid) {
|
||||
* connection. */
|
||||
server.master->flags |= CLIENT_MASTER;
|
||||
|
||||
/* Allocate a private query buffer for the master client instead of using the reusable query buffer.
|
||||
* This is done because the master's query buffer data needs to be preserved for my sub-replicas to use. */
|
||||
server.master->querybuf = sdsempty();
|
||||
server.master->authenticated = 1;
|
||||
server.master->reploff = server.master_initial_offset;
|
||||
server.master->read_reploff = server.master->reploff;
|
||||
@@ -1778,27 +1795,6 @@ void replicationCreateMasterClient(connection *conn, int dbid) {
|
||||
if (dbid != -1) selectDb(server.master,dbid);
|
||||
}
|
||||
|
||||
/* This function will try to re-enable the AOF file after the
|
||||
* master-replica synchronization: if it fails after multiple attempts
|
||||
* the replica cannot be considered reliable and exists with an
|
||||
* error. */
|
||||
void restartAOFAfterSYNC(void) {
|
||||
unsigned int tries, max_tries = 10;
|
||||
for (tries = 0; tries < max_tries; ++tries) {
|
||||
if (startAppendOnly() == C_OK) break;
|
||||
serverLog(LL_WARNING,
|
||||
"Failed enabling the AOF after successful master synchronization! "
|
||||
"Trying it again in one second.");
|
||||
sleep(1);
|
||||
}
|
||||
if (tries == max_tries) {
|
||||
serverLog(LL_WARNING,
|
||||
"FATAL: this replica instance finished the synchronization with "
|
||||
"its master, but the AOF can't be turned on. Exiting now.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static int useDisklessLoad(void) {
|
||||
/* compute boolean decision to use diskless load */
|
||||
int enabled = server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
|
||||
@@ -1831,7 +1827,7 @@ redisDb *disklessLoadInitTempDb(void) {
|
||||
/* Helper function for readSyncBulkPayload() to discard our tempDb
|
||||
* when the loading succeeded or failed. */
|
||||
void disklessLoadDiscardTempDb(redisDb *tempDb) {
|
||||
discardTempDb(tempDb, replicationEmptyDbCallback);
|
||||
discardTempDb(tempDb);
|
||||
}
|
||||
|
||||
/* If we know we got an entirely different data set from our master
|
||||
@@ -2057,9 +2053,6 @@ void readSyncBulkPayload(connection *conn) {
|
||||
NULL);
|
||||
} else {
|
||||
replicationAttachToNewMaster();
|
||||
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
|
||||
emptyData(-1,empty_db_flags,replicationEmptyDbCallback);
|
||||
}
|
||||
|
||||
/* Before loading the DB into memory we need to delete the readable
|
||||
@@ -2093,13 +2086,22 @@ void readSyncBulkPayload(connection *conn) {
|
||||
functionsLibCtxClear(functions_lib_ctx);
|
||||
}
|
||||
|
||||
loadingSetFlags(NULL, server.repl_transfer_size, asyncLoading);
|
||||
if (server.repl_diskless_load != REPL_DISKLESS_LOAD_SWAPDB) {
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
|
||||
/* Note that inside loadingSetFlags(), server.loading is set.
|
||||
* replicationEmptyDbCallback() may yield back to event-loop to
|
||||
* reply -LOADING. */
|
||||
emptyData(-1, empty_db_flags, replicationEmptyDbCallback);
|
||||
}
|
||||
loadingFireEvent(RDBFLAGS_REPLICATION);
|
||||
|
||||
rioInitWithConn(&rdb,conn,server.repl_transfer_size);
|
||||
|
||||
/* Put the socket in blocking mode to simplify RDB transfer.
|
||||
* We'll restore it when the RDB is received. */
|
||||
connBlock(conn);
|
||||
connRecvTimeout(conn, server.repl_timeout*1000);
|
||||
startLoading(server.repl_transfer_size, RDBFLAGS_REPLICATION, asyncLoading);
|
||||
|
||||
int loadingFailed = 0;
|
||||
rdbLoadingCtx loadingCtx = { .dbarray = dbarray, .functions_lib_ctx = functions_lib_ctx };
|
||||
@@ -2120,7 +2122,6 @@ void readSyncBulkPayload(connection *conn) {
|
||||
}
|
||||
|
||||
if (loadingFailed) {
|
||||
stopLoading(0);
|
||||
cancelReplicationHandshake(1);
|
||||
rioFreeConn(&rdb, NULL);
|
||||
|
||||
@@ -2138,6 +2139,11 @@ void readSyncBulkPayload(connection *conn) {
|
||||
emptyData(-1,empty_db_flags,replicationEmptyDbCallback);
|
||||
}
|
||||
|
||||
/* Note that replicationEmptyDbCallback() may yield back to event
|
||||
* loop to reply -LOADING if flushing the db takes a long time. So,
|
||||
* stopLoading() must be called after emptyData() above. */
|
||||
stopLoading(0);
|
||||
|
||||
/* Note that there's no point in restarting the AOF on SYNC
|
||||
* failure, it'll be restarted when sync succeeds or the replica
|
||||
* gets promoted. */
|
||||
@@ -2213,7 +2219,7 @@ void readSyncBulkPayload(connection *conn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != RDB_OK) {
|
||||
if (rdbLoadWithEmptyFunc(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION,rdbLoadEmptyDbFunc) != RDB_OK) {
|
||||
serverLog(LL_WARNING,
|
||||
"Failed trying to load the MASTER synchronization "
|
||||
"DB from disk, check server logs.");
|
||||
@@ -2225,9 +2231,6 @@ void readSyncBulkPayload(connection *conn) {
|
||||
bg_unlink(server.rdb_filename);
|
||||
}
|
||||
|
||||
/* If disk-based RDB loading fails, remove the half-loaded dataset. */
|
||||
emptyData(-1, empty_db_flags, replicationEmptyDbCallback);
|
||||
|
||||
/* Note that there's no point in restarting the AOF on sync failure,
|
||||
it'll be restarted when sync succeeds or replica promoted. */
|
||||
return;
|
||||
@@ -2281,7 +2284,10 @@ void readSyncBulkPayload(connection *conn) {
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
* to the new file. */
|
||||
if (server.aof_enabled) restartAOFAfterSYNC();
|
||||
if (server.aof_enabled) {
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Starting AOF after a successful sync");
|
||||
startAppendOnlyWithRetry();
|
||||
}
|
||||
return;
|
||||
|
||||
error:
|
||||
@@ -2919,7 +2925,7 @@ write_error: /* Handle sendCommand() errors. */
|
||||
}
|
||||
|
||||
int connectWithMaster(void) {
|
||||
server.repl_transfer_s = connCreate(connTypeOfReplication());
|
||||
server.repl_transfer_s = connCreate(server.el, connTypeOfReplication());
|
||||
if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
|
||||
server.bind_source_addr, syncWithMaster) == C_ERR) {
|
||||
serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
|
||||
@@ -3098,7 +3104,10 @@ void replicationUnsetMaster(void) {
|
||||
|
||||
/* Restart the AOF subsystem in case we shut it down during a sync when
|
||||
* we were still a slave. */
|
||||
if (server.aof_enabled && server.aof_state == AOF_OFF) restartAOFAfterSYNC();
|
||||
if (server.aof_enabled && server.aof_state == AOF_OFF) {
|
||||
serverLog(LL_NOTICE, "Restarting AOF after becoming master");
|
||||
startAppendOnlyWithRetry();
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called when the slave lose the connection with the
|
||||
|
||||
+2
-1
@@ -34,6 +34,7 @@
|
||||
* ----------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "fast_float_strtod.h"
|
||||
#include "resp_parser.h"
|
||||
#include "server.h"
|
||||
|
||||
@@ -132,7 +133,7 @@ static int parseDouble(ReplyParser *parser, void *p_ctx) {
|
||||
if (len <= MAX_LONG_DOUBLE_CHARS) {
|
||||
memcpy(buf,proto+1,len);
|
||||
buf[len] = '\0';
|
||||
d = strtod(buf,NULL); /* We expect a valid representation. */
|
||||
d = fast_float_strtod(buf,NULL); /* We expect a valid representation. */
|
||||
} else {
|
||||
d = 0;
|
||||
}
|
||||
|
||||
+150
-27
@@ -2,8 +2,13 @@
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2024-present, Valkey contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*
|
||||
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -734,6 +739,8 @@ long long getInstantaneousMetric(int metric) {
|
||||
*
|
||||
* The function always returns 0 as it never terminates the client. */
|
||||
int clientsCronResizeQueryBuffer(client *c) {
|
||||
/* If the client query buffer is NULL, it is using the reusable query buffer and there is nothing to do. */
|
||||
if (c->querybuf == NULL) return 0;
|
||||
size_t querybuf_size = sdsalloc(c->querybuf);
|
||||
time_t idletime = server.unixtime - c->lastinteraction;
|
||||
|
||||
@@ -743,7 +750,18 @@ int clientsCronResizeQueryBuffer(client *c) {
|
||||
/* There are two conditions to resize the query buffer: */
|
||||
if (idletime > 2) {
|
||||
/* 1) Query is idle for a long time. */
|
||||
c->querybuf = sdsRemoveFreeSpace(c->querybuf, 1);
|
||||
size_t remaining = sdslen(c->querybuf) - c->qb_pos;
|
||||
if (!(c->flags & CLIENT_MASTER) && !remaining) {
|
||||
/* If the client is not a master and no data is pending,
|
||||
* The client can safely use the reusable query buffer in the next read - free the client's querybuf. */
|
||||
sdsfree(c->querybuf);
|
||||
/* By setting the querybuf to NULL, the client will use the reusable query buffer in the next read.
|
||||
* We don't move the client to the reusable query buffer immediately, because if we allocated a private
|
||||
* query buffer for the client, it's likely that the client will use it again soon. */
|
||||
c->querybuf = NULL;
|
||||
} else {
|
||||
c->querybuf = sdsRemoveFreeSpace(c->querybuf, 1);
|
||||
}
|
||||
} else if (querybuf_size > PROTO_RESIZE_THRESHOLD && querybuf_size/2 > c->querybuf_peak) {
|
||||
/* 2) Query buffer is too big for latest peak and is larger than
|
||||
* resize threshold. Trim excess space but only up to a limit,
|
||||
@@ -759,7 +777,7 @@ int clientsCronResizeQueryBuffer(client *c) {
|
||||
|
||||
/* Reset the peak again to capture the peak memory usage in the next
|
||||
* cycle. */
|
||||
c->querybuf_peak = sdslen(c->querybuf);
|
||||
c->querybuf_peak = c->querybuf ? sdslen(c->querybuf) : 0;
|
||||
/* We reset to either the current used, or currently processed bulk size,
|
||||
* which ever is bigger. */
|
||||
if (c->bulklen != -1 && (size_t)c->bulklen + 2 > c->querybuf_peak) c->querybuf_peak = c->bulklen + 2;
|
||||
@@ -834,8 +852,9 @@ size_t ClientsPeakMemInput[CLIENTS_PEAK_MEM_USAGE_SLOTS] = {0};
|
||||
size_t ClientsPeakMemOutput[CLIENTS_PEAK_MEM_USAGE_SLOTS] = {0};
|
||||
|
||||
int clientsCronTrackExpansiveClients(client *c, int time_idx) {
|
||||
size_t in_usage = sdsZmallocSize(c->querybuf) + c->argv_len_sum +
|
||||
(c->argv ? zmalloc_size(c->argv) : 0);
|
||||
size_t qb_size = c->querybuf ? sdsZmallocSize(c->querybuf) : 0;
|
||||
size_t argv_size = c->argv ? zmalloc_size(c->argv) : 0;
|
||||
size_t in_usage = qb_size + c->argv_len_sum + argv_size;
|
||||
size_t out_usage = getClientOutputBufferMemoryUsage(c);
|
||||
|
||||
/* Track the biggest values observed so far in this slot. */
|
||||
@@ -930,7 +949,7 @@ void removeClientFromMemUsageBucket(client *c, int allow_eviction) {
|
||||
* returns 1 if client eviction for this client is allowed, 0 otherwise.
|
||||
*/
|
||||
int updateClientMemUsageAndBucket(client *c) {
|
||||
serverAssert(io_threads_op == IO_THREADS_OP_IDLE && c->conn);
|
||||
serverAssert(pthread_equal(pthread_self(), server.main_thread_id) && c->conn);
|
||||
int allow_eviction = clientEvictionAllowed(c);
|
||||
removeClientFromMemUsageBucket(c, allow_eviction);
|
||||
|
||||
@@ -982,6 +1001,7 @@ void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
|
||||
* default server.hz value is 10, so sometimes here we need to process thousands
|
||||
* of clients per second, turning this function into a source of latency.
|
||||
*/
|
||||
#define CLIENTS_CRON_PAUSE_IOTHREAD 8
|
||||
#define CLIENTS_CRON_MIN_ITERATIONS 5
|
||||
void clientsCron(void) {
|
||||
/* Try to process at least numclients/server.hz of clients
|
||||
@@ -1016,6 +1036,15 @@ void clientsCron(void) {
|
||||
ClientsPeakMemInput[zeroidx] = 0;
|
||||
ClientsPeakMemOutput[zeroidx] = 0;
|
||||
|
||||
/* Pause the IO threads that are processing clients, to let us access clients
|
||||
* safely. In order to avoid increasing CPU usage by pausing all threads when
|
||||
* there are too many io threads, we pause io threads in multiple batches. */
|
||||
static int start = 1, end = 0;
|
||||
if (server.io_threads_num >= 1 && listLength(server.clients) > 0) {
|
||||
end = start + CLIENTS_CRON_PAUSE_IOTHREAD - 1;
|
||||
if (end >= server.io_threads_num) end = server.io_threads_num - 1;
|
||||
pauseIOThreadsRange(start, end);
|
||||
}
|
||||
|
||||
while(listLength(server.clients) && iterations--) {
|
||||
client *c;
|
||||
@@ -1026,6 +1055,15 @@ void clientsCron(void) {
|
||||
head = listFirst(server.clients);
|
||||
c = listNodeValue(head);
|
||||
listRotateHeadToTail(server.clients);
|
||||
|
||||
if (c->running_tid != IOTHREAD_MAIN_THREAD_ID &&
|
||||
!(c->running_tid >= start && c->running_tid <= end))
|
||||
{
|
||||
/* Skip clients that are being processed by the IO threads that
|
||||
* are not paused. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The following functions do different service checks on the client.
|
||||
* The protocol is that they return non-zero if the client was
|
||||
* terminated. */
|
||||
@@ -1046,6 +1084,14 @@ void clientsCron(void) {
|
||||
|
||||
if (closeClientOnOutputBufferLimitReached(c, 0)) continue;
|
||||
}
|
||||
|
||||
/* Resume the IO threads that were paused */
|
||||
if (end) {
|
||||
resumeIOThreadsRange(start, end);
|
||||
start = end + 1;
|
||||
if (start >= server.io_threads_num) start = 1;
|
||||
end = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* This function handles 'background' operations we are required to do
|
||||
@@ -1494,9 +1540,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
migrateCloseTimedoutSockets();
|
||||
}
|
||||
|
||||
/* Stop the I/O threads if we don't have enough pending work. */
|
||||
stopThreadedIOIfNeeded();
|
||||
|
||||
/* Resize tracking keys table if needed. This is also done at every
|
||||
* command execution, but we want to be sure that if the last command
|
||||
* executed changes the value via CONFIG SET, the server will perform
|
||||
@@ -1648,24 +1691,28 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
* events to handle. */
|
||||
if (ProcessingEventsWhileBlocked) {
|
||||
uint64_t processed = 0;
|
||||
processed += handleClientsWithPendingReadsUsingThreads();
|
||||
processed += connTypeProcessPendingData();
|
||||
processed += connTypeProcessPendingData(server.el);
|
||||
if (server.aof_state == AOF_ON || server.aof_state == AOF_WAIT_REWRITE)
|
||||
flushAppendOnlyFile(0);
|
||||
processed += handleClientsWithPendingWrites();
|
||||
processed += freeClientsInAsyncFreeQueue();
|
||||
|
||||
/* Let the clients after the blocking call be processed. */
|
||||
processClientsOfAllIOThreads();
|
||||
/* New connections may have been established while blocked, clients from
|
||||
* IO thread may have replies to write, ensure they are promptly sent to
|
||||
* IO threads. */
|
||||
processed += sendPendingClientsToIOThreads();
|
||||
|
||||
server.events_processed_while_blocked += processed;
|
||||
return;
|
||||
}
|
||||
|
||||
/* We should handle pending reads clients ASAP after event loop. */
|
||||
handleClientsWithPendingReadsUsingThreads();
|
||||
|
||||
/* Handle pending data(typical TLS). (must be done before flushAppendOnlyFile) */
|
||||
connTypeProcessPendingData();
|
||||
connTypeProcessPendingData(server.el);
|
||||
|
||||
/* If any connection type(typical TLS) still has pending unread data don't sleep at all. */
|
||||
int dont_sleep = connTypeHasPendingData();
|
||||
int dont_sleep = connTypeHasPendingData(server.el);
|
||||
|
||||
/* Call the Redis Cluster before sleep function. Note that this function
|
||||
* may change the state of Redis Cluster (from ok to fail or vice versa),
|
||||
@@ -1731,8 +1778,8 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
long long prev_fsynced_reploff = server.fsynced_reploff;
|
||||
|
||||
/* Write the AOF buffer on disk,
|
||||
* must be done before handleClientsWithPendingWritesUsingThreads,
|
||||
* in case of appendfsync=always. */
|
||||
* must be done before handleClientsWithPendingWrites and
|
||||
* sendPendingClientsToIOThreads, in case of appendfsync=always. */
|
||||
if (server.aof_state == AOF_ON || server.aof_state == AOF_WAIT_REWRITE)
|
||||
flushAppendOnlyFile(0);
|
||||
|
||||
@@ -1754,7 +1801,10 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
}
|
||||
|
||||
/* Handle writes with pending output buffers. */
|
||||
handleClientsWithPendingWritesUsingThreads();
|
||||
handleClientsWithPendingWrites();
|
||||
|
||||
/* Let io thread to handle its pending clients. */
|
||||
sendPendingClientsToIOThreads();
|
||||
|
||||
/* Record cron time in beforeSleep. This does not include the time consumed by AOF writing and IO writing above. */
|
||||
monotime cron_start_time_after_write = getMonotonicUs();
|
||||
@@ -2055,7 +2105,7 @@ void initServerConfig(void) {
|
||||
server.bindaddr[j] = zstrdup(default_bindaddr[j]);
|
||||
memset(server.listeners, 0x00, sizeof(server.listeners));
|
||||
server.active_expire_enabled = 1;
|
||||
server.lazy_expire_disabled = 0;
|
||||
server.allow_access_expired = 0;
|
||||
server.skip_checksum_validation = 0;
|
||||
server.loading = 0;
|
||||
server.async_loading = 0;
|
||||
@@ -2083,6 +2133,7 @@ void initServerConfig(void) {
|
||||
memset(server.blocked_clients_by_type,0,
|
||||
sizeof(server.blocked_clients_by_type));
|
||||
server.shutdown_asap = 0;
|
||||
server.crashing = 0;
|
||||
server.shutdown_flags = 0;
|
||||
server.shutdown_mstime = 0;
|
||||
server.cluster_module_flags = CLUSTER_MODULE_FLAG_NONE;
|
||||
@@ -2549,9 +2600,9 @@ void resetServerStats(void) {
|
||||
server.stat_sync_full = 0;
|
||||
server.stat_sync_partial_ok = 0;
|
||||
server.stat_sync_partial_err = 0;
|
||||
server.stat_io_reads_processed = 0;
|
||||
atomicSet(server.stat_io_reads_processed, 0);
|
||||
atomicSet(server.stat_total_reads_processed, 0);
|
||||
server.stat_io_writes_processed = 0;
|
||||
atomicSet(server.stat_io_writes_processed, 0);
|
||||
atomicSet(server.stat_total_writes_processed, 0);
|
||||
atomicSet(server.stat_client_qbuf_limit_disconnections, 0);
|
||||
server.stat_client_outbuf_limit_disconnections = 0;
|
||||
@@ -2671,7 +2722,7 @@ void initServer(void) {
|
||||
flags |= KVSTORE_FREE_EMPTY_DICTS;
|
||||
}
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
server.db[j].keys = kvstoreCreate(&dbDictType, slot_count_bits, flags);
|
||||
server.db[j].keys = kvstoreCreate(&dbDictType, slot_count_bits, flags | KVSTORE_ALLOC_META_KEYS_HIST);
|
||||
server.db[j].expires = kvstoreCreate(&dbExpiresDictType, slot_count_bits, flags);
|
||||
server.db[j].hexpires = ebCreate();
|
||||
server.db[j].expires_cursor = 0;
|
||||
@@ -2744,6 +2795,7 @@ void initServer(void) {
|
||||
server.aof_last_write_errno = 0;
|
||||
server.repl_good_slaves_count = 0;
|
||||
server.last_sig_received = 0;
|
||||
memset(server.io_threads_clients_num, 0, sizeof(server.io_threads_clients_num));
|
||||
|
||||
/* Initiate acl info struct */
|
||||
server.acl_info.invalid_cmd_accesses = 0;
|
||||
@@ -5501,8 +5553,8 @@ void releaseInfoSectionDict(dict *sec) {
|
||||
* The resulting dictionary should be released with releaseInfoSectionDict. */
|
||||
dict *genInfoSectionDict(robj **argv, int argc, char **defaults, int *out_all, int *out_everything) {
|
||||
char *default_sections[] = {
|
||||
"server", "clients", "memory", "persistence", "stats", "replication",
|
||||
"cpu", "module_list", "errorstats", "cluster", "keyspace", NULL};
|
||||
"server", "clients", "memory", "persistence", "stats", "replication", "threads",
|
||||
"cpu", "module_list", "errorstats", "cluster", "keyspace", "keysizes", NULL};
|
||||
if (!defaults)
|
||||
defaults = default_sections;
|
||||
|
||||
@@ -5852,6 +5904,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
||||
long long current_active_defrag_time = server.stat_last_active_defrag_time ?
|
||||
(long long) elapsedUs(server.stat_last_active_defrag_time): 0;
|
||||
long long stat_client_qbuf_limit_disconnections;
|
||||
long long stat_io_reads_processed, stat_io_writes_processed;
|
||||
atomicGet(server.stat_total_reads_processed, stat_total_reads_processed);
|
||||
atomicGet(server.stat_total_writes_processed, stat_total_writes_processed);
|
||||
atomicGet(server.stat_net_input_bytes, stat_net_input_bytes);
|
||||
@@ -5859,6 +5912,8 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
||||
atomicGet(server.stat_net_repl_input_bytes, stat_net_repl_input_bytes);
|
||||
atomicGet(server.stat_net_repl_output_bytes, stat_net_repl_output_bytes);
|
||||
atomicGet(server.stat_client_qbuf_limit_disconnections, stat_client_qbuf_limit_disconnections);
|
||||
atomicGet(server.stat_io_reads_processed, stat_io_reads_processed);
|
||||
atomicGet(server.stat_io_writes_processed, stat_io_writes_processed);
|
||||
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
info = sdscatprintf(info, "# Stats\r\n" FMTARGS(
|
||||
@@ -5910,8 +5965,8 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
||||
"dump_payload_sanitizations:%lld\r\n", server.stat_dump_payload_sanitizations,
|
||||
"total_reads_processed:%lld\r\n", stat_total_reads_processed,
|
||||
"total_writes_processed:%lld\r\n", stat_total_writes_processed,
|
||||
"io_threaded_reads_processed:%lld\r\n", server.stat_io_reads_processed,
|
||||
"io_threaded_writes_processed:%lld\r\n", server.stat_io_writes_processed,
|
||||
"io_threaded_reads_processed:%lld\r\n", stat_io_reads_processed,
|
||||
"io_threaded_writes_processed:%lld\r\n", stat_io_writes_processed,
|
||||
"client_query_buffer_limit_disconnections:%lld\r\n", stat_client_qbuf_limit_disconnections,
|
||||
"client_output_buffer_limit_disconnections:%lld\r\n", server.stat_client_outbuf_limit_disconnections,
|
||||
"reply_buffer_shrinks:%lld\r\n", server.stat_reply_buffer_shrinks,
|
||||
@@ -6060,6 +6115,15 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
||||
#endif /* RUSAGE_THREAD */
|
||||
}
|
||||
|
||||
/* Threads */
|
||||
if (all_sections || (dictFind(section_dict,"threads") != NULL)) {
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
info = sdscatprintf(info, "# Threads\r\n");
|
||||
for (j = 0; j < server.io_threads_num; j++) {
|
||||
info = sdscatprintf(info, "io_thread_%d:clients=%d\r\n", j, server.io_threads_clients_num[j]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Modules */
|
||||
if (all_sections || (dictFind(section_dict,"module_list") != NULL) || (dictFind(section_dict,"modules") != NULL)) {
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
@@ -6130,6 +6194,60 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
||||
}
|
||||
}
|
||||
|
||||
/* keysizes */
|
||||
if (all_sections || (dictFind(section_dict,"keysizes") != NULL)) {
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
info = sdscatprintf(info, "# Keysizes\r\n");
|
||||
|
||||
char *typestr[] = {
|
||||
[OBJ_STRING] = "distrib_strings_sizes",
|
||||
[OBJ_LIST] = "distrib_lists_items",
|
||||
[OBJ_SET] = "distrib_sets_items",
|
||||
[OBJ_ZSET] = "distrib_zsets_items",
|
||||
[OBJ_HASH] = "distrib_hashes_items"
|
||||
};
|
||||
serverAssert(sizeof(typestr)/sizeof(typestr[0]) == OBJ_TYPE_BASIC_MAX);
|
||||
|
||||
for (int dbnum = 0; dbnum < server.dbnum; dbnum++) {
|
||||
char *expSizeLabels[] = {
|
||||
"1", "2", "4", "8", "16", "32", "64", "128", "256", "512", /* Byte */
|
||||
"1K", "2K", "4K", "8K", "16K", "32K", "64K", "128K", "256K", "512K", /* Kilo */
|
||||
"1M", "2M", "4M", "8M", "16M", "32M", "64M", "128M", "256M", "512M", /* Mega */
|
||||
"1G", "2G", "4G", "8G", "16G", "32G", "64G", "128G", "256G", "512G", /* Giga */
|
||||
"1T", "2T", "4T", "8T", "16T", "32T", "64T", "128T", "256T", "512T", /* Tera */
|
||||
"1P", "2P", "4P", "8P", "16P", "32P", "64P", "128P", "256P", "512P", /* Peta */
|
||||
"1E", "2E", "4E", "8E" /* Exa */
|
||||
};
|
||||
|
||||
if (kvstoreSize(server.db[dbnum].keys) == 0)
|
||||
continue;
|
||||
|
||||
for (int type = 0; type < OBJ_TYPE_BASIC_MAX; type++) {
|
||||
uint64_t *kvstoreHist = kvstoreGetMetadata(server.db[dbnum].keys)->keysizes_hist[type];
|
||||
char buf[10000];
|
||||
int cnt = 0, buflen = 0;
|
||||
|
||||
/* Print histogram to temp buf[]. First bin is garbage */
|
||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen, "db%d_%s:", dbnum, typestr[type]);
|
||||
|
||||
for (int i = 0; i < MAX_KEYSIZES_BINS; i++) {
|
||||
if (kvstoreHist[i] == 0)
|
||||
continue;
|
||||
|
||||
int res = snprintf(buf + buflen, sizeof(buf) - buflen,
|
||||
(cnt == 0) ? "%s=%llu" : ",%s=%llu",
|
||||
expSizeLabels[i], (unsigned long long) kvstoreHist[i]);
|
||||
if (res < 0) break;
|
||||
buflen += res;
|
||||
cnt += kvstoreHist[i];
|
||||
}
|
||||
|
||||
/* Print the temp buf[] to the info string */
|
||||
if (cnt) info = sdscatprintf(info, "%s\r\n", buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Get info from modules.
|
||||
* Returned when the user asked for "everything", "modules", or a specific module section.
|
||||
* We're not aware of the module section names here, and we rather avoid the search when we can.
|
||||
@@ -6567,7 +6685,7 @@ void dismissMemory(void* ptr, size_t size_hint) {
|
||||
void dismissClientMemory(client *c) {
|
||||
/* Dismiss client query buffer and static reply buffer. */
|
||||
dismissMemory(c->buf, c->buf_usable_size);
|
||||
dismissSds(c->querybuf);
|
||||
if (c->querybuf) dismissSds(c->querybuf);
|
||||
/* Dismiss argv array only if we estimate it contains a big buffer. */
|
||||
if (c->argc && c->argv_len_sum/c->argc >= server.page_size) {
|
||||
for (int i = 0; i < c->argc; i++) {
|
||||
@@ -7212,6 +7330,11 @@ int main(int argc, char **argv) {
|
||||
loadDataFromDisk();
|
||||
aofOpenIfNeededOnServerStart();
|
||||
aofDelHistoryFiles();
|
||||
/* While loading data, we delay applying "appendonly" config change.
|
||||
* If there was a config change while we were inside loadDataFromDisk()
|
||||
* above, we'll apply it here. */
|
||||
applyAppendOnlyConfig();
|
||||
|
||||
if (server.cluster_enabled) {
|
||||
serverAssert(verifyClusterConfigWithData() == C_OK);
|
||||
}
|
||||
|
||||
+128
-27
@@ -41,10 +41,6 @@
|
||||
#include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
||||
#ifndef static_assert
|
||||
#define static_assert(expr, lit) extern char __static_assert_failure[(expr) ? 1:-1]
|
||||
#endif
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
typedef long long ustime_t; /* microsecond time type. */
|
||||
|
||||
@@ -65,6 +61,7 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
N-elements flat arrays */
|
||||
#include "rax.h" /* Radix tree */
|
||||
#include "connection.h" /* Connection abstraction */
|
||||
#include "eventnotifier.h" /* Event notification */
|
||||
|
||||
#define REDISMODULE_CORE 1
|
||||
typedef struct redisObject robj;
|
||||
@@ -188,6 +185,14 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
||||
/* Hash table parameters */
|
||||
#define HASHTABLE_MAX_LOAD_FACTOR 1.618 /* Maximum hash table load factor. */
|
||||
|
||||
/* Max number of IO threads */
|
||||
#define IO_THREADS_MAX_NUM 128
|
||||
|
||||
/* Main thread id for doing IO work, whatever we enable or disable io thread
|
||||
* the main thread always does IO work, so we can consider that the main thread
|
||||
* is the io thread 0. */
|
||||
#define IOTHREAD_MAIN_THREAD_ID 0
|
||||
|
||||
/* Command flags. Please check the definition of struct redisCommand in this file
|
||||
* for more information about the meaning of every flag. */
|
||||
#define CMD_WRITE (1ULL<<0)
|
||||
@@ -392,6 +397,29 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
||||
/* Any flag that does not let optimize FLUSH SYNC to run it in bg as blocking client ASYNC */
|
||||
#define CLIENT_AVOID_BLOCKING_ASYNC_FLUSH (CLIENT_DENY_BLOCKING|CLIENT_MULTI|CLIENT_LUA_DEBUG|CLIENT_LUA_DEBUG_SYNC|CLIENT_MODULE)
|
||||
|
||||
/* Client flags for client IO */
|
||||
#define CLIENT_IO_READ_ENABLED (1ULL<<0) /* Client can read from socket. */
|
||||
#define CLIENT_IO_WRITE_ENABLED (1ULL<<1) /* Client can write to socket. */
|
||||
#define CLIENT_IO_PENDING_COMMAND (1ULL<<2) /* Similar to CLIENT_PENDING_COMMAND. */
|
||||
#define CLIENT_IO_REUSABLE_QUERYBUFFER (1ULL<<3) /* The client is using the reusable query buffer. */
|
||||
#define CLIENT_IO_CLOSE_ASAP (1ULL<<4) /* Close this client ASAP in IO thread. */
|
||||
|
||||
/* Definitions for client read errors. These error codes are used to indicate
|
||||
* various issues that can occur while reading or parsing data from a client. */
|
||||
#define CLIENT_READ_TOO_BIG_INLINE_REQUEST 1
|
||||
#define CLIENT_READ_UNBALANCED_QUOTES 2
|
||||
#define CLIENT_READ_MASTER_USING_INLINE_PROTOCAL 3
|
||||
#define CLIENT_READ_TOO_BIG_MBULK_COUNT_STRING 4
|
||||
#define CLIENT_READ_TOO_BIG_BUCK_COUNT_STRING 5
|
||||
#define CLIENT_READ_EXPECTED_DOLLAR 6
|
||||
#define CLIENT_READ_INVALID_BUCK_LENGTH 7
|
||||
#define CLIENT_READ_UNAUTH_BUCK_LENGTH 8
|
||||
#define CLIENT_READ_INVALID_MULTIBUCK_LENGTH 9
|
||||
#define CLIENT_READ_UNAUTH_MBUCK_COUNT 10
|
||||
#define CLIENT_READ_CONN_DISCONNECTED 11
|
||||
#define CLIENT_READ_CONN_CLOSED 12
|
||||
#define CLIENT_READ_REACHED_MAX_QUERYBUF 13
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
typedef enum blocking_type {
|
||||
@@ -580,6 +608,12 @@ typedef enum {
|
||||
#define SHUTDOWN_NOW 4 /* Don't wait for replicas to catch up. */
|
||||
#define SHUTDOWN_FORCE 8 /* Don't let errors prevent shutdown. */
|
||||
|
||||
/* IO thread pause status */
|
||||
#define IO_THREAD_UNPAUSED 0
|
||||
#define IO_THREAD_PAUSING 1
|
||||
#define IO_THREAD_PAUSED 2
|
||||
#define IO_THREAD_RESUMING 3
|
||||
|
||||
/* Command call flags, see call() function */
|
||||
#define CMD_CALL_NONE 0
|
||||
#define CMD_CALL_PROPAGATE_AOF (1<<0)
|
||||
@@ -697,6 +731,7 @@ typedef enum {
|
||||
#define OBJ_SET 2 /* Set object. */
|
||||
#define OBJ_ZSET 3 /* Sorted set object. */
|
||||
#define OBJ_HASH 4 /* Hash object. */
|
||||
#define OBJ_TYPE_BASIC_MAX 5 /* Max number of basic object types. */
|
||||
|
||||
/* The "module" object type is a special one that signals that the object
|
||||
* is one directly managed by a Redis module. In this case the value points
|
||||
@@ -820,6 +855,8 @@ struct RedisModule {
|
||||
int blocked_clients; /* Count of RedisModuleBlockedClient in this module. */
|
||||
RedisModuleInfoFunc info_cb; /* Callback for module to add INFO fields. */
|
||||
RedisModuleDefragFunc defrag_cb; /* Callback for global data defrag. */
|
||||
RedisModuleDefragFunc defrag_start_cb; /* Callback indicating defrag started. */
|
||||
RedisModuleDefragFunc defrag_end_cb; /* Callback indicating defrag ended. */
|
||||
struct moduleLoadQueueEntry *loadmod; /* Module load arguments for config rewrite. */
|
||||
int num_commands_with_acl_categories; /* Number of commands in this module included in acl categories */
|
||||
int onload; /* Flag to identify if the call is being made from Onload (0 or 1) */
|
||||
@@ -966,7 +1003,7 @@ typedef struct replBufBlock {
|
||||
* by integers from 0 (the default database) up to the max configured
|
||||
* database. The database number is the 'id' field in the structure. */
|
||||
typedef struct redisDb {
|
||||
kvstore *keys; /* The keyspace for this DB */
|
||||
kvstore *keys; /* The keyspace for this DB. As metadata, holds keysizes histogram */
|
||||
kvstore *expires; /* Timeout of keys with a timeout set */
|
||||
ebuckets hexpires; /* Hash expiration DS. Single TTL per hash (of next min field to expire) */
|
||||
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
|
||||
@@ -1158,6 +1195,10 @@ typedef struct client {
|
||||
uint64_t id; /* Client incremental unique ID. */
|
||||
uint64_t flags; /* Client flags: CLIENT_* macros. */
|
||||
connection *conn;
|
||||
uint8_t tid; /* Thread assigned ID this client is bound to. */
|
||||
uint8_t running_tid; /* Thread assigned ID this client is running on. */
|
||||
uint8_t io_flags; /* Accessed by both main and IO threads, but not modified concurrently */
|
||||
uint8_t read_error; /* Client read error: CLIENT_READ_* macros. */
|
||||
int resp; /* RESP protocol version. Can be 2 or 3. */
|
||||
redisDb *db; /* Pointer to currently SELECTed DB. */
|
||||
robj *name; /* As set by CLIENT SETNAME. */
|
||||
@@ -1225,8 +1266,8 @@ typedef struct client {
|
||||
sds peerid; /* Cached peer ID. */
|
||||
sds sockname; /* Cached connection target address. */
|
||||
listNode *client_list_node; /* list node in client list */
|
||||
listNode *io_thread_client_list_node; /* list node in io thread client list */
|
||||
listNode *postponed_list_node; /* list node within the postponed list */
|
||||
listNode *pending_read_list_node; /* list node in clients pending read list */
|
||||
void *module_blocked_client; /* Pointer to the RedisModuleBlockedClient associated with this
|
||||
* client. This is set in case of module authentication before the
|
||||
* unblocked client is reprocessed to handle reply callbacks. */
|
||||
@@ -1279,6 +1320,20 @@ typedef struct client {
|
||||
#endif
|
||||
} client;
|
||||
|
||||
typedef struct __attribute__((aligned(CACHE_LINE_SIZE))) {
|
||||
uint8_t id; /* The unique ID assigned, if IO_THREADS_MAX_NUM is more
|
||||
* than 256, we should also promote the data type. */
|
||||
pthread_t tid; /* Pthread ID */
|
||||
redisAtomic int paused; /* Paused status for the io thread. */
|
||||
aeEventLoop *el; /* Main event loop of io thread. */
|
||||
list *pending_clients; /* List of clients with pending writes. */
|
||||
list *processing_clients; /* List of clients being processed. */
|
||||
eventNotifier *pending_clients_notifier; /* Used to wake up the loop when write should be performed. */
|
||||
pthread_mutex_t pending_clients_mutex; /* Mutex for pending write list */
|
||||
list *pending_clients_to_main_thread; /* Clients that are waiting to be executed by the main thread. */
|
||||
list *clients; /* IO thread managed clients. */
|
||||
} IOThread;
|
||||
|
||||
/* ACL information */
|
||||
typedef struct aclInfo {
|
||||
long long user_auth_failures; /* Auth failure counts on user level */
|
||||
@@ -1566,6 +1621,7 @@ struct redisServer {
|
||||
int errors_enabled; /* If true, errorstats is enabled, and we will add new errors. */
|
||||
unsigned int lruclock; /* Clock for LRU eviction */
|
||||
volatile sig_atomic_t shutdown_asap; /* Shutdown ordered by signal handler. */
|
||||
volatile sig_atomic_t crashing; /* Server is crashing report. */
|
||||
mstime_t shutdown_mstime; /* Timestamp to limit graceful shutdown. */
|
||||
int last_sig_received; /* Indicates the last SIGNAL received, if any (e.g., SIGINT or SIGTERM). */
|
||||
int shutdown_flags; /* Flags passed to prepareForShutdown(). */
|
||||
@@ -1636,6 +1692,7 @@ struct redisServer {
|
||||
redisAtomic uint64_t next_client_id; /* Next client unique ID. Incremental. */
|
||||
int protected_mode; /* Don't accept external connections. */
|
||||
int io_threads_num; /* Number of IO threads to use. */
|
||||
int io_threads_clients_num[IO_THREADS_MAX_NUM]; /* Number of clients assigned to each IO thread. */
|
||||
int io_threads_do_reads; /* Read and parse from IO threads? */
|
||||
int io_threads_active; /* Is IO threads currently active? */
|
||||
long long events_processed_while_blocked; /* processEventsWhileBlocked() */
|
||||
@@ -1708,8 +1765,8 @@ struct redisServer {
|
||||
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
|
||||
long long stat_total_error_replies; /* Total number of issued error replies ( command + rejected errors ) */
|
||||
long long stat_dump_payload_sanitizations; /* Number deep dump payloads integrity validations. */
|
||||
long long stat_io_reads_processed; /* Number of read events processed by IO / Main threads */
|
||||
long long stat_io_writes_processed; /* Number of write events processed by IO / Main threads */
|
||||
redisAtomic long long stat_io_reads_processed; /* Number of read events processed by IO / Main threads */
|
||||
redisAtomic long long stat_io_writes_processed; /* Number of write events processed by IO / Main threads */
|
||||
redisAtomic long long stat_total_reads_processed; /* Total number of read events processed */
|
||||
redisAtomic long long stat_total_writes_processed; /* Total number of write events processed */
|
||||
redisAtomic long long stat_client_qbuf_limit_disconnections; /* Total number of clients reached query buf length limit */
|
||||
@@ -1741,7 +1798,7 @@ struct redisServer {
|
||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||
int active_expire_effort; /* From 1 (default) to 10, active effort. */
|
||||
int lazy_expire_disabled; /* If > 0, don't trigger lazy expire */
|
||||
int allow_access_expired; /* If > 0, allow access to logically expired keys */
|
||||
int active_defrag_enabled;
|
||||
int sanitize_dump_payload; /* Enables deep sanitization for ziplist and listpack in RDB and RESTORE. */
|
||||
int skip_checksum_validation; /* Disable checksum validation for RDB and RESTORE payload. */
|
||||
@@ -2459,11 +2516,6 @@ typedef struct {
|
||||
#define OBJ_HASH_KEY 1
|
||||
#define OBJ_HASH_VALUE 2
|
||||
|
||||
#define IO_THREADS_OP_IDLE 0
|
||||
#define IO_THREADS_OP_READ 1
|
||||
#define IO_THREADS_OP_WRITE 2
|
||||
extern int io_threads_op;
|
||||
|
||||
/* Hash-field data type (of t_hash.c) */
|
||||
typedef mstr hfield;
|
||||
extern mstrKind mstrFieldKind;
|
||||
@@ -2555,6 +2607,8 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj
|
||||
int moduleDefragValue(robj *key, robj *obj, int dbid);
|
||||
int moduleLateDefrag(robj *key, robj *value, unsigned long *cursor, long long endtime, int dbid);
|
||||
void moduleDefragGlobals(void);
|
||||
void moduleDefragStart(void);
|
||||
void moduleDefragEnd(void);
|
||||
void *moduleGetHandleByName(char *modulename);
|
||||
int moduleIsModuleCommand(void *module_handle, struct redisCommand *cmd);
|
||||
|
||||
@@ -2627,6 +2681,7 @@ void addReplyDouble(client *c, double d);
|
||||
void addReplyBigNum(client *c, const char *num, size_t len);
|
||||
void addReplyHumanLongDouble(client *c, long double d);
|
||||
void addReplyLongLong(client *c, long long ll);
|
||||
void addReplyLongLongFromStr(client *c, robj* str);
|
||||
void addReplyArrayLen(client *c, long length);
|
||||
void addReplyMapLen(client *c, long length);
|
||||
void addReplySetLen(client *c, long length);
|
||||
@@ -2675,9 +2730,6 @@ void whileBlockedCron(void);
|
||||
void blockingOperationStarts(void);
|
||||
void blockingOperationEnds(void);
|
||||
int handleClientsWithPendingWrites(void);
|
||||
int handleClientsWithPendingWritesUsingThreads(void);
|
||||
int handleClientsWithPendingReadsUsingThreads(void);
|
||||
int stopThreadedIOIfNeeded(void);
|
||||
int clientHasPendingReplies(client *c);
|
||||
int updateClientMemUsageAndBucket(client *c);
|
||||
void removeClientFromMemUsageBucket(client *c, int allow_eviction);
|
||||
@@ -2686,10 +2738,31 @@ int writeToClient(client *c, int handler_installed);
|
||||
void linkClient(client *c);
|
||||
void protectClient(client *c);
|
||||
void unprotectClient(client *c);
|
||||
void initThreadedIO(void);
|
||||
client *lookupClientByID(uint64_t id);
|
||||
int authRequired(client *c);
|
||||
void putClientInPendingWriteQueue(client *c);
|
||||
/* reply macros */
|
||||
#define ADD_REPLY_BULK_CBUFFER_STRING_CONSTANT(c, str) addReplyBulkCBuffer(c, str, strlen(str))
|
||||
|
||||
/* iothread.c - the threaded io implementation */
|
||||
void initThreadedIO(void);
|
||||
void killIOThreads(void);
|
||||
void pauseIOThread(int id);
|
||||
void resumeIOThread(int id);
|
||||
void pauseAllIOThreads(void);
|
||||
void resumeAllIOThreads(void);
|
||||
void pauseIOThreadsRange(int start, int end);
|
||||
void resumeIOThreadsRange(int start, int end);
|
||||
int resizeAllIOThreadsEventLoops(size_t newsize);
|
||||
int sendPendingClientsToIOThreads(void);
|
||||
void enqueuePendingClientsToMainThread(client *c, int unbind);
|
||||
void putInPendingClienstForIOThreads(client *c);
|
||||
void handleClientReadError(client *c);
|
||||
void unbindClientFromIOThreadEventLoop(client *c);
|
||||
void processClientsOfAllIOThreads(void);
|
||||
void assignClientToIOThread(client *c);
|
||||
void fetchClientFromIOThread(client *c);
|
||||
int isClientMustHandledByMainThread(client *c);
|
||||
|
||||
/* logreqres.c - logging of requests and responses */
|
||||
void reqresReset(client *c, int free_buf);
|
||||
@@ -2740,7 +2813,7 @@ robj *listTypeGet(listTypeEntry *entry);
|
||||
unsigned char *listTypeGetValue(listTypeEntry *entry, size_t *vlen, long long *lval);
|
||||
void listTypeInsert(listTypeEntry *entry, robj *value, int where);
|
||||
void listTypeReplace(listTypeEntry *entry, robj *value);
|
||||
int listTypeEqual(listTypeEntry *entry, robj *o);
|
||||
int listTypeEqual(listTypeEntry *entry, robj *o, size_t object_len);
|
||||
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
|
||||
robj *listTypeDup(robj *o);
|
||||
void listTypeDelRange(robj *o, long start, long stop);
|
||||
@@ -2791,6 +2864,7 @@ int isSdsRepresentableAsLongLong(sds s, long long *llval);
|
||||
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
|
||||
robj *tryObjectEncoding(robj *o);
|
||||
robj *tryObjectEncodingEx(robj *o, int try_trim);
|
||||
size_t getObjectLength(robj *o);
|
||||
robj *getDecodedObject(robj *o);
|
||||
size_t stringObjectLen(robj *o);
|
||||
robj *createStringObjectFromLongLong(long long value);
|
||||
@@ -2877,6 +2951,8 @@ const char *getFailoverStateString(void);
|
||||
/* Generic persistence functions */
|
||||
void startLoadingFile(size_t size, char* filename, int rdbflags);
|
||||
void startLoading(size_t size, int rdbflags, int async);
|
||||
void loadingSetFlags(char *filename, size_t size, int async);
|
||||
void loadingFireEvent(int rdbflags);
|
||||
void loadingAbsProgress(off_t pos);
|
||||
void loadingIncrProgress(off_t size);
|
||||
void stopLoading(int success);
|
||||
@@ -2904,9 +2980,10 @@ int rewriteAppendOnlyFileBackground(void);
|
||||
int loadAppendOnlyFiles(aofManifest *am);
|
||||
void stopAppendOnly(void);
|
||||
int startAppendOnly(void);
|
||||
void startAppendOnlyWithRetry(void);
|
||||
void applyAppendOnlyConfig(void);
|
||||
void backgroundRewriteDoneHandler(int exitcode, int bysignal);
|
||||
void killAppendOnlyChild(void);
|
||||
void restartAOFAfterSYNC(void);
|
||||
void aofLoadManifestFromDisk(void);
|
||||
void aofOpenIfNeededOnServerStart(void);
|
||||
void aofManifestFree(aofManifest *am);
|
||||
@@ -3127,6 +3204,8 @@ void checkChildrenDone(void);
|
||||
int setOOMScoreAdj(int process_class);
|
||||
void rejectCommandFormat(client *c, const char *fmt, ...);
|
||||
void *activeDefragAlloc(void *ptr);
|
||||
void *activeDefragAllocRaw(size_t size);
|
||||
void activeDefragFreeRaw(void *ptr);
|
||||
robj *activeDefragStringOb(robj* ob);
|
||||
void dismissSds(sds s);
|
||||
void dismissMemory(void* ptr, size_t size_hint);
|
||||
@@ -3208,6 +3287,7 @@ typedef struct dictExpireMetadata {
|
||||
#define HFE_LAZY_AVOID_HASH_DEL (1<<1) /* Avoid deleting hash if the field is the last one */
|
||||
#define HFE_LAZY_NO_NOTIFICATION (1<<2) /* Do not send notification, used when multiple fields
|
||||
* may expire and only one notification is desired. */
|
||||
#define HFE_LAZY_ACCESS_EXPIRED (1<<3) /* Avoid lazy expire and allow access to expired fields */
|
||||
|
||||
void hashTypeConvert(robj *o, int enc, ebuckets *hexpires);
|
||||
void hashTypeTryConversion(redisDb *db, robj *subject, robj **argv, int start, int end);
|
||||
@@ -3349,16 +3429,20 @@ long long getModuleNumericConfig(ModuleConfig *module_config);
|
||||
int setModuleNumericConfig(ModuleConfig *config, long long val, const char **err);
|
||||
|
||||
/* db.c -- Keyspace access API */
|
||||
void updateKeysizesHist(redisDb *db, int didx, uint32_t type, uint64_t oldLen, uint64_t newLen);
|
||||
int removeExpire(redisDb *db, robj *key);
|
||||
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj);
|
||||
void deleteEvictedKeyAndPropagate(redisDb *db, robj *keyobj, long long *key_mem_freed);
|
||||
void propagateDeletion(redisDb *db, robj *key, int lazy);
|
||||
int keyIsExpired(redisDb *db, robj *key);
|
||||
long long getExpire(redisDb *db, robj *key);
|
||||
void setExpire(client *c, redisDb *db, robj *key, long long when);
|
||||
void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde);
|
||||
int checkAlreadyExpired(long long when);
|
||||
int parseExtendedExpireArgumentsOrReply(client *c, int *flags);
|
||||
robj *lookupKeyRead(redisDb *db, robj *key);
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key);
|
||||
robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref);
|
||||
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
|
||||
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
|
||||
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
||||
@@ -3368,16 +3452,18 @@ robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock, int lru_multiplier);
|
||||
#define LOOKUP_NONE 0
|
||||
#define LOOKUP_NOTOUCH (1<<0) /* Don't update LRU. */
|
||||
#define LOOKUP_NONOTIFY (1<<1) /* Don't trigger keyspace event on key misses. */
|
||||
#define LOOKUP_NOSTATS (1<<2) /* Don't update keyspace hits/misses counters. */
|
||||
#define LOOKUP_WRITE (1<<3) /* Delete expired keys even in replicas. */
|
||||
#define LOOKUP_NOEXPIRE (1<<4) /* Avoid deleting lazy expired keys. */
|
||||
#define LOOKUP_NOTOUCH (1<<0) /* Don't update LRU. */
|
||||
#define LOOKUP_NONOTIFY (1<<1) /* Don't trigger keyspace event on key misses. */
|
||||
#define LOOKUP_NOSTATS (1<<2) /* Don't update keyspace hits/misses counters. */
|
||||
#define LOOKUP_WRITE (1<<3) /* Delete expired keys even in replicas. */
|
||||
#define LOOKUP_NOEXPIRE (1<<4) /* Avoid deleting lazy expired keys. */
|
||||
#define LOOKUP_ACCESS_EXPIRED (1<<5) /* Allow lookup to expired key. */
|
||||
#define LOOKUP_NOEFFECTS (LOOKUP_NONOTIFY | LOOKUP_NOSTATS | LOOKUP_NOTOUCH | LOOKUP_NOEXPIRE) /* Avoid any effects from fetching the key */
|
||||
|
||||
dictEntry *dbAdd(redisDb *db, robj *key, robj *val);
|
||||
int dbAddRDBLoad(redisDb *db, sds key, robj *val);
|
||||
void dbReplaceValue(redisDb *db, robj *key, robj *val);
|
||||
void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de);
|
||||
|
||||
#define SETKEY_KEEPTTL 1
|
||||
#define SETKEY_NO_SIGNAL 2
|
||||
@@ -3385,12 +3471,26 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val);
|
||||
#define SETKEY_DOESNT_EXIST 8
|
||||
#define SETKEY_ADD_OR_UPDATE 16 /* Key most likely doesn't exists */
|
||||
void setKey(client *c, redisDb *db, robj *key, robj *val, int flags);
|
||||
void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de);
|
||||
robj *dbRandomKey(redisDb *db);
|
||||
int dbGenericDelete(redisDb *db, robj *key, int async, int flags);
|
||||
int dbSyncDelete(redisDb *db, robj *key);
|
||||
int dbDelete(redisDb *db, robj *key);
|
||||
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
|
||||
robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de);
|
||||
|
||||
#define FLUSH_TYPE_ALL 0
|
||||
#define FLUSH_TYPE_DB 1
|
||||
#define FLUSH_TYPE_SLOTS 2
|
||||
typedef struct SlotRange {
|
||||
unsigned short first, last;
|
||||
} SlotRange;
|
||||
typedef struct SlotsFlush {
|
||||
int numRanges;
|
||||
SlotRange ranges[];
|
||||
} SlotsFlush;
|
||||
void replySlotsFlushAndFree(client *c, SlotsFlush *sflush);
|
||||
int flushCommandCommon(client *c, int type, int flags, SlotsFlush *sflush);
|
||||
#define EMPTYDB_NO_FLAGS 0 /* No flags. */
|
||||
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
|
||||
#define EMPTYDB_NOFUNCTIONS (1<<1) /* Indicate not to flush the functions. */
|
||||
@@ -3399,7 +3499,7 @@ long long emptyDbStructure(redisDb *dbarray, int dbnum, int async, void(callback
|
||||
void flushAllDataAndResetRDB(int flags);
|
||||
long long dbTotalServerKeyCount(void);
|
||||
redisDb *initTempDb(void);
|
||||
void discardTempDb(redisDb *tempDb, void(callback)(dict*));
|
||||
void discardTempDb(redisDb *tempDb);
|
||||
|
||||
|
||||
int selectDb(client *c, int id);
|
||||
@@ -3634,6 +3734,7 @@ void scardCommand(client *c);
|
||||
void spopCommand(client *c);
|
||||
void srandmemberCommand(client *c);
|
||||
void sinterCommand(client *c);
|
||||
void smembersCommand(client *c);
|
||||
void sinterCardCommand(client *c);
|
||||
void sinterstoreCommand(client *c);
|
||||
void sunionCommand(client *c);
|
||||
@@ -3760,6 +3861,7 @@ void migrateCommand(client *c);
|
||||
void askingCommand(client *c);
|
||||
void readonlyCommand(client *c);
|
||||
void readwriteCommand(client *c);
|
||||
void sflushCommand(client *c);
|
||||
int verifyDumpPayload(unsigned char *p, size_t len, uint16_t *rdbver_ptr);
|
||||
void dumpCommand(client *c);
|
||||
void objectCommand(client *c);
|
||||
@@ -3863,7 +3965,6 @@ void xorDigest(unsigned char *digest, const void *ptr, size_t len);
|
||||
sds catSubCommandFullname(const char *parent_name, const char *sub_name);
|
||||
void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subcommand, const char *declared_name);
|
||||
void debugDelay(int usec);
|
||||
void killIOThreads(void);
|
||||
void killThreads(void);
|
||||
void makeThreadKillable(void);
|
||||
void swapMainDbWithTempDb(redisDb *tempDb);
|
||||
|
||||
+25
-12
@@ -53,11 +53,12 @@ static ConnectionType CT_Socket;
|
||||
* be embedded in different structs, not just client.
|
||||
*/
|
||||
|
||||
static connection *connCreateSocket(void) {
|
||||
static connection *connCreateSocket(struct aeEventLoop *el) {
|
||||
connection *conn = zcalloc(sizeof(connection));
|
||||
conn->type = &CT_Socket;
|
||||
conn->fd = -1;
|
||||
conn->iovcnt = IOV_MAX;
|
||||
conn->el = el;
|
||||
|
||||
return conn;
|
||||
}
|
||||
@@ -72,9 +73,9 @@ static connection *connCreateSocket(void) {
|
||||
* is not in an error state (which is not possible for a socket connection,
|
||||
* but could but possible with other protocols).
|
||||
*/
|
||||
static connection *connCreateAcceptedSocket(int fd, void *priv) {
|
||||
static connection *connCreateAcceptedSocket(struct aeEventLoop *el, int fd, void *priv) {
|
||||
UNUSED(priv);
|
||||
connection *conn = connCreateSocket();
|
||||
connection *conn = connCreateSocket(el);
|
||||
conn->fd = fd;
|
||||
conn->state = CONN_STATE_ACCEPTING;
|
||||
return conn;
|
||||
@@ -93,7 +94,7 @@ static int connSocketConnect(connection *conn, const char *addr, int port, const
|
||||
conn->state = CONN_STATE_CONNECTING;
|
||||
|
||||
conn->conn_handler = connect_handler;
|
||||
aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE,
|
||||
aeCreateFileEvent(conn->el, conn->fd, AE_WRITABLE,
|
||||
conn->type->ae_handler, conn);
|
||||
|
||||
return C_OK;
|
||||
@@ -114,7 +115,7 @@ static void connSocketShutdown(connection *conn) {
|
||||
/* Close the connection and free resources. */
|
||||
static void connSocketClose(connection *conn) {
|
||||
if (conn->fd != -1) {
|
||||
aeDeleteFileEvent(server.el,conn->fd, AE_READABLE | AE_WRITABLE);
|
||||
if (conn->el) aeDeleteFileEvent(conn->el, conn->fd, AE_READABLE | AE_WRITABLE);
|
||||
close(conn->fd);
|
||||
conn->fd = -1;
|
||||
}
|
||||
@@ -190,6 +191,15 @@ static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_hand
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Rebind the connection to another event loop, read/write handlers must not
|
||||
* be installed in the current event loop, otherwise it will cause two event
|
||||
* loops to manage the same connection at the same time. */
|
||||
static int connSocketRebindEventLoop(connection *conn, aeEventLoop *el) {
|
||||
serverAssert(!conn->el && !conn->read_handler && !conn->write_handler);
|
||||
conn->el = el;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Register a write handler, to be called when the connection is writable.
|
||||
* If NULL, the existing handler is removed.
|
||||
*
|
||||
@@ -207,9 +217,9 @@ static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc fu
|
||||
else
|
||||
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
|
||||
if (!conn->write_handler)
|
||||
aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
|
||||
aeDeleteFileEvent(conn->el,conn->fd,AE_WRITABLE);
|
||||
else
|
||||
if (aeCreateFileEvent(server.el,conn->fd,AE_WRITABLE,
|
||||
if (aeCreateFileEvent(conn->el,conn->fd,AE_WRITABLE,
|
||||
conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
|
||||
return C_OK;
|
||||
}
|
||||
@@ -222,9 +232,9 @@ static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc fun
|
||||
|
||||
conn->read_handler = func;
|
||||
if (!conn->read_handler)
|
||||
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(conn->el,conn->fd,AE_READABLE);
|
||||
else
|
||||
if (aeCreateFileEvent(server.el,conn->fd,
|
||||
if (aeCreateFileEvent(conn->el,conn->fd,
|
||||
AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
|
||||
return C_OK;
|
||||
}
|
||||
@@ -250,7 +260,7 @@ static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientD
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
}
|
||||
|
||||
if (!conn->write_handler) aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
|
||||
if (!conn->write_handler) aeDeleteFileEvent(conn->el, conn->fd, AE_WRITABLE);
|
||||
|
||||
if (!callHandler(conn, conn->conn_handler)) return;
|
||||
conn->conn_handler = NULL;
|
||||
@@ -291,7 +301,6 @@ static void connSocketAcceptHandler(aeEventLoop *el, int fd, void *privdata, int
|
||||
int cport, cfd;
|
||||
int max = server.max_new_conns_per_cycle;
|
||||
char cip[NET_IP_STR_LEN];
|
||||
UNUSED(el);
|
||||
UNUSED(mask);
|
||||
UNUSED(privdata);
|
||||
|
||||
@@ -304,7 +313,7 @@ static void connSocketAcceptHandler(aeEventLoop *el, int fd, void *privdata, int
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(connCreateAcceptedSocket(cfd, NULL),0,cip);
|
||||
acceptCommonHandler(connCreateAcceptedSocket(el,cfd,NULL), 0, cip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +406,10 @@ static ConnectionType CT_Socket = {
|
||||
.blocking_connect = connSocketBlockingConnect,
|
||||
.accept = connSocketAccept,
|
||||
|
||||
/* event loop */
|
||||
.unbind_event_loop = NULL,
|
||||
.rebind_event_loop = connSocketRebindEventLoop,
|
||||
|
||||
/* IO */
|
||||
.write = connSocketWrite,
|
||||
.writev = connSocketWritev,
|
||||
|
||||
+8
-4
@@ -7,7 +7,7 @@
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*/
|
||||
|
||||
|
||||
#include "fast_float_strtod.h"
|
||||
#include "server.h"
|
||||
#include "pqsort.h" /* Partial qsort for SORT+LIMIT */
|
||||
#include <math.h> /* isnan() */
|
||||
@@ -241,8 +241,12 @@ void sortCommandGeneric(client *c, int readonly) {
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
|
||||
/* If GET is specified with a real pattern, we can't accept it in cluster mode,
|
||||
* unless we can make sure the keys formed by the pattern are in the same slot
|
||||
* as the key to sort. */
|
||||
if (server.cluster_enabled && patternHashSlot(c->argv[j+1]->ptr, sdslen(c->argv[j+1]->ptr)) != getKeySlot(c->argv[1]->ptr)) {
|
||||
* as the key to sort. The pattern # represents the key itself, so just skip
|
||||
* pattern slot check. */
|
||||
if (server.cluster_enabled &&
|
||||
strcmp(c->argv[j+1]->ptr, "#") &&
|
||||
patternHashSlot(c->argv[j+1]->ptr, sdslen(c->argv[j+1]->ptr)) != getKeySlot(c->argv[1]->ptr))
|
||||
{
|
||||
addReplyError(c, "GET option of SORT denied in Cluster mode when "
|
||||
"keys formed by the pattern may be in different slots.");
|
||||
syntax_error++;
|
||||
@@ -472,7 +476,7 @@ void sortCommandGeneric(client *c, int readonly) {
|
||||
if (sdsEncodedObject(byval)) {
|
||||
char *eptr;
|
||||
|
||||
vector[j].u.score = strtod(byval->ptr,&eptr);
|
||||
vector[j].u.score = fast_float_strtod(byval->ptr,&eptr);
|
||||
if (eptr[0] != '\0' || errno == ERANGE ||
|
||||
isnan(vector[j].u.score))
|
||||
{
|
||||
|
||||
+98
-13
@@ -422,8 +422,13 @@ void listpackExExpire(redisDb *db, robj *o, ExpireInfo *info) {
|
||||
expired++;
|
||||
}
|
||||
|
||||
if (expired)
|
||||
if (expired) {
|
||||
lpt->lp = lpDeleteRange(lpt->lp, 0, expired * 3);
|
||||
|
||||
/* update keysizes */
|
||||
unsigned long l = lpLength(lpt->lp) / 3;
|
||||
updateKeysizesHist(db, getKeySlot(lpt->key), OBJ_HASH, l + expired, l);
|
||||
}
|
||||
|
||||
min = hashTypeGetMinExpire(o, 1 /*accurate*/);
|
||||
info->nextExpireTime = min;
|
||||
@@ -546,6 +551,11 @@ SetExRes hashTypeSetExpiryListpack(HashTypeSetEx *ex, sds field,
|
||||
if (unlikely(checkAlreadyExpired(expireAt))) {
|
||||
propagateHashFieldDeletion(ex->db, ex->key->ptr, field, sdslen(field));
|
||||
hashTypeDelete(ex->hashObj, field, 1);
|
||||
|
||||
/* get listpack length */
|
||||
listpackEx *lpt = ((listpackEx *) ex->hashObj->ptr);
|
||||
unsigned long length = lpLength(lpt->lp) / 3;
|
||||
updateKeysizesHist(ex->db, getKeySlot(ex->key->ptr), OBJ_HASH, length+1, length);
|
||||
server.stat_expired_subkeys++;
|
||||
ex->fieldDeleted++;
|
||||
return HSETEX_DELETED;
|
||||
@@ -734,7 +744,7 @@ GetFieldRes hashTypeGetValue(redisDb *db, robj *o, sds field, unsigned char **vs
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
|
||||
if (expiredAt >= (uint64_t) commandTimeSnapshot())
|
||||
if ((expiredAt >= (uint64_t) commandTimeSnapshot()) || (hfeFlags & HFE_LAZY_ACCESS_EXPIRED))
|
||||
return GETF_OK;
|
||||
|
||||
if (server.masterhost) {
|
||||
@@ -747,7 +757,7 @@ GetFieldRes hashTypeGetValue(redisDb *db, robj *o, sds field, unsigned char **vs
|
||||
}
|
||||
|
||||
if ((server.loading) ||
|
||||
(server.lazy_expire_disabled) ||
|
||||
(server.allow_access_expired) ||
|
||||
(hfeFlags & HFE_LAZY_AVOID_FIELD_DEL) ||
|
||||
(isPausedActionsWithUpdate(PAUSE_ACTION_EXPIRE)))
|
||||
return GETF_EXPIRED;
|
||||
@@ -1042,6 +1052,8 @@ SetExRes hashTypeSetExpiryHT(HashTypeSetEx *exInfo, sds field, uint64_t expireAt
|
||||
/* If expired, then delete the field and propagate the deletion.
|
||||
* If replica, continue like the field is valid */
|
||||
if (unlikely(checkAlreadyExpired(expireAt))) {
|
||||
unsigned long length = dictSize(ht);
|
||||
updateKeysizesHist(exInfo->db, getKeySlot(exInfo->key->ptr), OBJ_HASH, length, length-1);
|
||||
/* replicas should not initiate deletion of fields */
|
||||
propagateHashFieldDeletion(exInfo->db, exInfo->key->ptr, field, sdslen(field));
|
||||
hashTypeDelete(exInfo->hashObj, field, 1);
|
||||
@@ -1134,6 +1146,20 @@ int hashTypeSetExInit(robj *key, robj *o, client *c, redisDb *db, const char *cm
|
||||
dictEntry *de = dbFind(c->db, key->ptr);
|
||||
serverAssert(de != NULL);
|
||||
lpt->key = dictGetKey(de);
|
||||
} else if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
|
||||
listpackEx *lpt = o->ptr;
|
||||
|
||||
/* If the hash previously had HFEs but later no longer does, the key ref
|
||||
* (lpt->key) in the hash might become outdated after a MOVE/COPY/RENAME/RESTORE
|
||||
* operation. These commands maintain the key ref only if HFEs are present.
|
||||
* That is, we can only be sure that key ref is valid as long as it is not
|
||||
* "trash". (TODO: dbFind() can be avoided. Instead need to extend the
|
||||
* lookupKey*() to return dictEntry). */
|
||||
if (lpt->meta.trash) {
|
||||
dictEntry *de = dbFind(c->db, key->ptr);
|
||||
serverAssert(de != NULL);
|
||||
lpt->key = dictGetKey(de);
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
/* Take care dict has HFE metadata */
|
||||
if (!isDictWithMetaHFE(ht)) {
|
||||
@@ -1151,6 +1177,18 @@ int hashTypeSetExInit(robj *key, robj *o, client *c, redisDb *db, const char *cm
|
||||
m->key = dictGetKey(de); /* reference key in keyspace */
|
||||
m->hfe = ebCreate(); /* Allocate HFE DS */
|
||||
m->expireMeta.trash = 1; /* mark as trash (as long it wasn't ebAdd()) */
|
||||
} else {
|
||||
dictExpireMetadata *m = (dictExpireMetadata *) dictMetadata(ht);
|
||||
/* If the hash previously had HFEs but later no longer does, the key ref
|
||||
* (m->key) in the hash might become outdated after a MOVE/COPY/RENAME/RESTORE
|
||||
* operation. These commands maintain the key ref only if HFEs are present.
|
||||
* That is, we can only be sure that key ref is valid as long as it is not
|
||||
* "trash". */
|
||||
if (m->expireMeta.trash) {
|
||||
dictEntry *de = dbFind(db, key->ptr);
|
||||
serverAssert(de != NULL);
|
||||
m->key = dictGetKey(de); /* reference key in keyspace */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1903,7 +1941,7 @@ static int hashTypeExpireIfNeeded(redisDb *db, robj *o) {
|
||||
|
||||
/* Follow expireIfNeeded() conditions of when not lazy-expire */
|
||||
if ( (server.loading) ||
|
||||
(server.lazy_expire_disabled) ||
|
||||
(server.allow_access_expired) ||
|
||||
(server.masterhost) || /* master-client or user-client, don't delete */
|
||||
(isPausedActionsWithUpdate(PAUSE_ACTION_EXPIRE)))
|
||||
return 0;
|
||||
@@ -1985,6 +2023,21 @@ uint64_t hashTypeRemoveFromExpires(ebuckets *hexpires, robj *o) {
|
||||
return expireTime;
|
||||
}
|
||||
|
||||
int hashTypeIsFieldsWithExpire(robj *o) {
|
||||
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
return 0;
|
||||
} else if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
|
||||
return EB_EXPIRE_TIME_INVALID != listpackExGetMinExpire(o);
|
||||
} else { /* o->encoding == OBJ_ENCODING_HT */
|
||||
dict *d = o->ptr;
|
||||
/* If dict doesn't holds HFE metadata */
|
||||
if (!isDictWithMetaHFE(d))
|
||||
return 0;
|
||||
dictExpireMetadata *meta = (dictExpireMetadata *) dictMetadata(d);
|
||||
return ebGetTotalItems(meta->hfe, &hashFieldExpireBucketsType) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add hash to global HFE DS and update key for notifications.
|
||||
*
|
||||
* key - must be the same key instance that is persisted in db->dict
|
||||
@@ -2091,6 +2144,7 @@ ebuckets *hashTypeGetDictMetaHFE(dict *d) {
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void hsetnxCommand(client *c) {
|
||||
unsigned long hlen;
|
||||
int isHashDeleted;
|
||||
robj *o;
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
@@ -2111,6 +2165,8 @@ void hsetnxCommand(client *c) {
|
||||
addReply(c, shared.cone);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
hlen = hashTypeLength(o, 0);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, hlen - 1, hlen);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -2139,6 +2195,8 @@ void hsetCommand(client *c) {
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
unsigned long l = hashTypeLength(o, 0);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, l - created, l);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty += (c->argc - 2)/2;
|
||||
}
|
||||
@@ -2164,11 +2222,14 @@ void hincrbyCommand(client *c) {
|
||||
} /* Else hashTypeGetValue() already stored it into &value */
|
||||
} else if ((res == GETF_NOT_FOUND) || (res == GETF_EXPIRED)) {
|
||||
value = 0;
|
||||
unsigned long l = hashTypeLength(o, 0);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, l, l + 1);
|
||||
} else {
|
||||
/* Field expired and in turn hash deleted. Create new one! */
|
||||
o = createHashObject();
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
value = 0;
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, 0, 1);
|
||||
}
|
||||
|
||||
oldvalue = value;
|
||||
@@ -2213,11 +2274,14 @@ void hincrbyfloatCommand(client *c) {
|
||||
}
|
||||
} else if ((res == GETF_NOT_FOUND) || (res == GETF_EXPIRED)) {
|
||||
value = 0;
|
||||
unsigned long l = hashTypeLength(o, 0);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, l, l + 1);
|
||||
} else {
|
||||
/* Field expired and in turn hash deleted. Create new one! */
|
||||
o = createHashObject();
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
value = 0;
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, 0, 1);
|
||||
}
|
||||
|
||||
value += incr;
|
||||
@@ -2315,6 +2379,16 @@ void hdelCommand(client *c) {
|
||||
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
unsigned long oldLen = hashTypeLength(o, 0);
|
||||
|
||||
/* Hash field expiration is optimized to avoid frequent update global HFE DS for
|
||||
* each field deletion. Eventually active-expiration will run and update or remove
|
||||
* the hash from global HFE DS gracefully. Nevertheless, statistic "subexpiry"
|
||||
* might reflect wrong number of hashes with HFE to the user if it is the last
|
||||
* field with expiration. The following logic checks if this is indeed the last
|
||||
* field with expiration and removes it from global HFE DS. */
|
||||
int isHFE = hashTypeIsFieldsWithExpire(o);
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (hashTypeDelete(o,c->argv[j]->ptr,1)) {
|
||||
deleted++;
|
||||
@@ -2326,11 +2400,17 @@ void hdelCommand(client *c) {
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_HASH, oldLen, oldLen - deleted);
|
||||
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
|
||||
c->db->id);
|
||||
if (keyremoved) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", c->argv[1], c->db->id);
|
||||
} else {
|
||||
if (isHFE && (hashTypeIsFieldsWithExpire(o) == 0)) /* is it last HFE */
|
||||
ebRemove(&c->db->hexpires, &hashExpireBucketsType, o);
|
||||
}
|
||||
|
||||
server.dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
@@ -2401,7 +2481,11 @@ void genericHgetallCommand(client *c, int flags) {
|
||||
|
||||
/* We return a map if the user requested keys and values, like in the
|
||||
* HGETALL case. Otherwise to use a flat array makes more sense. */
|
||||
length = hashTypeLength(o, 1 /*subtractExpiredFields*/);
|
||||
if ((length = hashTypeLength(o, 1 /*subtractExpiredFields*/)) == 0) {
|
||||
addReply(c, emptyResp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) {
|
||||
addReplyMapLen(c, length);
|
||||
} else {
|
||||
@@ -2410,11 +2494,7 @@ void genericHgetallCommand(client *c, int flags) {
|
||||
|
||||
hi = hashTypeInitIterator(o);
|
||||
|
||||
/* Skip expired fields if the hash has an expire time set at global HFE DS. We could
|
||||
* set it to constant 1, but then it will make another lookup for each field expiration */
|
||||
int skipExpiredFields = (EB_EXPIRE_TIME_INVALID == hashTypeGetMinExpire(o, 0)) ? 0 : 1;
|
||||
|
||||
while (hashTypeNext(hi, skipExpiredFields) != C_ERR) {
|
||||
while (hashTypeNext(hi, 1 /*skipExpiredFields*/) != C_ERR) {
|
||||
if (flags & OBJ_HASH_KEY) {
|
||||
addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
|
||||
count++;
|
||||
@@ -2890,6 +2970,11 @@ static ExpireAction onFieldExpire(eItem item, void *ctx) {
|
||||
dict *d = expCtx->hashObj->ptr;
|
||||
dictExpireMetadata *dictExpireMeta = (dictExpireMetadata *) dictMetadata(d);
|
||||
propagateHashFieldDeletion(expCtx->db, dictExpireMeta->key, hf, hfieldlen(hf));
|
||||
|
||||
/* update keysizes */
|
||||
unsigned long l = hashTypeLength(expCtx->hashObj, 0);
|
||||
updateKeysizesHist(expCtx->db, getKeySlot(dictExpireMeta->key), OBJ_HASH, l, l - 1);
|
||||
|
||||
serverAssert(hashTypeDelete(expCtx->hashObj, hf, 0) == 1);
|
||||
server.stat_expired_subkeys++;
|
||||
return ACT_REMOVE_EXP_ITEM;
|
||||
|
||||
+31
-12
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "util.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* List API
|
||||
@@ -383,12 +384,12 @@ int listTypeReplaceAtIndex(robj *o, int index, robj *value) {
|
||||
}
|
||||
|
||||
/* Compare the given object with the entry at the current position. */
|
||||
int listTypeEqual(listTypeEntry *entry, robj *o) {
|
||||
int listTypeEqual(listTypeEntry *entry, robj *o, size_t object_len) {
|
||||
serverAssertWithInfo(NULL,o,sdsEncodedObject(o));
|
||||
if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
return quicklistCompare(&entry->entry,o->ptr,sdslen(o->ptr));
|
||||
return quicklistCompare(&entry->entry,o->ptr,object_len);
|
||||
} else if (entry->li->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
return lpCompare(entry->lpe,o->ptr,sdslen(o->ptr));
|
||||
return lpCompare(entry->lpe,o->ptr,object_len);
|
||||
} else {
|
||||
serverPanic("Unknown list encoding");
|
||||
}
|
||||
@@ -462,6 +463,7 @@ void listTypeDelRange(robj *subject, long start, long count) {
|
||||
/* Implements LPUSH/RPUSH/LPUSHX/RPUSHX.
|
||||
* 'xx': push if key exists. */
|
||||
void pushGenericCommand(client *c, int where, int xx) {
|
||||
unsigned long llen;
|
||||
int j;
|
||||
|
||||
robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
|
||||
@@ -482,11 +484,13 @@ void pushGenericCommand(client *c, int where, int xx) {
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
addReplyLongLong(c, listTypeLength(lobj));
|
||||
llen = listTypeLength(lobj);
|
||||
addReplyLongLong(c, llen);
|
||||
|
||||
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, llen - (c->argc - 2), llen);
|
||||
}
|
||||
|
||||
/* LPUSH <key> <element> [<element> ...] */
|
||||
@@ -538,8 +542,9 @@ void linsertCommand(client *c) {
|
||||
|
||||
/* Seek pivot from head to tail */
|
||||
iter = listTypeInitIterator(subject,0,LIST_TAIL);
|
||||
const size_t object_len = sdslen(c->argv[3]->ptr);
|
||||
while (listTypeNext(iter,&entry)) {
|
||||
if (listTypeEqual(&entry,c->argv[3])) {
|
||||
if (listTypeEqual(&entry,c->argv[3],object_len)) {
|
||||
listTypeInsert(&entry,c->argv[4],where);
|
||||
inserted = 1;
|
||||
break;
|
||||
@@ -552,6 +557,8 @@ void linsertCommand(client *c) {
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"linsert",
|
||||
c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
unsigned long ll = listTypeLength(subject);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, ll-1, ll);
|
||||
} else {
|
||||
/* Notify client of a failed insert */
|
||||
addReplyLongLong(c,-1);
|
||||
@@ -735,9 +742,11 @@ void addListRangeReply(client *c, robj *o, long start, long end, int reverse) {
|
||||
* if the key got deleted by this function. */
|
||||
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int signal, int *deleted) {
|
||||
char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
|
||||
|
||||
unsigned long llen = listTypeLength(o);
|
||||
|
||||
notifyKeyspaceEvent(NOTIFY_LIST, event, key, c->db->id);
|
||||
if (listTypeLength(o) == 0) {
|
||||
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_LIST, llen + count, llen);
|
||||
if (llen == 0) {
|
||||
if (deleted) *deleted = 1;
|
||||
|
||||
dbDelete(c->db, key);
|
||||
@@ -869,7 +878,7 @@ void lrangeCommand(client *c) {
|
||||
/* LTRIM <key> <start> <stop> */
|
||||
void ltrimCommand(client *c) {
|
||||
robj *o;
|
||||
long start, end, llen, ltrim, rtrim;
|
||||
long start, end, llen, ltrim, rtrim, llenNew;;
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
|
||||
@@ -907,12 +916,13 @@ void ltrimCommand(client *c) {
|
||||
}
|
||||
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"ltrim",c->argv[1],c->db->id);
|
||||
if (listTypeLength(o) == 0) {
|
||||
if ((llenNew = listTypeLength(o)) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
} else {
|
||||
listTypeTryConversion(o,LIST_CONV_SHRINKING,NULL,NULL);
|
||||
}
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, llen, llenNew);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
server.dirty += (ltrim + rtrim);
|
||||
addReply(c,shared.ok);
|
||||
@@ -999,8 +1009,9 @@ void lposCommand(client *c) {
|
||||
listTypeEntry entry;
|
||||
long llen = listTypeLength(o);
|
||||
long index = 0, matches = 0, matchindex = -1, arraylen = 0;
|
||||
const size_t ele_len = sdslen(ele->ptr);
|
||||
while (listTypeNext(li,&entry) && (maxlen == 0 || index < maxlen)) {
|
||||
if (listTypeEqual(&entry,ele)) {
|
||||
if (listTypeEqual(&entry,ele,ele_len)) {
|
||||
matches++;
|
||||
matchindex = (direction == LIST_TAIL) ? index : llen - index - 1;
|
||||
if (matches >= rank) {
|
||||
@@ -1052,8 +1063,9 @@ void lremCommand(client *c) {
|
||||
}
|
||||
|
||||
listTypeEntry entry;
|
||||
const size_t object_len = sdslen(c->argv[3]->ptr);
|
||||
while (listTypeNext(li,&entry)) {
|
||||
if (listTypeEqual(&entry,obj)) {
|
||||
if (listTypeEqual(&entry,obj,object_len)) {
|
||||
listTypeDelete(li, &entry);
|
||||
server.dirty++;
|
||||
removed++;
|
||||
@@ -1063,8 +1075,11 @@ void lremCommand(client *c) {
|
||||
listTypeReleaseIterator(li);
|
||||
|
||||
if (removed) {
|
||||
long ll = listTypeLength(subject);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, ll + removed, ll);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
|
||||
if (listTypeLength(subject) == 0) {
|
||||
|
||||
if (ll == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
} else {
|
||||
@@ -1086,6 +1101,10 @@ void lmoveHandlePush(client *c, robj *dstkey, robj *dstobj, robj *value,
|
||||
listTypeTryConversionAppend(dstobj,&value,0,0,NULL,NULL);
|
||||
listTypePush(dstobj,value,where);
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
|
||||
long ll = listTypeLength(dstobj);
|
||||
updateKeysizesHist(c->db, getKeySlot(dstkey->ptr), OBJ_LIST, ll - 1, ll);
|
||||
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,
|
||||
where == LIST_HEAD ? "lpush" : "rpush",
|
||||
dstkey,
|
||||
|
||||
+80
-5
@@ -2,8 +2,13 @@
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2024-present, Valkey contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*
|
||||
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -598,6 +603,8 @@ void saddCommand(client *c) {
|
||||
if (setTypeAdd(set,c->argv[j]->ptr)) added++;
|
||||
}
|
||||
if (added) {
|
||||
unsigned long size = setTypeSize(set);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, size - added, size);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
|
||||
}
|
||||
@@ -612,6 +619,8 @@ void sremCommand(client *c) {
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
unsigned long oldSize = setTypeSize(set);
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (setTypeRemove(set,c->argv[j]->ptr)) {
|
||||
deleted++;
|
||||
@@ -623,6 +632,8 @@ void sremCommand(client *c) {
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, oldSize, oldSize - deleted);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"srem",c->argv[1],c->db->id);
|
||||
if (keyremoved)
|
||||
@@ -664,8 +675,12 @@ void smoveCommand(client *c) {
|
||||
}
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"srem",c->argv[1],c->db->id);
|
||||
|
||||
/* Update keysizes histogram */
|
||||
unsigned long srcLen = setTypeSize(srcset);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, srcLen + 1, srcLen);
|
||||
|
||||
/* Remove the src set from the database when empty */
|
||||
if (setTypeSize(srcset) == 0) {
|
||||
if (srcLen == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
@@ -681,6 +696,8 @@ void smoveCommand(client *c) {
|
||||
|
||||
/* An extra key has changed when ele was successfully added to dstset */
|
||||
if (setTypeAdd(dstset,ele->ptr)) {
|
||||
unsigned long dstLen = setTypeSize(dstset);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[2]->ptr), OBJ_SET, dstLen - 1, dstLen);
|
||||
server.dirty++;
|
||||
signalModifiedKey(c,c->db,c->argv[2]);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[2],c->db->id);
|
||||
@@ -738,7 +755,7 @@ void scardCommand(client *c) {
|
||||
|
||||
void spopWithCountCommand(client *c) {
|
||||
long l;
|
||||
unsigned long count, size;
|
||||
unsigned long count, size, toRemove;
|
||||
robj *set;
|
||||
|
||||
/* Get the count argument */
|
||||
@@ -758,10 +775,12 @@ void spopWithCountCommand(client *c) {
|
||||
}
|
||||
|
||||
size = setTypeSize(set);
|
||||
toRemove = (count >= size) ? size : count;
|
||||
|
||||
/* Generate an SPOP keyspace notification */
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"spop",c->argv[1],c->db->id);
|
||||
server.dirty += (count >= size) ? size : count;
|
||||
server.dirty += toRemove;
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, size, size - toRemove);
|
||||
|
||||
/* CASE 1:
|
||||
* The number of requested elements is greater than or equal to
|
||||
@@ -944,6 +963,7 @@ void spopWithCountCommand(client *c) {
|
||||
}
|
||||
|
||||
void spopCommand(client *c) {
|
||||
unsigned long size;
|
||||
robj *set, *ele;
|
||||
|
||||
if (c->argc == 3) {
|
||||
@@ -959,6 +979,9 @@ void spopCommand(client *c) {
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
size = setTypeSize(set);
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_SET, size, size-1);
|
||||
|
||||
/* Pop a random element from the set */
|
||||
ele = setTypePopRandom(set);
|
||||
|
||||
@@ -1244,7 +1267,7 @@ int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SINTER / SMEMBERS / SINTERSTORE / SINTERCARD
|
||||
/* SINTER / SINTERSTORE / SINTERCARD
|
||||
*
|
||||
* 'cardinality_only' work for SINTERCARD, only return the cardinality
|
||||
* with minimum processing and memory overheads.
|
||||
@@ -1420,6 +1443,36 @@ void sinterCommand(client *c) {
|
||||
sinterGenericCommand(c, c->argv+1, c->argc-1, NULL, 0, 0);
|
||||
}
|
||||
|
||||
/* SMEMBERS key */
|
||||
void smembersCommand(client *c) {
|
||||
setTypeIterator *si;
|
||||
char *str;
|
||||
size_t len;
|
||||
int64_t intobj;
|
||||
robj *setobj = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (checkType(c,setobj,OBJ_SET)) return;
|
||||
if (!setobj) {
|
||||
addReply(c, shared.emptyset[c->resp]);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Prepare the response. */
|
||||
unsigned long length = setTypeSize(setobj);
|
||||
addReplySetLen(c,length);
|
||||
/* Iterate through the elements of the set. */
|
||||
si = setTypeInitIterator(setobj);
|
||||
|
||||
while (setTypeNext(si, &str, &len, &intobj) != -1) {
|
||||
if (str != NULL)
|
||||
addReplyBulkCBuffer(c, str, len);
|
||||
else
|
||||
addReplyBulkLongLong(c, intobj);
|
||||
length--;
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
serverAssert(length == 0); /* fail on corrupt data */
|
||||
}
|
||||
|
||||
/* SINTERCARD numkeys key [key ...] [LIMIT limit] */
|
||||
void sinterCardCommand(client *c) {
|
||||
long j;
|
||||
@@ -1462,6 +1515,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
robj **sets = zmalloc(sizeof(robj*)*setnum);
|
||||
setTypeIterator *si;
|
||||
robj *dstset = NULL;
|
||||
int dstset_encoding = OBJ_ENCODING_INTSET;
|
||||
char *str;
|
||||
size_t len;
|
||||
int64_t llval;
|
||||
@@ -1480,6 +1534,23 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
zfree(sets);
|
||||
return;
|
||||
}
|
||||
/* For a SET's encoding, according to the factory method setTypeCreate(), currently have 3 types:
|
||||
* 1. OBJ_ENCODING_INTSET
|
||||
* 2. OBJ_ENCODING_LISTPACK
|
||||
* 3. OBJ_ENCODING_HT
|
||||
* 'dstset_encoding' is used to determine which kind of encoding to use when initialize 'dstset'.
|
||||
*
|
||||
* If all sets are all OBJ_ENCODING_INTSET encoding or 'dstkey' is not null, keep 'dstset'
|
||||
* OBJ_ENCODING_INTSET encoding when initialize. Otherwise it is not efficient to create the 'dstset'
|
||||
* from intset and then convert to listpack or hashtable.
|
||||
*
|
||||
* If one of the set is OBJ_ENCODING_LISTPACK, let's set 'dstset' to hashtable default encoding,
|
||||
* the hashtable is more efficient when find and compare than the listpack. The corresponding
|
||||
* time complexity are O(1) vs O(n). */
|
||||
if (!dstkey && dstset_encoding == OBJ_ENCODING_INTSET &&
|
||||
(setobj->encoding == OBJ_ENCODING_LISTPACK || setobj->encoding == OBJ_ENCODING_HT)) {
|
||||
dstset_encoding = OBJ_ENCODING_HT;
|
||||
}
|
||||
sets[j] = setobj;
|
||||
if (j > 0 && sets[0] == sets[j]) {
|
||||
sameset = 1;
|
||||
@@ -1522,7 +1593,11 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
/* We need a temp set object to store our union/diff. If the dstkey
|
||||
* is not NULL (that is, we are inside an SUNIONSTORE/SDIFFSTORE operation) then
|
||||
* this set object will be the resulting object to set into the target key*/
|
||||
dstset = createIntsetObject();
|
||||
if (dstset_encoding == OBJ_ENCODING_INTSET) {
|
||||
dstset = createIntsetObject();
|
||||
} else {
|
||||
dstset = createSetObject();
|
||||
}
|
||||
|
||||
if (op == SET_OP_UNION) {
|
||||
/* Union is trivial, just add every element of every set to the
|
||||
|
||||
+21
-11
@@ -1405,11 +1405,6 @@ int streamRangeHasTombstones(stream *s, streamID *start, streamID *end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (streamCompareID(&s->first_id,&s->max_deleted_entry_id) > 0) {
|
||||
/* The latest tombstone is before the first entry. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (start) {
|
||||
start_id = *start;
|
||||
} else {
|
||||
@@ -1446,6 +1441,17 @@ void streamReplyWithCGLag(client *c, stream *s, streamCG *cg) {
|
||||
/* The lag of a newly-initialized stream is 0. */
|
||||
lag = 0;
|
||||
valid = 1;
|
||||
} else if (!s->length) { /* All entries deleted, now empty. */
|
||||
lag = 0;
|
||||
valid = 1;
|
||||
} else if (streamCompareID(&cg->last_id,&s->first_id) < 0 &&
|
||||
streamCompareID(&s->max_deleted_entry_id,&s->first_id) < 0)
|
||||
{
|
||||
/* When both the consumer group's last_id and the maximum tombstone are behind
|
||||
* the stream's first entry, the consumer group's lag will always be equal to
|
||||
* the number of remainin entries in the stream. */
|
||||
lag = s->length;
|
||||
valid = 1;
|
||||
} else if (cg->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&cg->last_id,NULL)) {
|
||||
/* No fragmentation ahead means that the group's logical reads counter
|
||||
* is valid for performing the lag calculation. */
|
||||
@@ -1502,6 +1508,10 @@ long long streamEstimateDistanceFromFirstEverEntry(stream *s, streamID *id) {
|
||||
return s->entries_added;
|
||||
}
|
||||
|
||||
/* There are fragmentations between the `id` and the stream's last-generated-id. */
|
||||
if (!streamIDEqZero(id) && streamCompareID(id,&s->max_deleted_entry_id) < 0)
|
||||
return SCG_INVALID_ENTRIES_READ;
|
||||
|
||||
int cmp_last = streamCompareID(id,&s->last_id);
|
||||
if (cmp_last == 0) {
|
||||
/* Return the exact counter of the last entry in the stream. */
|
||||
@@ -1684,10 +1694,10 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
/* Update the group last_id if needed. */
|
||||
if (group && streamCompareID(&id,&group->last_id) > 0) {
|
||||
if (group->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&id,NULL)) {
|
||||
/* A valid counter and no future tombstones mean we can
|
||||
* increment the read counter to keep tracking the group's
|
||||
* progress. */
|
||||
if (group->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&group->last_id,NULL)) {
|
||||
/* A valid counter and no tombstones between the group's last-delivered-id
|
||||
* and the stream's last-generated-id mean we can increment the read counter
|
||||
* to keep tracking the group's progress. */
|
||||
group->entries_read++;
|
||||
} else if (s->entries_added) {
|
||||
/* The group's counter may be invalid, so we try to obtain it. */
|
||||
@@ -2190,9 +2200,9 @@ void xreadCommand(client *c) {
|
||||
streams_arg = i+1;
|
||||
streams_count = (c->argc-streams_arg);
|
||||
if ((streams_count % 2) != 0) {
|
||||
char symbol = xreadgroup ? '>' : '$';
|
||||
const char *symbol = xreadgroup ? "ID or '>'" : "ID, '+', or '$'";
|
||||
addReplyErrorFormat(c,"Unbalanced '%s' list of streams: "
|
||||
"for each stream key an ID or '%c' must be "
|
||||
"for each stream key an %s must be "
|
||||
"specified.", c->cmd->fullname,symbol);
|
||||
return;
|
||||
}
|
||||
|
||||
+44
-44
@@ -73,7 +73,8 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
if (getGenericCommand(c) == C_ERR) return;
|
||||
}
|
||||
|
||||
found = (lookupKeyWrite(c->db,key) != NULL);
|
||||
dictEntry *de = NULL;
|
||||
found = (lookupKeyWriteWithDictEntry(c->db,key,&de) != NULL);
|
||||
|
||||
if ((flags & OBJ_SET_NX && found) ||
|
||||
(flags & OBJ_SET_XX && !found))
|
||||
@@ -88,12 +89,12 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
setkey_flags |= ((flags & OBJ_KEEPTTL) || expire) ? SETKEY_KEEPTTL : 0;
|
||||
setkey_flags |= found ? SETKEY_ALREADY_EXIST : SETKEY_DOESNT_EXIST;
|
||||
|
||||
setKey(c,c->db,key,val,setkey_flags);
|
||||
setKeyWithDictEntry(c,c->db,key,val,setkey_flags,de);
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
|
||||
|
||||
if (expire) {
|
||||
setExpire(c,c->db,key,milliseconds);
|
||||
setExpireWithDictEntry(c,c->db,key,milliseconds,de);
|
||||
/* Propagate as SET Key Value PXAT millisecond-timestamp if there is
|
||||
* EX/PX/EXAT flag. */
|
||||
if (!(flags & OBJ_PXAT)) {
|
||||
@@ -419,9 +420,11 @@ void getsetCommand(client *c) {
|
||||
}
|
||||
|
||||
void setrangeCommand(client *c) {
|
||||
size_t oldLen = 0, newLen;
|
||||
robj *o;
|
||||
long offset;
|
||||
sds value = c->argv[3]->ptr;
|
||||
const size_t value_len = sdslen(value);
|
||||
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK)
|
||||
return;
|
||||
@@ -431,51 +434,53 @@ void setrangeCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
dictEntry *de;
|
||||
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
|
||||
if (o == NULL) {
|
||||
/* Return 0 when setting nothing on a non-existing string */
|
||||
if (sdslen(value) == 0) {
|
||||
if (value_len == 0) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return when the resulting string exceeds allowed size */
|
||||
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
|
||||
if (checkStringLength(c,offset,value_len) != C_OK)
|
||||
return;
|
||||
|
||||
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
|
||||
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+value_len));
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
} else {
|
||||
size_t olen;
|
||||
|
||||
/* Key exists, check type */
|
||||
if (checkType(c,o,OBJ_STRING))
|
||||
return;
|
||||
|
||||
/* Return existing string length when setting nothing */
|
||||
olen = stringObjectLen(o);
|
||||
if (sdslen(value) == 0) {
|
||||
addReplyLongLong(c,olen);
|
||||
oldLen = stringObjectLen(o);
|
||||
if (value_len == 0) {
|
||||
addReplyLongLong(c, oldLen);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return when the resulting string exceeds allowed size */
|
||||
if (checkStringLength(c,offset,sdslen(value)) != C_OK)
|
||||
if (checkStringLength(c,offset,value_len) != C_OK)
|
||||
return;
|
||||
|
||||
/* Create a copy when the object is shared or encoded. */
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de);
|
||||
}
|
||||
|
||||
if (sdslen(value) > 0) {
|
||||
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
|
||||
memcpy((char*)o->ptr+offset,value,sdslen(value));
|
||||
if (value_len > 0) {
|
||||
o->ptr = sdsgrowzero(o->ptr,offset+value_len);
|
||||
memcpy((char*)o->ptr+offset,value,value_len);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,
|
||||
"setrange",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
addReplyLongLong(c,sdslen(o->ptr));
|
||||
|
||||
newLen = sdslen(o->ptr);
|
||||
updateKeysizesHist(c->db,getKeySlot(c->argv[1]->ptr),OBJ_STRING,oldLen,newLen);
|
||||
addReplyLongLong(c,newLen);
|
||||
}
|
||||
|
||||
void getrangeCommand(client *c) {
|
||||
@@ -499,24 +504,15 @@ void getrangeCommand(client *c) {
|
||||
strlen = sdslen(str);
|
||||
}
|
||||
|
||||
/* Convert negative indexes */
|
||||
if (start < 0 && end < 0 && start > end) {
|
||||
if (start < 0) start += strlen;
|
||||
if (end < 0) end += strlen;
|
||||
if (strlen == 0 || start >= (long long)strlen || end < 0 || start > end) {
|
||||
addReply(c,shared.emptybulk);
|
||||
return;
|
||||
}
|
||||
if (start < 0) start = strlen+start;
|
||||
if (end < 0) end = strlen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
if ((unsigned long long)end >= strlen) end = strlen-1;
|
||||
|
||||
/* Precondition: end >= 0 && end < strlen, so the only condition where
|
||||
* nothing can be returned is: start > end. */
|
||||
if (start > end || strlen == 0) {
|
||||
addReply(c,shared.emptybulk);
|
||||
} else {
|
||||
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
|
||||
}
|
||||
if (end >= (long long)strlen) end = strlen-1;
|
||||
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
|
||||
}
|
||||
|
||||
void mgetCommand(client *c) {
|
||||
@@ -580,8 +576,8 @@ void msetnxCommand(client *c) {
|
||||
void incrDecrCommand(client *c, long long incr) {
|
||||
long long value, oldvalue;
|
||||
robj *o, *new;
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
dictEntry *de;
|
||||
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
|
||||
if (checkType(c,o,OBJ_STRING)) return;
|
||||
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
|
||||
|
||||
@@ -602,7 +598,7 @@ void incrDecrCommand(client *c, long long incr) {
|
||||
} else {
|
||||
new = createStringObjectFromLongLongForValue(value);
|
||||
if (o) {
|
||||
dbReplaceValue(c->db,c->argv[1],new);
|
||||
dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de);
|
||||
} else {
|
||||
dbAdd(c->db,c->argv[1],new);
|
||||
}
|
||||
@@ -610,7 +606,7 @@ void incrDecrCommand(client *c, long long incr) {
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
addReplyLongLong(c, value);
|
||||
addReplyLongLongFromStr(c,new);
|
||||
}
|
||||
|
||||
void incrCommand(client *c) {
|
||||
@@ -644,7 +640,8 @@ void incrbyfloatCommand(client *c) {
|
||||
long double incr, value;
|
||||
robj *o, *new;
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
dictEntry *de;
|
||||
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
|
||||
if (checkType(c,o,OBJ_STRING)) return;
|
||||
if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK ||
|
||||
getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK)
|
||||
@@ -657,7 +654,7 @@ void incrbyfloatCommand(client *c) {
|
||||
}
|
||||
new = createStringObjectFromLongDouble(value,1);
|
||||
if (o)
|
||||
dbReplaceValue(c->db,c->argv[1],new);
|
||||
dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de);
|
||||
else
|
||||
dbAdd(c->db,c->argv[1],new);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
@@ -674,16 +671,17 @@ void incrbyfloatCommand(client *c) {
|
||||
}
|
||||
|
||||
void appendCommand(client *c) {
|
||||
size_t totlen;
|
||||
size_t totlen, append_len;
|
||||
robj *o, *append;
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
dictEntry *de;
|
||||
o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de);
|
||||
if (o == NULL) {
|
||||
/* Create the key */
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
dbAdd(c->db,c->argv[1],c->argv[2]);
|
||||
incrRefCount(c->argv[2]);
|
||||
totlen = stringObjectLen(c->argv[2]);
|
||||
append_len = totlen = stringObjectLen(c->argv[2]);
|
||||
} else {
|
||||
/* Key exists, check type */
|
||||
if (checkType(c,o,OBJ_STRING))
|
||||
@@ -691,17 +689,19 @@ void appendCommand(client *c) {
|
||||
|
||||
/* "append" is an argument, so always an sds */
|
||||
append = c->argv[2];
|
||||
if (checkStringLength(c,stringObjectLen(o),sdslen(append->ptr)) != C_OK)
|
||||
append_len = sdslen(append->ptr);
|
||||
if (checkStringLength(c,stringObjectLen(o),append_len) != C_OK)
|
||||
return;
|
||||
|
||||
/* Append the value */
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
|
||||
o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de);
|
||||
o->ptr = sdscatlen(o->ptr,append->ptr,append_len);
|
||||
totlen = sdslen(o->ptr);
|
||||
}
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
updateKeysizesHist(c->db,getKeySlot(c->argv[1]->ptr),OBJ_STRING, totlen - append_len, totlen);
|
||||
addReplyLongLong(c,totlen);
|
||||
}
|
||||
|
||||
|
||||
+38
-56
@@ -1,31 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2009-current, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
/* t_zset.c -- zset data type implementation.
|
||||
*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* Copyright (c) 2024-present, Valkey contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
*/
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -55,7 +39,7 @@
|
||||
* c) there is a back pointer, so it's a doubly linked list with the back
|
||||
* pointers being only at "level 1". This allows to traverse the list
|
||||
* from tail to head, useful for ZREVRANGE. */
|
||||
|
||||
#include "fast_float_strtod.h"
|
||||
#include "server.h"
|
||||
#include "intset.h" /* Compact integer set structure */
|
||||
#include <math.h>
|
||||
@@ -566,11 +550,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
|
||||
spec->min = (long)min->ptr;
|
||||
} else {
|
||||
if (((char*)min->ptr)[0] == '(') {
|
||||
spec->min = strtod((char*)min->ptr+1,&eptr);
|
||||
spec->min = fast_float_strtod((char*)min->ptr+1,&eptr);
|
||||
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
|
||||
spec->minex = 1;
|
||||
} else {
|
||||
spec->min = strtod((char*)min->ptr,&eptr);
|
||||
spec->min = fast_float_strtod((char*)min->ptr,&eptr);
|
||||
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
|
||||
}
|
||||
}
|
||||
@@ -578,11 +562,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
|
||||
spec->max = (long)max->ptr;
|
||||
} else {
|
||||
if (((char*)max->ptr)[0] == '(') {
|
||||
spec->max = strtod((char*)max->ptr+1,&eptr);
|
||||
spec->max = fast_float_strtod((char*)max->ptr+1,&eptr);
|
||||
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
|
||||
spec->maxex = 1;
|
||||
} else {
|
||||
spec->max = strtod((char*)max->ptr,&eptr);
|
||||
spec->max = fast_float_strtod((char*)max->ptr,&eptr);
|
||||
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
|
||||
}
|
||||
}
|
||||
@@ -789,7 +773,7 @@ double zzlStrtod(unsigned char *vstr, unsigned int vlen) {
|
||||
vlen = sizeof(buf) - 1;
|
||||
memcpy(buf,vstr,vlen);
|
||||
buf[vlen] = '\0';
|
||||
return strtod(buf,NULL);
|
||||
return fast_float_strtod(buf,NULL);
|
||||
}
|
||||
|
||||
double zzlGetScore(unsigned char *sptr) {
|
||||
@@ -1859,6 +1843,7 @@ void zaddGenericCommand(client *c, int flags) {
|
||||
zsetTypeMaybeConvert(zobj, elements);
|
||||
}
|
||||
|
||||
unsigned long llen = zsetLength(zobj);
|
||||
for (j = 0; j < elements; j++) {
|
||||
double newscore;
|
||||
score = scores[j];
|
||||
@@ -1876,6 +1861,7 @@ void zaddGenericCommand(client *c, int flags) {
|
||||
score = newscore;
|
||||
}
|
||||
server.dirty += (added+updated);
|
||||
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_ZSET, llen, llen+added);
|
||||
|
||||
reply_to_client:
|
||||
if (incr) { /* ZINCRBY or INCR option. */
|
||||
@@ -1923,8 +1909,13 @@ void zremCommand(client *c) {
|
||||
|
||||
if (deleted) {
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"zrem",key,c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
if (keyremoved) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, c->db->id);
|
||||
/* No need updateKeysizesHist(). dbDelete() done it already. */
|
||||
} else {
|
||||
unsigned long len = zsetLength(zobj);
|
||||
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_ZSET, len + deleted, len);
|
||||
}
|
||||
signalModifiedKey(c,c->db,key);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
@@ -2039,8 +2030,13 @@ void zremrangeGenericCommand(client *c, zrange_type rangetype) {
|
||||
if (deleted) {
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,notify_type,key,c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
if (keyremoved) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, c->db->id);
|
||||
/* No need updateKeysizesHist(). dbDelete() done it already. */
|
||||
} else {
|
||||
unsigned long len = zsetLength(zobj);
|
||||
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_ZSET, len + deleted, len);
|
||||
}
|
||||
}
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
@@ -2611,16 +2607,6 @@ static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen,
|
||||
}
|
||||
}
|
||||
|
||||
dictType setAccumulatorDictType = {
|
||||
dictSdsHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCompare, /* key compare */
|
||||
NULL, /* key destructor */
|
||||
NULL, /* val destructor */
|
||||
NULL /* allow to expand */
|
||||
};
|
||||
|
||||
/* The zunionInterDiffGenericCommand() function is called in order to implement the
|
||||
* following commands: ZUNION, ZINTER, ZDIFF, ZUNIONSTORE, ZINTERSTORE, ZDIFFSTORE,
|
||||
* ZINTERCARD.
|
||||
@@ -2816,7 +2802,6 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
zuiClearIterator(&src[0]);
|
||||
}
|
||||
} else if (op == SET_OP_UNION) {
|
||||
dict *accumulator = dictCreate(&setAccumulatorDictType);
|
||||
dictIterator *di;
|
||||
dictEntry *de, *existing;
|
||||
double score;
|
||||
@@ -2824,7 +2809,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
if (setnum) {
|
||||
/* Our union is at least as large as the largest set.
|
||||
* Resize the dictionary ASAP to avoid useless rehashing. */
|
||||
dictExpand(accumulator,zuiLength(&src[setnum-1]));
|
||||
dictExpand(dstzset->dict,zuiLength(&src[setnum-1]));
|
||||
}
|
||||
|
||||
/* Step 1: Create a dictionary of elements -> aggregated-scores
|
||||
@@ -2839,7 +2824,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
if (isnan(score)) score = 0;
|
||||
|
||||
/* Search for this element in the accumulating dictionary. */
|
||||
de = dictAddRaw(accumulator,zuiSdsFromValue(&zval),&existing);
|
||||
de = dictAddRaw(dstzset->dict,zuiSdsFromValue(&zval),&existing);
|
||||
/* If we don't have it, we need to create a new entry. */
|
||||
if (!existing) {
|
||||
tmp = zuiNewSdsFromValue(&zval);
|
||||
@@ -2849,7 +2834,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
totelelen += sdslen(tmp);
|
||||
if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
|
||||
/* Update the element with its initial score. */
|
||||
dictSetKey(accumulator, de, tmp);
|
||||
dictSetKey(dstzset->dict, de, tmp);
|
||||
dictSetDoubleVal(de,score);
|
||||
} else {
|
||||
/* Update the score with the score of the new instance
|
||||
@@ -2866,21 +2851,15 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
}
|
||||
|
||||
/* Step 2: convert the dictionary into the final sorted set. */
|
||||
di = dictGetIterator(accumulator);
|
||||
|
||||
/* We now are aware of the final size of the resulting sorted set,
|
||||
* let's resize the dictionary embedded inside the sorted set to the
|
||||
* right size, in order to save rehashing time. */
|
||||
dictExpand(dstzset->dict,dictSize(accumulator));
|
||||
di = dictGetIterator(dstzset->dict);
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds ele = dictGetKey(de);
|
||||
score = dictGetDoubleVal(de);
|
||||
znode = zslInsert(dstzset->zsl,score,ele);
|
||||
dictAdd(dstzset->dict,ele,&znode->score);
|
||||
dictSetVal(dstzset->dict,de,&znode->score);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
dictRelease(accumulator);
|
||||
} else if (op == SET_OP_DIFF) {
|
||||
zdiff(src, setnum, dstzset, &maxelelen, &totelelen);
|
||||
} else {
|
||||
@@ -4064,6 +4043,9 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
|
||||
dbDelete(c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
/* No need updateKeysizesHist(). dbDelete() done it already. */
|
||||
} else {
|
||||
updateKeysizesHist(c->db, getKeySlot(key->ptr), OBJ_ZSET, llen, llen - result_count);
|
||||
}
|
||||
signalModifiedKey(c,c->db,key);
|
||||
|
||||
|
||||
@@ -75,10 +75,6 @@ static int parseProtocolsConfig(const char *str) {
|
||||
return protocols;
|
||||
}
|
||||
|
||||
/* list of connections with pending data already read from the socket, but not
|
||||
* served to the reader yet. */
|
||||
static list *pending_list = NULL;
|
||||
|
||||
/**
|
||||
* OpenSSL global initialization and locking handling callbacks.
|
||||
* Note that this is only required for OpenSSL < 1.1.0.
|
||||
@@ -144,8 +140,6 @@ static void tlsInit(void) {
|
||||
if (!RAND_poll()) {
|
||||
serverLog(LL_WARNING, "OpenSSL: Failed to seed random number generator.");
|
||||
}
|
||||
|
||||
pending_list = listCreate();
|
||||
}
|
||||
|
||||
static void tlsCleanup(void) {
|
||||
@@ -435,20 +429,21 @@ typedef struct tls_connection {
|
||||
listNode *pending_list_node;
|
||||
} tls_connection;
|
||||
|
||||
static connection *createTLSConnection(int client_side) {
|
||||
static connection *createTLSConnection(struct aeEventLoop *el, int client_side) {
|
||||
SSL_CTX *ctx = redis_tls_ctx;
|
||||
if (client_side && redis_tls_client_ctx)
|
||||
ctx = redis_tls_client_ctx;
|
||||
tls_connection *conn = zcalloc(sizeof(tls_connection));
|
||||
conn->c.type = &CT_TLS;
|
||||
conn->c.fd = -1;
|
||||
conn->c.el = el;
|
||||
conn->c.iovcnt = IOV_MAX;
|
||||
conn->ssl = SSL_new(ctx);
|
||||
return (connection *) conn;
|
||||
}
|
||||
|
||||
static connection *connCreateTLS(void) {
|
||||
return createTLSConnection(1);
|
||||
static connection *connCreateTLS(struct aeEventLoop *el) {
|
||||
return createTLSConnection(el, 1);
|
||||
}
|
||||
|
||||
/* Fetch the latest OpenSSL error and store it in the connection */
|
||||
@@ -468,10 +463,11 @@ static void updateTLSError(tls_connection *conn) {
|
||||
* Callers should use connGetState() and verify the created connection
|
||||
* is not in an error state.
|
||||
*/
|
||||
static connection *connCreateAcceptedTLS(int fd, void *priv) {
|
||||
static connection *connCreateAcceptedTLS(struct aeEventLoop *el, int fd, void *priv) {
|
||||
int require_auth = *(int *)priv;
|
||||
tls_connection *conn = (tls_connection *) createTLSConnection(0);
|
||||
tls_connection *conn = (tls_connection *) createTLSConnection(el, 0);
|
||||
conn->c.fd = fd;
|
||||
conn->c.el = el;
|
||||
conn->c.state = CONN_STATE_ACCEPTING;
|
||||
|
||||
if (!conn->ssl) {
|
||||
@@ -575,17 +571,17 @@ static int updateStateAfterSSLIO(tls_connection *conn, int ret_value, int update
|
||||
}
|
||||
|
||||
static void registerSSLEvent(tls_connection *conn, WantIOType want) {
|
||||
int mask = aeGetFileEvents(server.el, conn->c.fd);
|
||||
int mask = aeGetFileEvents(conn->c.el, conn->c.fd);
|
||||
|
||||
switch (want) {
|
||||
case WANT_READ:
|
||||
if (mask & AE_WRITABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
|
||||
if (!(mask & AE_READABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE,
|
||||
if (mask & AE_WRITABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE);
|
||||
if (!(mask & AE_READABLE)) aeCreateFileEvent(conn->c.el, conn->c.fd, AE_READABLE,
|
||||
tlsEventHandler, conn);
|
||||
break;
|
||||
case WANT_WRITE:
|
||||
if (mask & AE_READABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
|
||||
if (!(mask & AE_WRITABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE,
|
||||
if (mask & AE_READABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_READABLE);
|
||||
if (!(mask & AE_WRITABLE)) aeCreateFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE,
|
||||
tlsEventHandler, conn);
|
||||
break;
|
||||
default:
|
||||
@@ -595,19 +591,42 @@ static void registerSSLEvent(tls_connection *conn, WantIOType want) {
|
||||
}
|
||||
|
||||
static void updateSSLEvent(tls_connection *conn) {
|
||||
int mask = aeGetFileEvents(server.el, conn->c.fd);
|
||||
serverAssert(conn->c.el);
|
||||
int mask = aeGetFileEvents(conn->c.el, conn->c.fd);
|
||||
int need_read = conn->c.read_handler || (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ);
|
||||
int need_write = conn->c.write_handler || (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE);
|
||||
|
||||
if (need_read && !(mask & AE_READABLE))
|
||||
aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE, tlsEventHandler, conn);
|
||||
aeCreateFileEvent(conn->c.el, conn->c.fd, AE_READABLE, tlsEventHandler, conn);
|
||||
if (!need_read && (mask & AE_READABLE))
|
||||
aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
|
||||
aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_READABLE);
|
||||
|
||||
if (need_write && !(mask & AE_WRITABLE))
|
||||
aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE, tlsEventHandler, conn);
|
||||
aeCreateFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE, tlsEventHandler, conn);
|
||||
if (!need_write && (mask & AE_WRITABLE))
|
||||
aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
|
||||
aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE);
|
||||
}
|
||||
|
||||
/* Add a connection to the list of connections with pending data that has
|
||||
* already been read from the socket but has not yet been served to the reader. */
|
||||
static void tlsPendingAdd(tls_connection *conn) {
|
||||
if (!conn->c.el->privdata[1])
|
||||
conn->c.el->privdata[1] = listCreate();
|
||||
|
||||
list *pending_list = conn->c.el->privdata[1];
|
||||
if (!conn->pending_list_node) {
|
||||
listAddNodeTail(pending_list, conn);
|
||||
conn->pending_list_node = listLast(pending_list);
|
||||
}
|
||||
}
|
||||
|
||||
/* Removes a connection from the list of connections with pending data. */
|
||||
static void tlsPendingRemove(tls_connection *conn) {
|
||||
if (conn->pending_list_node) {
|
||||
list *pending_list = conn->c.el->privdata[1];
|
||||
listDelNode(pending_list, conn->pending_list_node);
|
||||
conn->pending_list_node = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void tlsHandleEvent(tls_connection *conn, int mask) {
|
||||
@@ -718,13 +737,9 @@ static void tlsHandleEvent(tls_connection *conn, int mask) {
|
||||
* to a list of pending connection that should be handled anyway. */
|
||||
if ((mask & AE_READABLE)) {
|
||||
if (SSL_pending(conn->ssl) > 0) {
|
||||
if (!conn->pending_list_node) {
|
||||
listAddNodeTail(pending_list, conn);
|
||||
conn->pending_list_node = listLast(pending_list);
|
||||
}
|
||||
tlsPendingAdd(conn);
|
||||
} else if (conn->pending_list_node) {
|
||||
listDelNode(pending_list, conn->pending_list_node);
|
||||
conn->pending_list_node = NULL;
|
||||
tlsPendingRemove(conn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,7 +749,8 @@ static void tlsHandleEvent(tls_connection *conn, int mask) {
|
||||
break;
|
||||
}
|
||||
|
||||
updateSSLEvent(conn);
|
||||
/* The event loop may have been unbound during the event processing above. */
|
||||
if (conn->c.el) updateSSLEvent(conn);
|
||||
}
|
||||
|
||||
static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask) {
|
||||
@@ -748,7 +764,6 @@ static void tlsAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask)
|
||||
int cport, cfd;
|
||||
int max = server.max_new_tls_conns_per_cycle;
|
||||
char cip[NET_IP_STR_LEN];
|
||||
UNUSED(el);
|
||||
UNUSED(mask);
|
||||
UNUSED(privdata);
|
||||
|
||||
@@ -761,7 +776,7 @@ static void tlsAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask)
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(connCreateAcceptedTLS(cfd, &server.tls_auth_clients),0,cip);
|
||||
acceptCommonHandler(connCreateAcceptedTLS(el,cfd,&server.tls_auth_clients), 0, cip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,6 +821,7 @@ static void connTLSClose(connection *conn_) {
|
||||
}
|
||||
|
||||
if (conn->pending_list_node) {
|
||||
list *pending_list = conn->c.el->privdata[1];
|
||||
listDelNode(pending_list, conn->pending_list_node);
|
||||
conn->pending_list_node = NULL;
|
||||
}
|
||||
@@ -863,6 +879,33 @@ static int connTLSConnect(connection *conn_, const char *addr, int port, const c
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
static void connTLSUnbindEventLoop(connection *conn_) {
|
||||
tls_connection *conn = (tls_connection *) conn_;
|
||||
|
||||
/* We need to remove all events from the old event loop. The subsequent
|
||||
* updateSSLEvent() will add the appropriate events to the new event loop. */
|
||||
if (conn->c.el) {
|
||||
int mask = aeGetFileEvents(conn->c.el, conn->c.fd);
|
||||
if (mask & AE_READABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_READABLE);
|
||||
if (mask & AE_WRITABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE);
|
||||
|
||||
/* Check if there are pending events and handle accordingly. */
|
||||
int has_pending = conn->pending_list_node != NULL;
|
||||
if (has_pending) tlsPendingRemove(conn);
|
||||
}
|
||||
}
|
||||
|
||||
static int connTLSRebindEventLoop(connection *conn_, aeEventLoop *el) {
|
||||
tls_connection *conn = (tls_connection *) conn_;
|
||||
serverAssert(!conn->c.el && !conn->c.read_handler &&
|
||||
!conn->c.write_handler && !conn->pending_list_node);
|
||||
conn->c.el = el;
|
||||
if (el && SSL_pending(conn->ssl)) tlsPendingAdd(conn);
|
||||
/* Add the appropriate events to the new event loop. */
|
||||
updateSSLEvent((tls_connection *) conn);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
static int connTLSWrite(connection *conn_, const void *data, size_t data_len) {
|
||||
tls_connection *conn = (tls_connection *) conn_;
|
||||
int ret;
|
||||
@@ -1044,16 +1087,19 @@ static const char *connTLSGetType(connection *conn_) {
|
||||
return CONN_TYPE_TLS;
|
||||
}
|
||||
|
||||
static int tlsHasPendingData(void) {
|
||||
static int tlsHasPendingData(struct aeEventLoop *el) {
|
||||
list *pending_list = el->privdata[1];
|
||||
if (!pending_list)
|
||||
return 0;
|
||||
return listLength(pending_list) > 0;
|
||||
}
|
||||
|
||||
static int tlsProcessPendingData(void) {
|
||||
static int tlsProcessPendingData(struct aeEventLoop *el) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
list *pending_list = el->privdata[1];
|
||||
if (!pending_list) return 0;
|
||||
int processed = listLength(pending_list);
|
||||
listRewind(pending_list,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
@@ -1114,6 +1160,10 @@ static ConnectionType CT_TLS = {
|
||||
.blocking_connect = connTLSBlockingConnect,
|
||||
.accept = connTLSAccept,
|
||||
|
||||
/* event loop */
|
||||
.unbind_event_loop = connTLSUnbindEventLoop,
|
||||
.rebind_event_loop = connTLSRebindEventLoop,
|
||||
|
||||
/* IO */
|
||||
.read = connTLSRead,
|
||||
.write = connTLSWrite,
|
||||
|
||||
+18
-1
@@ -253,6 +253,7 @@ void trackingRememberKeys(client *tracking, client *executing) {
|
||||
* - Following a flush command, to send a single RESP NULL to indicate
|
||||
* that all keys are now invalid. */
|
||||
void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
int paused = 0;
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
|
||||
@@ -275,6 +276,11 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
c = redir;
|
||||
using_redirection = 1;
|
||||
/* Start to touch another client data. */
|
||||
if (c->running_tid != IOTHREAD_MAIN_THREAD_ID) {
|
||||
pauseIOThread(c->running_tid);
|
||||
paused = 1;
|
||||
}
|
||||
old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
}
|
||||
@@ -296,7 +302,7 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
* it since RESP2 does not support push messages in the same
|
||||
* connection. */
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
return;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Send the "value" part, which is the array of keys. */
|
||||
@@ -308,6 +314,17 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
}
|
||||
updateClientMemUsageAndBucket(c);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
|
||||
done:
|
||||
if (paused) {
|
||||
if (clientHasPendingReplies(c)) {
|
||||
serverAssert(!(c->flags & CLIENT_PENDING_WRITE));
|
||||
/* Actually we install write handler of client which is in IO thread
|
||||
* event loop, it is safe since the io thread is paused */
|
||||
connSetWriteHandler(c->conn, sendReplyToClient);
|
||||
}
|
||||
resumeIOThread(c->running_tid);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called when a key is modified in Redis and in the case
|
||||
|
||||
+13
-4
@@ -74,18 +74,19 @@ static int connUnixListen(connListener *listener) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
static connection *connCreateUnix(void) {
|
||||
static connection *connCreateUnix(struct aeEventLoop *el) {
|
||||
connection *conn = zcalloc(sizeof(connection));
|
||||
conn->type = &CT_Unix;
|
||||
conn->fd = -1;
|
||||
conn->iovcnt = IOV_MAX;
|
||||
conn->el = el;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
static connection *connCreateAcceptedUnix(int fd, void *priv) {
|
||||
static connection *connCreateAcceptedUnix(struct aeEventLoop *el, int fd, void *priv) {
|
||||
UNUSED(priv);
|
||||
connection *conn = connCreateUnix();
|
||||
connection *conn = connCreateUnix(el);
|
||||
conn->fd = fd;
|
||||
conn->state = CONN_STATE_ACCEPTING;
|
||||
return conn;
|
||||
@@ -107,7 +108,7 @@ static void connUnixAcceptHandler(aeEventLoop *el, int fd, void *privdata, int m
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
acceptCommonHandler(connCreateAcceptedUnix(cfd, NULL),CLIENT_UNIX_SOCKET,NULL);
|
||||
acceptCommonHandler(connCreateAcceptedUnix(el, cfd, NULL),CLIENT_UNIX_SOCKET,NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +124,10 @@ static int connUnixAccept(connection *conn, ConnectionCallbackFunc accept_handle
|
||||
return connectionTypeTcp()->accept(conn, accept_handler);
|
||||
}
|
||||
|
||||
static int connUnixRebindEventLoop(connection *conn, aeEventLoop *el) {
|
||||
return connectionTypeTcp()->rebind_event_loop(conn, el);
|
||||
}
|
||||
|
||||
static int connUnixWrite(connection *conn, const void *data, size_t data_len) {
|
||||
return connectionTypeTcp()->write(conn, data, data_len);
|
||||
}
|
||||
@@ -186,6 +191,10 @@ static ConnectionType CT_Unix = {
|
||||
.blocking_connect = NULL,
|
||||
.accept = connUnixAccept,
|
||||
|
||||
/* event loop */
|
||||
.unbind_event_loop = NULL,
|
||||
.rebind_event_loop = connUnixRebindEventLoop,
|
||||
|
||||
/* IO */
|
||||
.write = connUnixWrite,
|
||||
.writev = connUnixWritev,
|
||||
|
||||
+15
-4
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "fpconv_dtoa.h"
|
||||
#include "fast_float_strtod.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -53,10 +54,20 @@
|
||||
|
||||
#define UNUSED(x) ((void)(x))
|
||||
|
||||
/* Selectively define static_assert. Attempt to avoid include server.h in this file. */
|
||||
#ifndef static_assert
|
||||
#define static_assert(expr, lit) extern char __static_assert_failure[(expr) ? 1:-1]
|
||||
#endif
|
||||
|
||||
static_assert(UINTPTR_MAX == 0xffffffffffffffff || UINTPTR_MAX == 0xffffffff, "Unsupported pointer size");
|
||||
|
||||
/* Glob-style pattern matching. */
|
||||
static int stringmatchlen_impl(const char *pattern, int patternLen,
|
||||
const char *string, int stringLen, int nocase, int *skipLongerMatches)
|
||||
const char *string, int stringLen, int nocase, int *skipLongerMatches, int nesting)
|
||||
{
|
||||
/* Protection against abusive patterns. */
|
||||
if (nesting > 1000) return 0;
|
||||
|
||||
while(patternLen && stringLen) {
|
||||
switch(pattern[0]) {
|
||||
case '*':
|
||||
@@ -68,7 +79,7 @@ static int stringmatchlen_impl(const char *pattern, int patternLen,
|
||||
return 1; /* match */
|
||||
while(stringLen) {
|
||||
if (stringmatchlen_impl(pattern+1, patternLen-1,
|
||||
string, stringLen, nocase, skipLongerMatches))
|
||||
string, stringLen, nocase, skipLongerMatches, nesting+1))
|
||||
return 1; /* match */
|
||||
if (*skipLongerMatches)
|
||||
return 0; /* no match */
|
||||
@@ -190,7 +201,7 @@ static int stringmatchlen_impl(const char *pattern, int patternLen,
|
||||
int stringmatchlen(const char *pattern, int patternLen,
|
||||
const char *string, int stringLen, int nocase) {
|
||||
int skipLongerMatches = 0;
|
||||
return stringmatchlen_impl(pattern,patternLen,string,stringLen,nocase,&skipLongerMatches);
|
||||
return stringmatchlen_impl(pattern,patternLen,string,stringLen,nocase,&skipLongerMatches,0);
|
||||
}
|
||||
|
||||
int stringmatch(const char *pattern, const char *string, int nocase) {
|
||||
@@ -612,7 +623,7 @@ int string2ld(const char *s, size_t slen, long double *dp) {
|
||||
int string2d(const char *s, size_t slen, double *dp) {
|
||||
errno = 0;
|
||||
char *eptr;
|
||||
*dp = strtod(s, &eptr);
|
||||
*dp = fast_float_strtod(s, &eptr);
|
||||
if (slen == 0 ||
|
||||
isspace(((const char*)s)[0]) ||
|
||||
(size_t)(eptr-(char*)s) != slen ||
|
||||
|
||||
+13
@@ -79,6 +79,19 @@ int snprintf_async_signal_safe(char *to, size_t n, const char *fmt, ...);
|
||||
size_t redis_strlcpy(char *dst, const char *src, size_t dsize);
|
||||
size_t redis_strlcat(char *dst, const char *src, size_t dsize);
|
||||
|
||||
/* to keep it opt without conditions Works only for: 0 < x < 2^63 */
|
||||
static inline int log2ceil(size_t x) {
|
||||
#if UINTPTR_MAX == 0xffffffffffffffff
|
||||
return 63 - __builtin_clzll(x);
|
||||
#else
|
||||
return 31 - __builtin_clz(x);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef static_assert
|
||||
#define static_assert(expr, lit) extern char __static_assert_failure[(expr) ? 1:-1]
|
||||
#endif
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int utilTest(int argc, char **argv, int flags);
|
||||
#endif
|
||||
|
||||
+40
-8
@@ -31,6 +31,7 @@ void zlibc_free(void *ptr) {
|
||||
#include <string.h>
|
||||
#include "zmalloc.h"
|
||||
#include "atomicvar.h"
|
||||
#include "redisassert.h"
|
||||
|
||||
#define UNUSED(x) ((void)(x))
|
||||
|
||||
@@ -67,10 +68,34 @@ void zlibc_free(void *ptr) {
|
||||
#define dallocx(ptr,flags) je_dallocx(ptr,flags)
|
||||
#endif
|
||||
|
||||
#define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n))
|
||||
#define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n))
|
||||
#define MAX_THREADS 16 /* Keep it a power of 2 so we can use '&' instead of '%'. */
|
||||
#define THREAD_MASK (MAX_THREADS - 1)
|
||||
|
||||
static redisAtomic size_t used_memory = 0;
|
||||
typedef struct used_memory_entry {
|
||||
redisAtomic long long used_memory;
|
||||
char padding[CACHE_LINE_SIZE - sizeof(long long)];
|
||||
} used_memory_entry;
|
||||
|
||||
static __attribute__((aligned(CACHE_LINE_SIZE))) used_memory_entry used_memory[MAX_THREADS];
|
||||
static redisAtomic size_t num_active_threads = 0;
|
||||
static __thread long my_thread_index = -1;
|
||||
|
||||
static inline void init_my_thread_index(void) {
|
||||
if (unlikely(my_thread_index == -1)) {
|
||||
atomicGetIncr(num_active_threads, my_thread_index, 1);
|
||||
my_thread_index &= THREAD_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_zmalloc_stat_alloc(long long num) {
|
||||
init_my_thread_index();
|
||||
atomicIncr(used_memory[my_thread_index].used_memory, num);
|
||||
}
|
||||
|
||||
static void update_zmalloc_stat_free(long long num) {
|
||||
init_my_thread_index();
|
||||
atomicDecr(used_memory[my_thread_index].used_memory, num);
|
||||
}
|
||||
|
||||
static void zmalloc_default_oom(size_t size) {
|
||||
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
|
||||
@@ -436,9 +461,18 @@ char *zstrdup(const char *s) {
|
||||
}
|
||||
|
||||
size_t zmalloc_used_memory(void) {
|
||||
size_t um;
|
||||
atomicGet(used_memory,um);
|
||||
return um;
|
||||
size_t local_num_active_threads;
|
||||
long long total_mem = 0;
|
||||
atomicGet(num_active_threads,local_num_active_threads);
|
||||
if (local_num_active_threads > MAX_THREADS) {
|
||||
local_num_active_threads = MAX_THREADS;
|
||||
}
|
||||
for (size_t i = 0; i < local_num_active_threads; ++i) {
|
||||
long long thread_used_mem;
|
||||
atomicGet(used_memory[i].used_memory, thread_used_mem);
|
||||
total_mem += thread_used_mem;
|
||||
}
|
||||
return total_mem;
|
||||
}
|
||||
|
||||
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
|
||||
@@ -655,8 +689,6 @@ size_t zmalloc_get_rss(void) {
|
||||
|
||||
#if defined(USE_JEMALLOC)
|
||||
|
||||
#include "redisassert.h"
|
||||
|
||||
/* Compute the total memory wasted in fragmentation of inside small arena bins.
|
||||
* Done by summing the memory in unused regs in all slabs of all small bins.
|
||||
*
|
||||
|
||||
@@ -32,6 +32,13 @@ proc get_one_of_my_replica {id} {
|
||||
}
|
||||
set replica_port [lindex [lindex [lindex [R $id role] 2] 0] 1]
|
||||
set replica_id_num [get_instance_id_by_port redis $replica_port]
|
||||
|
||||
# To avoid -LOADING reply, wait until replica syncs with master.
|
||||
wait_for_condition 1000 50 {
|
||||
[RI $replica_id_num master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replica did not sync in time."
|
||||
}
|
||||
return $replica_id_num
|
||||
}
|
||||
|
||||
@@ -70,6 +77,7 @@ proc test_slave_load_expired_keys {aof} {
|
||||
|
||||
# wait for replica to be in sync with master
|
||||
wait_for_condition 500 10 {
|
||||
[RI $replica_id master_link_status] eq {up} &&
|
||||
[R $replica_id dbsize] eq [R $master_id dbsize]
|
||||
} else {
|
||||
fail "replica didn't sync"
|
||||
@@ -104,8 +112,15 @@ proc test_slave_load_expired_keys {aof} {
|
||||
# start the replica again (loading an RDB or AOF file)
|
||||
restart_instance redis $replica_id
|
||||
|
||||
# Replica may start a full sync after restart, trying in a loop to avoid
|
||||
# -LOADING reply in that case.
|
||||
wait_for_condition 1000 50 {
|
||||
[catch {set replica_dbsize_3 [R $replica_id dbsize]} e] == 0
|
||||
} else {
|
||||
fail "Replica is not up."
|
||||
}
|
||||
|
||||
# make sure the keys are still there
|
||||
set replica_dbsize_3 [R $replica_id dbsize]
|
||||
assert {$replica_dbsize_3 > $replica_dbsize_0}
|
||||
|
||||
# restore settings
|
||||
@@ -113,6 +128,7 @@ proc test_slave_load_expired_keys {aof} {
|
||||
|
||||
# wait for the master to expire all keys and replica to get the DELs
|
||||
wait_for_condition 500 10 {
|
||||
[RI $replica_id master_link_status] eq {up} &&
|
||||
[R $replica_id dbsize] eq $master_dbsize_0
|
||||
} else {
|
||||
fail "keys didn't expire"
|
||||
|
||||
@@ -124,6 +124,10 @@ test "Verify health as fail for killed node" {
|
||||
}
|
||||
}
|
||||
|
||||
test "Verify that other nodes can correctly output the new master's slots" {
|
||||
assert_not_equal {} [dict get [get_node_info_from_shard [R 4 CLUSTER MYID] 8 "shard"] slots]
|
||||
}
|
||||
|
||||
set primary_id 4
|
||||
set replica_id 0
|
||||
|
||||
@@ -133,7 +137,8 @@ test "Restarting primary node" {
|
||||
|
||||
test "Instance #0 gets converted into a replica" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI $replica_id role] eq {slave}
|
||||
[RI $replica_id role] eq {slave} &&
|
||||
[RI $replica_id master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Old primary was not converted into replica"
|
||||
}
|
||||
|
||||
@@ -32,6 +32,17 @@ test "Cluster nodes hard reset" {
|
||||
} else {
|
||||
set node_timeout 3000
|
||||
}
|
||||
|
||||
# Wait until slave is synced. Otherwise, it may reply -LOADING
|
||||
# for any commands below.
|
||||
if {[RI $id role] eq {slave}} {
|
||||
wait_for_condition 50 1000 {
|
||||
[RI $id master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Slave were not able to sync."
|
||||
}
|
||||
}
|
||||
|
||||
catch {R $id flushall} ; # May fail for readonly slaves.
|
||||
R $id MULTI
|
||||
R $id cluster reset hard
|
||||
|
||||
@@ -655,4 +655,50 @@ tags {"aof external:skip"} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {overrides {loading-process-events-interval-bytes 1024}} {
|
||||
test "Allow changing appendonly config while loading from AOF on startup" {
|
||||
# Set AOF enabled, populate db and restart.
|
||||
r config set appendonly yes
|
||||
r config set key-load-delay 100
|
||||
r config rewrite
|
||||
populate 10000
|
||||
restart_server 0 false false
|
||||
|
||||
# Disable AOF while loading from the disk.
|
||||
assert_equal 1 [s loading]
|
||||
r config set appendonly no
|
||||
assert_equal 1 [s loading]
|
||||
|
||||
# Speed up loading, verify AOF disabled.
|
||||
r config set key-load-delay 0
|
||||
wait_done_loading r
|
||||
assert_equal {10000} [r dbsize]
|
||||
assert_equal 0 [s aof_enabled]
|
||||
}
|
||||
|
||||
test "Allow changing appendonly config while loading from RDB on startup" {
|
||||
# Set AOF disabled, populate db and restart.
|
||||
r flushall
|
||||
r config set appendonly no
|
||||
r config set key-load-delay 100
|
||||
r config rewrite
|
||||
populate 10000
|
||||
r save
|
||||
restart_server 0 false false
|
||||
|
||||
# Enable AOF while loading from the disk.
|
||||
assert_equal 1 [s loading]
|
||||
r config set appendonly yes
|
||||
assert_equal 1 [s loading]
|
||||
|
||||
# Speed up loading, verify AOF enabled, do a quick sanity.
|
||||
r config set key-load-delay 0
|
||||
wait_done_loading r
|
||||
assert_equal {10000} [r dbsize]
|
||||
assert_equal 1 [s aof_enabled]
|
||||
r set t 1
|
||||
assert_equal {1} [r get t]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ foreach sanitize_dump {no yes} {
|
||||
set restore_failed false
|
||||
set report_and_restart false
|
||||
set sent {}
|
||||
set expired_subkeys [s expired_subkeys]
|
||||
# RESTORE can fail, but hopefully not terminate
|
||||
if { [catch { r restore "_$k" 0 $dump REPLACE } err] } {
|
||||
set restore_failed true
|
||||
@@ -145,7 +146,17 @@ foreach sanitize_dump {no yes} {
|
||||
# if RESTORE didn't fail or terminate, run some random traffic on the new key
|
||||
incr stat_successful_restore
|
||||
if { [ catch {
|
||||
set sent [generate_fuzzy_traffic_on_key "_$k" 1] ;# traffic for 1 second
|
||||
set type [r type "_$k"]
|
||||
if {$type eq {none}} {
|
||||
# The key has been removed due to expiration.
|
||||
# Ensure the server didn't terminate during expiration and verify
|
||||
# expire stats to confirm the key was removed due to expiration.
|
||||
r ping
|
||||
assert_morethan [s expired_subkeys] $expired_subkeys
|
||||
} else {
|
||||
set sent [generate_fuzzy_traffic_on_key "_$k" $type 1] ;# traffic for 1 second
|
||||
}
|
||||
|
||||
incr stat_traffic_commands_sent [llength $sent]
|
||||
r del "_$k" ;# in case the server terminated, here's where we'll detect it.
|
||||
if {$dbsize != [r dbsize]} {
|
||||
@@ -182,7 +193,7 @@ foreach sanitize_dump {no yes} {
|
||||
dump_server_log $srv
|
||||
}
|
||||
|
||||
puts "Server crashed (by signal: $by_signal), with payload: $printable_dump"
|
||||
puts "Server crashed (by signal: $by_signal, err: $err), with payload: $printable_dump"
|
||||
set print_commands true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -885,5 +885,51 @@ test {corrupt payload: fuzzer findings - set with duplicate elements causes sdif
|
||||
}
|
||||
} {} {logreqres:skip} ;# This test violates {"uniqueItems": true}
|
||||
|
||||
test {corrupt payload: fuzzer findings - set with invalid length causes smembers to hang} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
# In the past, it generated a broken protocol and left the client hung in smembers
|
||||
r config set sanitize-dump-payload no
|
||||
assert_equal {OK} [r restore _set 0 "\x14\x16\x16\x00\x00\x00\x0c\x00\x81\x61\x02\x81\x62\x02\x81\x63\x02\x01\x01\x02\x01\x03\x01\xff\x0c\x00\x91\x00\x56\x73\xc1\x82\xd5\xbd" replace]
|
||||
assert_encoding listpack _set
|
||||
catch { r SMEMBERS _set } err
|
||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - set with invalid length causes sscan to hang} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
# In the past, it generated a broken protocol and left the client hung in smembers
|
||||
r config set sanitize-dump-payload no
|
||||
assert_equal {OK} [r restore _set 0 "\x14\x16\x16\x00\x00\x00\x0c\x00\x81\x61\x02\x81\x62\x02\x81\x63\x02\x01\x01\x02\x01\x03\x01\xff\x0c\x00\x91\x00\x56\x73\xc1\x82\xd5\xbd" replace]
|
||||
assert_encoding listpack _set
|
||||
catch { r SSCAN _set 0 } err
|
||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: zset listpack encoded with invalid length causes zscan to hang} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
assert_equal {OK} [r restore _zset 0 "\x11\x16\x16\x00\x00\x00\x1a\x00\x81\x61\x02\x01\x01\x81\x62\x02\x02\x01\x81\x63\x02\x03\x01\xff\x0c\x00\x81\xa7\xcd\x31\x22\x6c\xef\xf7" replace]
|
||||
assert_encoding listpack _zset
|
||||
catch { r ZSCAN _zset 0 } err
|
||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: hash listpack encoded with invalid length causes hscan to hang} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
assert_equal {OK} [r restore _hash 0 "\x10\x17\x17\x00\x00\x00\x0e\x00\x82\x66\x31\x03\x82\x76\x31\x03\x82\x66\x32\x03\x82\x76\x32\x03\xff\x0c\x00\xf1\xc5\x36\x92\x29\x6a\x8c\xc5" replace]
|
||||
assert_encoding listpack _hash
|
||||
catch { r HSCAN _hash 0 } err
|
||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
||||
}
|
||||
}
|
||||
|
||||
} ;# tags
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ if {$::singledb} {
|
||||
set ::dbnum 9
|
||||
}
|
||||
|
||||
file delete ./.rediscli_history_test
|
||||
set ::env(REDISCLI_HISTFILE) ".rediscli_history_test"
|
||||
|
||||
start_server {tags {"cli"}} {
|
||||
proc open_cli {{opts ""} {infile ""}} {
|
||||
if { $opts == "" } {
|
||||
@@ -68,10 +71,8 @@ start_server {tags {"cli"}} {
|
||||
set _ [format_output [read_cli $fd]]
|
||||
}
|
||||
|
||||
file delete ./.rediscli_history_test
|
||||
proc test_interactive_cli_with_prompt {name code} {
|
||||
set ::env(FAKETTY_WITH_PROMPT) 1
|
||||
set ::env(REDISCLI_HISTFILE) ".rediscli_history_test"
|
||||
test_interactive_cli $name $code
|
||||
unset ::env(FAKETTY_WITH_PROMPT)
|
||||
}
|
||||
@@ -808,4 +809,35 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
|
||||
assert_equal 3 [exec {*}$cmdline ZCARD new_zset]
|
||||
assert_equal "a\n1\nb\n2\nc\n3" [exec {*}$cmdline ZRANGE new_zset 0 -1 WITHSCORES]
|
||||
}
|
||||
|
||||
test "Send eval command by using --eval option" {
|
||||
set tmpfile [write_tmpfile {return ARGV[1]}]
|
||||
set cmdline [rediscli [srv host] [srv port]]
|
||||
assert_equal "foo" [exec {*}$cmdline --eval $tmpfile , foo]
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"cli external:skip"}} {
|
||||
test_interactive_cli_with_prompt "db_num showed in redis-cli after reconnected" {
|
||||
run_command $fd "select 0\x0D"
|
||||
run_command $fd "set a zoo-0\x0D"
|
||||
run_command $fd "select 6\x0D"
|
||||
run_command $fd "set a zoo-6\x0D"
|
||||
r save
|
||||
|
||||
# kill server and restart
|
||||
exec kill [s process_id]
|
||||
wait_for_log_messages 0 {"*Redis is now ready to exit*"} 0 1000 10
|
||||
catch {[run_command $fd "ping\x0D"]} err
|
||||
restart_server 0 true false 0
|
||||
|
||||
# redis-cli should show '[6]' after reconnected and return 'zoo-6'
|
||||
write_cli $fd "GET a\x0D"
|
||||
after 100
|
||||
set result [format_output [read_cli $fd]]
|
||||
set regex {not connected> GET a.*"zoo-6".*127\.0\.0\.1:[0-9]*\[6\]>}
|
||||
assert_equal 1 [regexp $regex $result]
|
||||
}
|
||||
}
|
||||
|
||||
file delete ./.rediscli_history_test
|
||||
|
||||
@@ -652,7 +652,6 @@ foreach testType {Successful Aborted} {
|
||||
}
|
||||
|
||||
test {Blocked commands and configs during async-loading} {
|
||||
assert_error {LOADING*} {$replica config set appendonly no}
|
||||
assert_error {LOADING*} {$replica REPLICAOF no one}
|
||||
}
|
||||
|
||||
@@ -1457,3 +1456,143 @@ start_server {tags {"repl external:skip"}} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach disklessload {disabled on-empty-db} {
|
||||
test "Replica should reply LOADING while flushing a large db (disklessload: $disklessload)" {
|
||||
start_server {} {
|
||||
set replica [srv 0 client]
|
||||
start_server {} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
|
||||
$replica config set repl-diskless-load $disklessload
|
||||
|
||||
# Populate replica with many keys, master with a few keys.
|
||||
$replica debug populate 2000000
|
||||
populate 3 master 10
|
||||
|
||||
# Start the replication process...
|
||||
$replica replicaof $master_host $master_port
|
||||
|
||||
wait_for_condition 100 100 {
|
||||
[s -1 loading] eq 1
|
||||
} else {
|
||||
fail "Replica didn't get into loading mode"
|
||||
}
|
||||
|
||||
# If replica has a large db, it may take some time to discard it
|
||||
# after receiving new db from the master. In this case, replica
|
||||
# should reply -LOADING. Replica may reply -LOADING while
|
||||
# loading the new db as well. To test the first case, populated
|
||||
# replica with large amount of keys and master with a few keys.
|
||||
# Discarding old db will take a long time and loading new one
|
||||
# will be quick. So, if we receive -LOADING, most probably it is
|
||||
# when flushing the db.
|
||||
wait_for_condition 1 10000 {
|
||||
[catch {$replica ping} err] &&
|
||||
[string match *LOADING* $err]
|
||||
} else {
|
||||
# There is a chance that we may not catch LOADING response
|
||||
# if flushing db happens too fast compared to test execution
|
||||
# Then, we may consider increasing key count or introducing
|
||||
# artificial delay to db flush.
|
||||
fail "Replica did not reply LOADING."
|
||||
}
|
||||
|
||||
catch {$replica shutdown nosave}
|
||||
}
|
||||
}
|
||||
} {} {repl external:skip}
|
||||
}
|
||||
|
||||
start_server {tags {"repl external:skip"} overrides {save {}}} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
populate 10000 master 10
|
||||
|
||||
start_server {overrides {save {} rdb-del-sync-files yes loading-process-events-interval-bytes 1024}} {
|
||||
test "Allow appendonly config change while loading rdb on slave" {
|
||||
set replica [srv 0 client]
|
||||
|
||||
# While loading rdb on slave, verify appendonly config changes are allowed
|
||||
# 1- Change appendonly config from no to yes
|
||||
$replica config set appendonly no
|
||||
$replica config set key-load-delay 100
|
||||
$replica debug populate 1000
|
||||
|
||||
# Start the replication process...
|
||||
$replica replicaof $master_host $master_port
|
||||
|
||||
wait_for_condition 10 1000 {
|
||||
[s loading] eq 1
|
||||
} else {
|
||||
fail "Replica didn't get into loading mode"
|
||||
}
|
||||
|
||||
# Change config while replica is loading data
|
||||
$replica config set appendonly yes
|
||||
assert_equal 1 [s loading]
|
||||
|
||||
# Speed up loading and verify aof is enabled
|
||||
$replica config set key-load-delay 0
|
||||
wait_done_loading $replica
|
||||
assert_equal 1 [s aof_enabled]
|
||||
|
||||
# Quick sanity for AOF
|
||||
$replica replicaof no one
|
||||
set prev [s aof_current_size]
|
||||
$replica set x 100
|
||||
assert_morethan [s aof_current_size] $prev
|
||||
|
||||
# 2- While loading rdb, change appendonly from yes to no
|
||||
$replica config set appendonly yes
|
||||
$replica config set key-load-delay 100
|
||||
$replica flushall
|
||||
|
||||
# Start the replication process...
|
||||
$replica replicaof $master_host $master_port
|
||||
|
||||
wait_for_condition 10 1000 {
|
||||
[s loading] eq 1
|
||||
} else {
|
||||
fail "Replica didn't get into loading mode"
|
||||
}
|
||||
|
||||
# Change config while replica is loading data
|
||||
$replica config set appendonly no
|
||||
assert_equal 1 [s loading]
|
||||
|
||||
# Speed up loading and verify aof is disabled
|
||||
$replica config set key-load-delay 0
|
||||
wait_done_loading $replica
|
||||
assert_equal 0 [s 0 aof_enabled]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl external:skip"}} {
|
||||
set replica [srv 0 client]
|
||||
start_server {} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
|
||||
test "Replica flushes db lazily when replica-lazy-flush enabled" {
|
||||
$replica config set replica-lazy-flush yes
|
||||
$replica debug populate 1000
|
||||
populate 1 master 10
|
||||
|
||||
# Start the replication process...
|
||||
$replica replicaof $master_host $master_port
|
||||
|
||||
wait_for_condition 100 100 {
|
||||
[s -1 lazyfreed_objects] >= 1000 &&
|
||||
[s -1 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replica did not free db lazily"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +156,11 @@ test "Shutting down master waits for replica then fails" {
|
||||
set rd2 [redis_deferring_client -1]
|
||||
$rd1 shutdown
|
||||
$rd2 shutdown
|
||||
wait_for_condition 100 10 {
|
||||
[llength [regexp -all -inline {cmd=shutdown} [$master client list]]] eq 2
|
||||
} else {
|
||||
fail "shutdown did not arrive"
|
||||
}
|
||||
set info_clients [$master info clients]
|
||||
assert_match "*connected_clients:3*" $info_clients
|
||||
assert_match "*blocked_clients:2*" $info_clients
|
||||
@@ -209,6 +214,11 @@ test "Shutting down master waits for replica then aborted" {
|
||||
set rd2 [redis_deferring_client -1]
|
||||
$rd1 shutdown
|
||||
$rd2 shutdown
|
||||
wait_for_condition 100 10 {
|
||||
[llength [regexp -all -inline {cmd=shutdown} [$master client list]]] eq 2
|
||||
} else {
|
||||
fail "shutdown did not arrive"
|
||||
}
|
||||
set info_clients [$master info clients]
|
||||
assert_match "*connected_clients:3*" $info_clients
|
||||
assert_match "*blocked_clients:2*" $info_clients
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "redismodule.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static RedisModuleType *FragType;
|
||||
|
||||
@@ -17,9 +18,12 @@ unsigned long int last_set_cursor = 0;
|
||||
|
||||
unsigned long int datatype_attempts = 0;
|
||||
unsigned long int datatype_defragged = 0;
|
||||
unsigned long int datatype_raw_defragged = 0;
|
||||
unsigned long int datatype_resumes = 0;
|
||||
unsigned long int datatype_wrong_cursor = 0;
|
||||
unsigned long int global_attempts = 0;
|
||||
unsigned long int defrag_started = 0;
|
||||
unsigned long int defrag_ended = 0;
|
||||
unsigned long int global_defragged = 0;
|
||||
|
||||
int global_strings_len = 0;
|
||||
@@ -47,16 +51,29 @@ static void defragGlobalStrings(RedisModuleDefragCtx *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
static void defragStart(RedisModuleDefragCtx *ctx) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
defrag_started++;
|
||||
}
|
||||
|
||||
static void defragEnd(RedisModuleDefragCtx *ctx) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
defrag_ended++;
|
||||
}
|
||||
|
||||
static void FragInfo(RedisModuleInfoCtx *ctx, int for_crash_report) {
|
||||
REDISMODULE_NOT_USED(for_crash_report);
|
||||
|
||||
RedisModule_InfoAddSection(ctx, "stats");
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "datatype_attempts", datatype_attempts);
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "datatype_defragged", datatype_defragged);
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "datatype_raw_defragged", datatype_raw_defragged);
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "datatype_resumes", datatype_resumes);
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "datatype_wrong_cursor", datatype_wrong_cursor);
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "global_attempts", global_attempts);
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "global_defragged", global_defragged);
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "defrag_started", defrag_started);
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "defrag_ended", defrag_ended);
|
||||
}
|
||||
|
||||
struct FragObject *createFragObject(unsigned long len, unsigned long size, int maxstep) {
|
||||
@@ -79,10 +96,13 @@ static int fragResetStatsCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
|
||||
datatype_attempts = 0;
|
||||
datatype_defragged = 0;
|
||||
datatype_raw_defragged = 0;
|
||||
datatype_resumes = 0;
|
||||
datatype_wrong_cursor = 0;
|
||||
global_attempts = 0;
|
||||
global_defragged = 0;
|
||||
defrag_started = 0;
|
||||
defrag_ended = 0;
|
||||
|
||||
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
return REDISMODULE_OK;
|
||||
@@ -141,13 +161,14 @@ size_t FragFreeEffort(RedisModuleString *key, const void *value) {
|
||||
}
|
||||
|
||||
int FragDefrag(RedisModuleDefragCtx *ctx, RedisModuleString *key, void **value) {
|
||||
REDISMODULE_NOT_USED(key);
|
||||
unsigned long i = 0;
|
||||
int steps = 0;
|
||||
|
||||
int dbid = RedisModule_GetDbIdFromDefragCtx(ctx);
|
||||
RedisModule_Assert(dbid != -1);
|
||||
|
||||
RedisModule_Log(NULL, "notice", "Defrag key: %s", RedisModule_StringPtrLen(key, NULL));
|
||||
|
||||
/* Attempt to get cursor, validate it's what we're exepcting */
|
||||
if (RedisModule_DefragCursorGet(ctx, &i) == REDISMODULE_OK) {
|
||||
if (i > 0) datatype_resumes++;
|
||||
@@ -188,6 +209,14 @@ int FragDefrag(RedisModuleDefragCtx *ctx, RedisModuleString *key, void **value)
|
||||
}
|
||||
}
|
||||
|
||||
/* Defrag the values array itself using RedisModule_DefragAllocRaw
|
||||
* and RedisModule_DefragFreeRaw for testing purposes. */
|
||||
void *new_values = RedisModule_DefragAllocRaw(ctx, o->len * sizeof(void*));
|
||||
memcpy(new_values, o->values, o->len * sizeof(void*));
|
||||
RedisModule_DefragFreeRaw(ctx, o->values);
|
||||
o->values = new_values;
|
||||
datatype_raw_defragged++;
|
||||
|
||||
last_set_cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -230,6 +259,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
|
||||
RedisModule_RegisterInfoFunc(ctx, FragInfo);
|
||||
RedisModule_RegisterDefragFunc(ctx, defragGlobalStrings);
|
||||
RedisModule_RegisterDefragCallbacks(ctx, defragStart, defragEnd);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user