Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81bed3ec00 | ||
|
|
80022b4c2c | ||
|
|
a3e1fe0cd8 | ||
|
|
01ee9f276d | ||
|
|
754e908e14 | ||
|
|
69bdda9fb8 | ||
|
|
7decd4f4b7 | ||
|
|
cb824a9c29 | ||
|
|
1aedfa455a | ||
|
|
98790669e5 | ||
|
|
cc73cf1615 | ||
|
|
8550b82ef2 | ||
|
|
1427741f3b | ||
|
|
c883ca9a08 | ||
|
|
8e360c7eb7 | ||
|
|
3e2fd3c800 | ||
|
|
40b32ac4a8 | ||
|
|
7464d45eaa | ||
|
|
37242267c9 | ||
|
|
28f1cc9b29 | ||
|
|
60fbe16c63 | ||
|
|
a09e12e27b | ||
|
|
728595058a | ||
|
|
6a6b6c8746 | ||
|
|
0d297bddb4 | ||
|
|
267e3a54f9 | ||
|
|
2f351c1b2b | ||
|
|
7661f53622 | ||
|
|
3cbf0abb9d | ||
|
|
34ce284e3d | ||
|
|
43f39b08d9 | ||
|
|
f3b496193b | ||
|
|
15d6dc347f | ||
|
|
110dc03968 | ||
|
|
fe11fcb2a8 | ||
|
|
99333f9a69 | ||
|
|
27f23b4d0e | ||
|
|
8a909a06e6 | ||
|
|
6c0bf7c539 | ||
|
|
24a0721daf | ||
|
|
04cf1f8550 | ||
|
|
f94c1a7edf | ||
|
|
da8d047a9b | ||
|
|
1ddaefbb93 | ||
|
|
d7d85ea831 | ||
|
|
3c9b27b476 | ||
|
|
5b25c93731 | ||
|
|
d598dbb991 | ||
|
|
84240f51c6 | ||
|
|
ab7498e020 | ||
|
|
76b4b4b47e | ||
|
|
3b1ae0afd0 | ||
|
|
2c5a748ba3 | ||
|
|
3d80bf6e6c | ||
|
|
75bb8bd3e4 | ||
|
|
178e9e99d2 | ||
|
|
1de84d9d4f | ||
|
|
8d062cc469 | ||
|
|
b454c95776 | ||
|
|
2eadce4ff5 | ||
|
|
218ad732a0 | ||
|
|
258060ddbb | ||
|
|
9142991068 | ||
|
|
6c4bb03dc5 | ||
|
|
ca82cdde9c | ||
|
|
0a33b19ea2 | ||
|
|
4147dd14f8 | ||
|
|
c91cd8c926 | ||
|
|
347e1168b5 | ||
|
|
3f9fc9ff69 | ||
|
|
803fc6e7c6 | ||
|
|
b3ae643246 | ||
|
|
5a2509b743 | ||
|
|
dc31c5f91a | ||
|
|
d9b200a02e | ||
|
|
df1738b4d7 | ||
|
|
8848a9ca9c | ||
|
|
ff1c26159a | ||
|
|
eed79cb6cc | ||
|
|
e00129eb87 | ||
|
|
9239bf28db | ||
|
|
837a48cef1 | ||
|
|
d3240a9b8d | ||
|
|
cd67bc9494 | ||
|
|
5d27f8335a | ||
|
|
8c0c6b2044 | ||
|
|
1303dc2fbb | ||
|
|
259921486b | ||
|
|
fa232d72bb | ||
|
|
c16c83bd34 | ||
|
|
2b594a69f0 | ||
|
|
ed85c734c2 | ||
|
|
bb89628d19 | ||
|
|
971e5f5c30 | ||
|
|
9568ee4f05 | ||
|
|
653e330af4 | ||
|
|
f6ac10dcef | ||
|
|
fe7e4392ef | ||
|
|
ffaa9ad1d7 | ||
|
|
255b827f35 | ||
|
|
aa06454d3f | ||
|
|
3550a651fd | ||
|
|
276cbd7c43 | ||
|
|
ef1da0f81c | ||
|
|
136852efcc | ||
|
|
2622aa8e03 | ||
|
|
9a51cc390a | ||
|
|
be048d4246 | ||
|
|
c1b6c248b3 | ||
|
|
05fb04465a | ||
|
|
4969ccc08a | ||
|
|
811abbf18f | ||
|
|
6b2411da04 | ||
|
|
e97d127fb2 | ||
|
|
da206c609a | ||
|
|
ce291b3613 | ||
|
|
c84990bf56 | ||
|
|
3110d910ef | ||
|
|
3107c4eb21 | ||
|
|
1b52016873 | ||
|
|
ea942753c4 | ||
|
|
aa39375bdf | ||
|
|
c3020e0a11 | ||
|
|
617643f35e | ||
|
|
63f37ee960 | ||
|
|
851ca54c66 | ||
|
|
b32c68a66f | ||
|
|
6b81e36c9d | ||
|
|
af7bac374b | ||
|
|
fd65d487eb | ||
|
|
a737a11d66 | ||
|
|
77ff9eb0e3 | ||
|
|
5a97f83b5b | ||
|
|
274deca160 | ||
|
|
bdefaa54ff | ||
|
|
d3ae62cc9c |
+5
-1
@@ -5,7 +5,7 @@ extend-exclude = [
|
||||
".git/",
|
||||
"deps/",
|
||||
# crc16_slottable is primarily pre-generated random strings.
|
||||
"src/crc16_slottable.h",
|
||||
"src/crc16_slottable.c",
|
||||
]
|
||||
ignore-hidden = false
|
||||
|
||||
@@ -14,6 +14,7 @@ exat = "exat"
|
||||
optin = "optin"
|
||||
smove = "smove"
|
||||
Parth = "Parth" # seems like the spellchecker does not like it is similar to "Path"
|
||||
NAM = "NAM" # Name similar to Name
|
||||
nd = "nd"
|
||||
|
||||
[default]
|
||||
@@ -71,3 +72,6 @@ ake = "ake"
|
||||
[type.tcl.extend-words]
|
||||
fo = "fo"
|
||||
tre = "tre"
|
||||
|
||||
[type.cpp.extend-words]
|
||||
fo = "fo"
|
||||
|
||||
@@ -36,7 +36,7 @@ Apply these standards to core engine C code. Do NOT apply to `deps/` (vendored d
|
||||
- **PR Scope:** Separate refactoring from functional changes for easier backporting.
|
||||
|
||||
## 5. Testing & Documentation
|
||||
- **Unit Tests:** Required for data structures in `src/unit/`. Test files should follow `test_*.c` naming.
|
||||
- **Unit Tests:** Required for data structures in `src/unit/`. Test files should follow `test_*.cpp` naming.
|
||||
- **Integration Tests:** Required for commands in `tests/`.
|
||||
- **Command Changes:** New/modified commands need corresponding updates in `src/commands/*.json`.
|
||||
- **New C Files:** Remind to update `CMakeLists.txt` when adding new `.c` source files.
|
||||
|
||||
@@ -5,8 +5,8 @@ on:
|
||||
types: [labeled]
|
||||
|
||||
concurrency:
|
||||
group: ec2-al-2023-pr-benchmarking-arm64
|
||||
cancel-in-progress: false
|
||||
group: benchmark-${{ github.event.pull_request.number }}-${{ github.event.label.name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -20,7 +20,8 @@ permissions:
|
||||
jobs:
|
||||
benchmark:
|
||||
if: |
|
||||
github.event.action == 'labeled' && github.event.label.name == 'run-benchmark' &&
|
||||
github.event.action == 'labeled' &&
|
||||
(github.event.label.name == 'run-benchmark' || github.event.label.name == 'run-cluster-benchmark') &&
|
||||
github.repository == 'valkey-io/valkey'
|
||||
|
||||
runs-on: ["self-hosted", "ec2-al-2023-pr-benchmarking-arm64"]
|
||||
@@ -66,7 +67,7 @@ jobs:
|
||||
openssl-devel \
|
||||
bzip2-devel \
|
||||
libffi-devel
|
||||
pip install -r requirements.txt
|
||||
pip install --require-hashes -r requirements.txt
|
||||
|
||||
- name: Build latest valkey_latest
|
||||
working-directory: valkey_latest
|
||||
@@ -86,6 +87,15 @@ jobs:
|
||||
echo "VALKEY_BENCHMARK_PATH=$VALKEY_BENCHMARK_PATH" >> $GITHUB_ENV
|
||||
echo "Latest valkey-benchmark path: $VALKEY_BENCHMARK_PATH"
|
||||
|
||||
- name: Enable cluster mode in benchmark config
|
||||
working-directory: valkey-perf-benchmark
|
||||
if: github.event.label.name == 'run-cluster-benchmark'
|
||||
run: |
|
||||
CONFIG_FILE="../valkey/.github/benchmark_configs/benchmark-config-arm.json"
|
||||
sed -i 's/"cluster_mode": false/"cluster_mode": true/g' "$CONFIG_FILE"
|
||||
echo "Updated config for cluster mode:"
|
||||
cat "$CONFIG_FILE"
|
||||
|
||||
- name: Run benchmarks
|
||||
working-directory: valkey-perf-benchmark
|
||||
run: |
|
||||
@@ -100,7 +110,7 @@ jobs:
|
||||
--target-ip ${{ secrets.EC2_ARM64_IP }}
|
||||
--valkey-path "../valkey"
|
||||
--results-dir "results"
|
||||
--runs 5
|
||||
--runs 3
|
||||
)
|
||||
|
||||
# Run benchmark
|
||||
@@ -120,10 +130,9 @@ jobs:
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: benchmark-results
|
||||
name: ${{ github.event.label.name }}-results
|
||||
path: |
|
||||
./valkey-perf-benchmark/results/${{ github.event.pull_request.merge_commit_sha }}/metrics.json
|
||||
./valkey-perf-benchmark/results/${{ github.event.pull_request.base.ref }}/metrics.json
|
||||
./valkey-perf-benchmark/results/
|
||||
comparison.md
|
||||
|
||||
- name: Comment PR with results
|
||||
@@ -137,11 +146,13 @@ jobs:
|
||||
const sha = '${{ github.event.pull_request.head.sha }}';
|
||||
const short = sha.slice(0,7);
|
||||
const link = `[\`${short}\`](https://github.com/${owner}/${repo}/commit/${sha})`
|
||||
const label = '${{ github.event.label.name }}';
|
||||
const prefix = label === 'run-cluster-benchmark' ? 'Cluster Benchmark' : 'Benchmark';
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner,
|
||||
repo,
|
||||
body: `**Benchmark ran on this commit:** ${link}\n\n${body}`
|
||||
body: `**${prefix} ran on this commit:** ${link}\n\n${body}`
|
||||
});
|
||||
|
||||
- name: Cleanup any running valkey processes
|
||||
@@ -160,7 +171,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Remove ${{ github.event.label.name }} label
|
||||
if: always() && github.event.label.name == 'run-benchmark'
|
||||
if: always() && (github.event.label.name == 'run-benchmark' || github.event.label.name == 'run-cluster-benchmark')
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
openssl-devel \
|
||||
bzip2-devel \
|
||||
libffi-devel
|
||||
pip install -r requirements.txt
|
||||
pip install --require-hashes -r requirements.txt
|
||||
|
||||
- name: Build latest valkey_latest
|
||||
working-directory: valkey_latest
|
||||
|
||||
+51
-15
@@ -35,7 +35,9 @@ jobs:
|
||||
- name: make
|
||||
# Fail build if there are warnings
|
||||
# build with TLS just for compilation coverage
|
||||
run: make -j4 all-with-unit-tests SERVER_CFLAGS='-Werror' BUILD_TLS=yes USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes
|
||||
run: |
|
||||
sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
make -j4 all-with-unit-tests SERVER_CFLAGS='-Werror' BUILD_TLS=yes USE_LIBBACKTRACE=yes
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.6 tclx
|
||||
@@ -50,7 +52,7 @@ jobs:
|
||||
if [[ ! -z "$dirty" ]]; then echo "$dirty"; exit 1; fi
|
||||
- name: unit tests
|
||||
run: |
|
||||
./src/valkey-unit-tests
|
||||
make test-unit
|
||||
|
||||
test-ubuntu-latest-compatibility:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -62,6 +64,9 @@ jobs:
|
||||
- {version: "8.0.6", file: "valkey-8.0.6-noble-x86_64.tar.gz"}
|
||||
- {version: "8.1.4", file: "valkey-8.1.4-noble-x86_64.tar.gz"}
|
||||
steps:
|
||||
- name: Install gtest
|
||||
run: |
|
||||
sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -74,7 +79,7 @@ jobs:
|
||||
- name: make
|
||||
# Fail build if there are warnings
|
||||
# build with TLS just for compilation coverage
|
||||
run: make -j4 all-with-unit-tests SERVER_CFLAGS='-Werror' BUILD_TLS=yes USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes
|
||||
run: make -j4 all-with-unit-tests SERVER_CFLAGS='-Werror' BUILD_TLS=yes USE_LIBBACKTRACE=yes
|
||||
|
||||
- name: Install old server (${{ matrix.server.version }}) for compatibility testing
|
||||
run: |
|
||||
@@ -103,7 +108,7 @@ jobs:
|
||||
sudo apt-get install -y cmake libssl-dev
|
||||
mkdir -p build-release
|
||||
cd build-release
|
||||
cmake -DCMAKE_BUILD_TYPE=Release .. -DBUILD_TLS=yes -DBUILD_UNIT_TESTS=yes
|
||||
cmake -DCMAKE_BUILD_TYPE=Release .. -DBUILD_TLS=yes -DBUILD_UNIT_GTESTS=yes
|
||||
make -j$(nproc)
|
||||
- name: test
|
||||
run: |
|
||||
@@ -111,8 +116,7 @@ jobs:
|
||||
./utils/gen-test-certs.sh
|
||||
./build-release/runtest --verbose --tags -slow --dump-logs --tls
|
||||
- name: unit tests
|
||||
run: |
|
||||
./build-release/bin/valkey-unit-tests
|
||||
run: make -C build-release test-unit
|
||||
|
||||
test-sanitizer-address:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -128,7 +132,9 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: make
|
||||
# build with TLS module just for compilation coverage
|
||||
run: make -j4 all-with-unit-tests SANITIZER=address SERVER_CFLAGS='-Werror' BUILD_TLS=module USE_LIBBACKTRACE=yes
|
||||
run: |
|
||||
sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
make -j4 all-with-unit-tests SANITIZER=address SERVER_CFLAGS='-Werror' BUILD_TLS=module USE_LIBBACKTRACE=yes
|
||||
- name: testprep
|
||||
run: sudo apt-get install tcl8.6 tclx -y
|
||||
- name: test
|
||||
@@ -136,7 +142,8 @@ jobs:
|
||||
- name: module api test
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs
|
||||
- name: unit tests
|
||||
run: ./src/valkey-unit-tests
|
||||
run: |
|
||||
make test-unit
|
||||
|
||||
test-rdma:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -160,6 +167,7 @@ jobs:
|
||||
make -j4 BUILD_RDMA=yes USE_LIBBACKTRACE=yes
|
||||
- name: clone-rxe-kmod
|
||||
run: |
|
||||
uname -a
|
||||
mkdir -p tests/rdma/rxe
|
||||
git clone https://github.com/pizhenwei/rxe.git tests/rdma/rxe
|
||||
make -C tests/rdma/rxe
|
||||
@@ -220,9 +228,17 @@ jobs:
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Checkout Valkey
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install build dependencies
|
||||
run: brew install llvm googletest
|
||||
- name: make
|
||||
# Build with additional upcoming features
|
||||
run: make -j3 all-with-unit-tests SERVER_CFLAGS='-Werror' USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes
|
||||
run: |
|
||||
export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
|
||||
export CC=/opt/homebrew/opt/llvm/bin/clang
|
||||
export CXX=/opt/homebrew/opt/llvm/bin/clang++
|
||||
export AR=/opt/homebrew/opt/llvm/bin/llvm-ar
|
||||
export RANLIB=/opt/homebrew/opt/llvm/bin/llvm-ranlib
|
||||
make -j3 all-with-unit-tests SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes LIBBACKTRACE_PREFIX=/usr/local
|
||||
|
||||
build-32bit:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -240,16 +256,36 @@ jobs:
|
||||
cd libbacktrace && ./configure CFLAGS="-m32" --prefix=/usr/local/libbacktrace32 && make && sudo make install
|
||||
- name: Checkout Valkey
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install gtest
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libgtest-dev
|
||||
mkdir -p /tmp/gtest32
|
||||
cd /tmp/gtest32
|
||||
cmake -B build32 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_FLAGS="-m32" \
|
||||
-DCMAKE_CXX_FLAGS="-m32" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-m32" \
|
||||
/usr/src/googletest
|
||||
cmake --build build32 --parallel
|
||||
sudo cp build32/lib/*.a /usr/lib32/
|
||||
cd $GITHUB_WORKSPACE
|
||||
- name: make
|
||||
# Fast float requires C++ 32-bit libraries to compile on 64-bit ubuntu
|
||||
# machine i.e. "-cross" suffixed version. Cross-compiling c++ to 32-bit
|
||||
# also requires multilib support for g++ compiler i.e. "-multilib"
|
||||
# suffixed version of g++. g++-multilib generally includes libstdc++.
|
||||
# *cross version as well, but it is also added explicitly just in case.
|
||||
run: make -j4 SERVER_CFLAGS='-Werror' 32bit USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes LIBBACKTRACE_PREFIX=/usr/local/libbacktrace32
|
||||
run: |
|
||||
make -j4 SERVER_CFLAGS='-Werror' 32bit USE_LIBBACKTRACE=yes LIBBACKTRACE_PREFIX=/usr/local/libbacktrace32 \
|
||||
GTEST_CFLAGS="-I/usr/src/googletest/googletest/include -I/usr/src/googletest/googlemock/include" \
|
||||
GTEST_LIBS="/usr/lib32/libgtest.a /usr/lib32/libgmock.a"
|
||||
- name: unit tests
|
||||
run: |
|
||||
./src/valkey-unit-tests
|
||||
make test-unit \
|
||||
GTEST_CFLAGS="-I/usr/src/googletest/googletest/include -I/usr/src/googletest/googlemock/include" \
|
||||
GTEST_LIBS="/usr/lib32/libgtest.a /usr/lib32/libgmock.a"
|
||||
|
||||
build-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -264,7 +300,7 @@ jobs:
|
||||
- name: Checkout Valkey
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: make
|
||||
run: make -j4 SERVER_CFLAGS='-Werror' MALLOC=libc USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes
|
||||
run: make -j4 SERVER_CFLAGS='-Werror' MALLOC=libc USE_LIBBACKTRACE=yes
|
||||
|
||||
build-almalinux8-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -278,12 +314,12 @@ jobs:
|
||||
path: libbacktrace
|
||||
- name: Build libbacktrace
|
||||
run: |
|
||||
dnf -y install epel-release gcc gcc-c++ make procps-ng which
|
||||
dnf -y install epel-release gcc gcc-c++ make procps-ng which git cmake
|
||||
cd libbacktrace && ./configure && make && make install
|
||||
- name: Checkout Valkey
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: make
|
||||
run: make -j4 SERVER_CFLAGS='-Werror' USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes
|
||||
run: make -j4 SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
|
||||
|
||||
format-yaml:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -298,7 +334,7 @@ jobs:
|
||||
|
||||
- name: Setup YAML formatter
|
||||
run: |
|
||||
go install github.com/google/yamlfmt/cmd/yamlfmt@latest
|
||||
go install github.com/google/yamlfmt/cmd/yamlfmt@v0.21.0
|
||||
|
||||
- name: Run yamlfmt
|
||||
id: yamlfmt
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: Clang Format Check
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
@@ -39,7 +42,7 @@ jobs:
|
||||
# Run clang-format and capture the diff
|
||||
cd src
|
||||
shopt -s globstar
|
||||
clang-format-18 -i **/*.c **/*.h
|
||||
clang-format-18 -i **/*.c **/*.h **/*.cpp **/*.hpp
|
||||
# Capture the diff output
|
||||
DIFF=$(git diff)
|
||||
if [ ! -z "$DIFF" ]; then
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: "Codecov"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Enabling on each push is to display the coverage changes in every PR,
|
||||
# where each PR needs to be compared against the coverage of the head commit
|
||||
on:
|
||||
@@ -32,6 +35,12 @@ jobs:
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Checkout Valkey
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install gtest
|
||||
run: |
|
||||
git clone --depth 1 -b v1.17.x https://github.com/google/googletest.git
|
||||
cmake -S googletest -B googletest/build
|
||||
cmake --build googletest/build -- -j$(nproc)
|
||||
sudo cmake --install googletest/build
|
||||
- name: Install lcov and run test
|
||||
run: |
|
||||
sudo apt-get install lcov tclx
|
||||
@@ -41,4 +50,4 @@ jobs:
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./src/valkey.info
|
||||
files: ./src/valkey.info
|
||||
|
||||
@@ -40,12 +40,12 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b5ebac6f4c00c8ccddb7cdcd45fdb248329f808a # v3.32.2
|
||||
uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@b5ebac6f4c00c8ccddb7cdcd45fdb248329f808a # v3.32.2
|
||||
uses: github/codeql-action/autobuild@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b5ebac6f4c00c8ccddb7cdcd45fdb248329f808a # v3.32.2
|
||||
uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5
|
||||
|
||||
+231
-88
@@ -101,8 +101,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -110,6 +110,8 @@ jobs:
|
||||
ref: b9e40069c0b47a722286b94eb5231f7f05c08713
|
||||
path: libbacktrace
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Install gtest
|
||||
run: sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make all-with-unit-tests SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
|
||||
- name: testprep
|
||||
@@ -128,7 +130,12 @@ jobs:
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --accurate
|
||||
run: |
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
make test-unit accurate=1
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
./src/valkey-unit-tests --accurate
|
||||
fi
|
||||
- name: install Redis OSS 6.2 server for compatibility testing
|
||||
run: |
|
||||
cd tests/tmp
|
||||
@@ -180,8 +187,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -189,6 +196,8 @@ jobs:
|
||||
ref: b9e40069c0b47a722286b94eb5231f7f05c08713
|
||||
path: libbacktrace
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Install gtest
|
||||
run: sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make all-with-unit-tests SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
|
||||
- name: testprep
|
||||
@@ -207,7 +216,12 @@ jobs:
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --accurate
|
||||
run: |
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
make test-unit accurate=1
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
./src/valkey-unit-tests --accurate
|
||||
fi
|
||||
test-ubuntu-jemalloc-fortify:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -234,8 +248,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -244,9 +258,11 @@ jobs:
|
||||
path: libbacktrace
|
||||
- name: Build libbacktrace
|
||||
run: |
|
||||
apt-get update && apt-get install -y make gcc-13
|
||||
apt-get update && apt-get install -y make gcc-13 git cmake g++ python3
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100
|
||||
cd libbacktrace && ./configure && make && make install
|
||||
- name: Install gtest
|
||||
run: apt-get install -y pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make all-with-unit-tests CC=gcc OPT=-O3 SERVER_CFLAGS='-Werror -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3' USE_LIBBACKTRACE=yes
|
||||
- name: testprep
|
||||
@@ -265,7 +281,12 @@ jobs:
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --accurate
|
||||
run: |
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
make test-unit accurate=1
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
./src/valkey-unit-tests --accurate
|
||||
fi
|
||||
test-ubuntu-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -291,8 +312,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -341,8 +362,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -391,8 +412,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -401,10 +422,28 @@ jobs:
|
||||
path: libbacktrace
|
||||
- name: Build libbacktrace
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386 g++-multilib
|
||||
cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Install gtest
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libgtest-dev
|
||||
mkdir -p /tmp/gtest32
|
||||
cd /tmp/gtest32
|
||||
cmake -B build32 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_FLAGS="-m32" \
|
||||
-DCMAKE_CXX_FLAGS="-m32" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-m32" \
|
||||
/usr/src/googletest
|
||||
cmake --build build32 --parallel
|
||||
sudo cp build32/lib/*.a /usr/lib32/
|
||||
cd $GITHUB_WORKSPACE
|
||||
- name: make
|
||||
run: make 32bit SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
|
||||
run: |
|
||||
make 32bit SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes \
|
||||
GTEST_CFLAGS="-I/usr/src/googletest/googletest/include -I/usr/src/googletest/googlemock/include" \
|
||||
GTEST_LIBS="/usr/lib32/libgtest.a /usr/lib32/libgmock.a"
|
||||
- name: testprep
|
||||
run: sudo apt-get install tcl8.6 tclx
|
||||
- name: test
|
||||
@@ -423,7 +462,14 @@ jobs:
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --accurate
|
||||
run: |
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
make test-unit accurate=1 \
|
||||
GTEST_CFLAGS="-I/usr/src/googletest/googletest/include -I/usr/src/googletest/googlemock/include" \
|
||||
GTEST_LIBS="/usr/lib32/libgtest.a /usr/lib32/libgmock.a"
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
./src/valkey-unit-tests --accurate
|
||||
fi
|
||||
test-ubuntu-tls:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -449,8 +495,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -505,8 +551,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -515,7 +561,7 @@ jobs:
|
||||
path: libbacktrace
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: make
|
||||
run: make BUILD_TLS=yes SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes USE_FAST_FLOAT=yes
|
||||
run: make BUILD_TLS=yes SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get install tcl8.6 tclx tcl-tls
|
||||
@@ -561,8 +607,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -605,8 +651,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -653,8 +699,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -737,8 +783,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -775,8 +821,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -784,6 +830,8 @@ jobs:
|
||||
ref: b9e40069c0b47a722286b94eb5231f7f05c08713
|
||||
path: libbacktrace
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Install gtest
|
||||
run: sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make valgrind all-with-unit-tests SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
|
||||
- name: testprep
|
||||
@@ -796,8 +844,13 @@ jobs:
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: |
|
||||
valgrind --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.txt ./src/valkey-unit-tests --valgrind
|
||||
if grep -q 0x err.txt; then cat err.txt; exit 1; fi
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
./deps/gtest-parallel/gtest-parallel valgrind -- --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.%p.txt ./src/unit/valkey-unit-gtests --valgrind
|
||||
if grep -qlE '0x[0-9A-Fa-f]+:' err.*.txt 2>/dev/null; then grep -lE '0x[0-9A-Fa-f]+:' err.*.txt | xargs cat; exit 1; fi
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
valgrind --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.txt ./src/valkey-unit-tests --valgrind
|
||||
if grep -q 0x err.txt; then cat err.txt; exit 1; fi
|
||||
fi
|
||||
test-valgrind-no-malloc-usable-size-test:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -818,8 +871,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -856,8 +909,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -865,6 +918,8 @@ jobs:
|
||||
ref: b9e40069c0b47a722286b94eb5231f7f05c08713
|
||||
path: libbacktrace
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Install gtest
|
||||
run: sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make valgrind all-with-unit-tests CFLAGS="-DNO_MALLOC_USABLE_SIZE" SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
|
||||
- name: testprep
|
||||
@@ -877,8 +932,13 @@ jobs:
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: |
|
||||
valgrind --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.txt ./src/valkey-unit-tests --valgrind
|
||||
if grep -q 0x err.txt; then cat err.txt; exit 1; fi
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
./deps/gtest-parallel/gtest-parallel valgrind -- --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.%p.txt ./src/unit/valkey-unit-gtests --valgrind
|
||||
if grep -qlE '0x[0-9A-Fa-f]+:' err.*.txt 2>/dev/null; then grep -lE '0x[0-9A-Fa-f]+:' err.*.txt | xargs cat; exit 1; fi
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
valgrind --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.txt ./src/valkey-unit-tests --valgrind
|
||||
if grep -q 0x err.txt; then cat err.txt; exit 1; fi
|
||||
fi
|
||||
test-sanitizer-address:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -905,8 +965,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -914,6 +974,8 @@ jobs:
|
||||
ref: b9e40069c0b47a722286b94eb5231f7f05c08713
|
||||
path: libbacktrace
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Install gtest
|
||||
run: sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make all-with-unit-tests OPT=-O3 SANITIZER=address SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
|
||||
- name: testprep
|
||||
@@ -934,7 +996,15 @@ jobs:
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests
|
||||
run: |
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
make test-unit
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
./src/valkey-unit-tests
|
||||
fi
|
||||
# Large-memory tests with sanitizers require 10-14GB RAM due to ASAN/UBSAN overhead.
|
||||
# GitHub-hosted runners for public repos provide 16GB (ubuntu-latest).
|
||||
# These tests are borderline - monitoring memory usage to determine if they can run reliably.
|
||||
test-sanitizer-address-large-memory:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -960,25 +1030,53 @@ jobs:
|
||||
echo "skiptests: ${{github.event.inputs.skiptests}}"
|
||||
echo "test_args: ${{github.event.inputs.test_args}}"
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- name: Log runner memory
|
||||
run: free -h
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install gtest
|
||||
run: sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make all-with-unit-tests OPT=-O3 SANITIZER=address SERVER_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 tclx -y
|
||||
- name: Start memory monitor
|
||||
run: |
|
||||
# Track minimum free memory to detect OOM risk
|
||||
(while true; do
|
||||
FREE=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
|
||||
echo "$FREE" >> /tmp/memfree.log
|
||||
sleep 5
|
||||
done) &
|
||||
echo $! > /tmp/memmon.pid
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --large-memory
|
||||
run: |
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
make test-unit large_memory=1
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
./src/valkey-unit-tests --large-memory
|
||||
fi
|
||||
- name: large memory tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'valkey')
|
||||
run: ./runtest --accurate --verbose --dump-logs --clients 5 --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
run: ./runtest --accurate --verbose --dump-logs --clients 1 --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
- name: large memory module api tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --clients 5 --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --clients 1 --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
- name: Memory usage summary
|
||||
if: always()
|
||||
run: |
|
||||
kill $(cat /tmp/memmon.pid) 2>/dev/null || true
|
||||
echo "=== Memory Summary ==="
|
||||
printf "Total RAM: %.1fGB\n" $(awk '/MemTotal/ {print $2/1024/1024}' /proc/meminfo)
|
||||
if [ -f /tmp/memfree.log ]; then
|
||||
MIN_FREE=$(sort -n /tmp/memfree.log | head -1)
|
||||
printf "Minimum free memory: %.1fGB\n" $(echo "$MIN_FREE/1024/1024" | bc -l)
|
||||
fi
|
||||
test-sanitizer-undefined:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -1005,8 +1103,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1014,6 +1112,8 @@ jobs:
|
||||
ref: b9e40069c0b47a722286b94eb5231f7f05c08713
|
||||
path: libbacktrace
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Install gtest
|
||||
run: sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make all-with-unit-tests OPT=-O3 SANITIZER=undefined SERVER_CFLAGS='-Werror' LUA_DEBUG=yes USE_LIBBACKTRACE=yes # we (ab)use this flow to also check Lua C API violations
|
||||
- name: testprep
|
||||
@@ -1034,7 +1134,15 @@ jobs:
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --accurate
|
||||
run: |
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
make test-unit accurate=1
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
./src/valkey-unit-tests --accurate
|
||||
fi
|
||||
# Large-memory tests with sanitizers require 10-14GB RAM due to ASAN/UBSAN overhead.
|
||||
# GitHub-hosted runners for public repos provide 16GB (ubuntu-latest).
|
||||
# These tests are borderline - monitoring memory usage to determine if they can run reliably.
|
||||
test-sanitizer-undefined-large-memory:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -1060,25 +1168,53 @@ jobs:
|
||||
echo "skiptests: ${{github.event.inputs.skiptests}}"
|
||||
echo "test_args: ${{github.event.inputs.test_args}}"
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- name: Log runner memory
|
||||
run: free -h
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install gtest
|
||||
run: sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make all-with-unit-tests OPT=-O3 SANITIZER=undefined SERVER_CFLAGS='-Werror' LUA_DEBUG=yes
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 tclx -y
|
||||
- name: Start memory monitor
|
||||
run: |
|
||||
# Track minimum free memory to detect OOM risk
|
||||
(while true; do
|
||||
FREE=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
|
||||
echo "$FREE" >> /tmp/memfree.log
|
||||
sleep 5
|
||||
done) &
|
||||
echo $! > /tmp/memmon.pid
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --accurate --large-memory
|
||||
run: |
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
make test-unit accurate=1 large_memory=1
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
./src/valkey-unit-tests --accurate --large-memory
|
||||
fi
|
||||
- name: large memory tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'valkey')
|
||||
run: ./runtest --accurate --verbose --dump-logs --clients 5 --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
run: ./runtest --accurate --verbose --dump-logs --clients 1 --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
- name: large memory module api tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --clients 5 --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --clients 1 --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
- name: Memory usage summary
|
||||
if: always()
|
||||
run: |
|
||||
kill $(cat /tmp/memmon.pid) 2>/dev/null || true
|
||||
echo "=== Memory Summary ==="
|
||||
printf "Total RAM: %.1fGB\n" $(awk '/MemTotal/ {print $2/1024/1024}' /proc/meminfo)
|
||||
if [ -f /tmp/memfree.log ]; then
|
||||
MIN_FREE=$(sort -n /tmp/memfree.log | head -1)
|
||||
printf "Minimum free memory: %.1fGB\n" $(echo "$MIN_FREE/1024/1024" | bc -l)
|
||||
fi
|
||||
test-sanitizer-force-defrag:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -1101,8 +1237,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1110,6 +1246,8 @@ jobs:
|
||||
ref: b9e40069c0b47a722286b94eb5231f7f05c08713
|
||||
path: libbacktrace
|
||||
- run: cd libbacktrace && ./configure && make && sudo make install
|
||||
- name: Install gtest
|
||||
run: sudo apt-get install pkg-config libgtest-dev libgmock-dev
|
||||
- name: make
|
||||
run: make all-with-unit-tests OPT=-O3 SANITIZER=address DEBUG_FORCE_DEFRAG=yes USE_JEMALLOC=no SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
|
||||
- name: testprep
|
||||
@@ -1130,7 +1268,12 @@ jobs:
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests
|
||||
run: |
|
||||
if [ -f ./src/unit/valkey-unit-gtests ]; then
|
||||
make test-unit
|
||||
elif [ -f ./src/valkey-unit-tests ]; then
|
||||
./src/valkey-unit-tests
|
||||
fi
|
||||
test-ubuntu-lttng:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -1156,8 +1299,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1203,7 +1346,7 @@ jobs:
|
||||
container: almalinux:8
|
||||
install_epel: true
|
||||
- name: test-almalinux9-jemalloc
|
||||
container: almalinux:8
|
||||
container: almalinux:9
|
||||
install_epel: true
|
||||
- name: test-centosstream9-jemalloc
|
||||
container: quay.io/centos/centos:stream9
|
||||
@@ -1228,8 +1371,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install EPEL
|
||||
if: matrix.install_epel
|
||||
run: dnf -y install epel-release
|
||||
@@ -1278,7 +1421,7 @@ jobs:
|
||||
container: almalinux:8
|
||||
install_epel: true
|
||||
- name: test-almalinux9-tls-module
|
||||
container: almalinux:8
|
||||
container: almalinux:9
|
||||
install_epel: true
|
||||
- name: test-centosstream9-tls-module
|
||||
container: quay.io/centos/centos:stream9
|
||||
@@ -1303,8 +1446,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install EPEL
|
||||
if: matrix.install_epel
|
||||
run: dnf -y install epel-release
|
||||
@@ -1354,7 +1497,7 @@ jobs:
|
||||
container: almalinux:8
|
||||
install_epel: true
|
||||
- name: test-almalinux9-tls-module-no-tls
|
||||
container: almalinux:8
|
||||
container: almalinux:9
|
||||
install_epel: true
|
||||
- name: test-centosstream9-tls-module-no-tls
|
||||
container: quay.io/centos/centos:stream9
|
||||
@@ -1379,8 +1522,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install EPEL
|
||||
if: matrix.install_epel
|
||||
run: dnf -y install epel-release
|
||||
@@ -1441,8 +1584,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1483,8 +1626,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1522,8 +1665,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1568,8 +1711,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1600,8 +1743,8 @@ jobs:
|
||||
echo "GITHUB_HEAD_REF=${{inputs.use_git_ref || github.event.inputs.use_git_ref}}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: test
|
||||
uses: cross-platform-actions/action@39a2a80642eca0947594ad03e4355dc3d28c617a # v0.32.0
|
||||
with:
|
||||
@@ -1638,8 +1781,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1691,8 +1834,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1744,8 +1887,8 @@ jobs:
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_REPOSITORY) || github.repository }}
|
||||
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && env.GITHUB_HEAD_REF) || github.ref }}
|
||||
repository: ${{ inputs.use_repo || github.event.inputs.use_repo || github.repository }}
|
||||
ref: ${{ inputs.use_git_ref || github.event.inputs.use_git_ref || github.ref }}
|
||||
- name: Install libbacktrace
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
|
||||
@@ -23,7 +23,8 @@ jobs:
|
||||
- name: Setup nodejs
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
- name: Install packages
|
||||
run: npm install ajv
|
||||
working-directory: utils/reply-schema-linter
|
||||
run: npm ci --ignore-scripts
|
||||
- name: linter
|
||||
run: node ./utils/reply_schema_linter.js
|
||||
run: NODE_PATH=utils/reply-schema-linter/node_modules node ./utils/reply_schema_linter.js
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: OpenSSF Scorecard supply-chain security
|
||||
on:
|
||||
push:
|
||||
branches: [unstable]
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a #v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
publish_results: true
|
||||
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@6bc82e05fd0ea64601dd4b465378bbcf57de0314 #v4.32.1
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -1,5 +1,8 @@
|
||||
name: Trigger Build Release
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
+3
-1
@@ -15,7 +15,7 @@ dump*.rdb
|
||||
*-cli
|
||||
*-sentinel
|
||||
*-server
|
||||
*-unit-tests
|
||||
*-unit-gtests
|
||||
doc-tools
|
||||
release
|
||||
misc/*
|
||||
@@ -56,3 +56,5 @@ build-debug/
|
||||
build-release/
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
__pycache__
|
||||
src/unit/.flags
|
||||
|
||||
+192
-11
@@ -1,16 +1,197 @@
|
||||
Hello! This file is just a placeholder, since this is the "unstable" branch
|
||||
of Valkey, the place where all the development happens.
|
||||
Valkey 9.1 release notes
|
||||
========================
|
||||
|
||||
There is no release notes for this branch, it gets forked into another branch
|
||||
every time there is a partial feature freeze in order to eventually create
|
||||
a new stable release.
|
||||
Upgrade urgency levels:
|
||||
|
||||
Usually "unstable" is stable enough for you to use it in development environments
|
||||
however you should never use it in production environments. It is possible
|
||||
to download the latest stable release here:
|
||||
| Level | Meaning |
|
||||
|----------|---------------------------------------------------------------------|
|
||||
| LOW | No need to upgrade unless there are new features you want to use. |
|
||||
| MODERATE | Program an upgrade of the server, but it's not urgent. |
|
||||
| HIGH | There is a critical bug that may affect a subset of users. Upgrade! |
|
||||
| CRITICAL | There is a critical bug affecting MOST USERS. Upgrade ASAP. |
|
||||
| SECURITY | There are security fixes in the release. |
|
||||
|
||||
https://valkey.io/download/
|
||||
Valkey 9.1.0-rc2 - Released Tue Apr 28 00:00:00 2026
|
||||
-------------------------------------
|
||||
|
||||
More information is available at https://valkey.io
|
||||
Upgrade urgency LOW: This is the second release candidate of Valkey 9.1.0.
|
||||
|
||||
Happy hacking!
|
||||
### Behavior Changes
|
||||
* Revert strict TLS certificate validation at config load as it is a breaking change, deferred to next major version (#3572)
|
||||
|
||||
### New Features and enhanced behavior
|
||||
* Do the failover immediately if the replica is the best ranked replica by @enjoy-binbin (#2227)
|
||||
* Add `cluster-config-save-behavior` option to control nodes.conf save behavior by @enjoy-binbin (#3372)
|
||||
* Lua scripting engine is now statically linked by default instead of dynamically linked by @eifrah-aws (#3392)
|
||||
* Module command result callback addition by @martinrvisser (#2936)
|
||||
|
||||
### Performance and Efficiency improvements
|
||||
* Redesign IO threading communication model with lock-free queues (8-17% throughput gain) by @akashkgit (#3324)
|
||||
* Increase embedded string threshold from 64 to 128 bytes (30% GET throughput gain) by @Nikhil-Manglore (#3397)
|
||||
* ARM NEON SIMD optimization for pvFind() in vset.c (2-3x speedup) by @ahmadbelb (#3033)
|
||||
* Optimize WATCH duplicate key check from O(N) to O(1) using per-db hashtable by @enjoy-binbin (#3360)
|
||||
* Optimize `CLUSTERSCAN MATCH` so that it uses a specific slot if given by @nmvk (#3380)
|
||||
* Improve COB memory tracking with copy avoidance by @dvkashapov (#3306)
|
||||
|
||||
### Bug Fixes
|
||||
* Fix `valkey-cli --cluster del-node` for unreachable nodes by @yang-z-o (#3209)
|
||||
* Enhance cluster stale packet detection to prevent sub-replica and empty primary by @zhijun42 (#2811)
|
||||
* Big endian bitmap byte order mismatch fix by @nmvk (#3401)
|
||||
* Fix slot-migration-max-failover-repl-bytes unable to accept -1 by @enjoy-binbin (#3443)
|
||||
* Fix config rewrite producing negative values for unsigned memory configs by @enjoy-binbin (#3440)
|
||||
* Fix HPERSIST RESP protocol violation on wrong-type key by @madolson (#3516)
|
||||
* Fix lua-enable-insecure-api default value cannot be changed to yes by @enjoy-binbin (#3548)
|
||||
|
||||
Valkey 9.1.0-rc1 - Released Tue Mar 17 00:00:00 2026
|
||||
-------------------------------------
|
||||
|
||||
Upgrade urgency LOW: This is the first release candidate of Valkey 9.1.0, with
|
||||
new features, performance improvements, and bug fixes.
|
||||
|
||||
### New Features and enhanced behavior
|
||||
* Database-level access control by @dvkashapov (#2309)
|
||||
* Move Lua scripting engine into a Valkey module by @rjd15372 (#2858)
|
||||
* Support cross node consistency for `SCAN` commands through configurable DB hash seed by @sarthakaggarwal97 (#2608)
|
||||
* Support automatic TLS reload by @yang-z-o (#3020)
|
||||
* Support TLS authentication using SAN URI by @yang-z-o (#3078)
|
||||
* Prevent invalid TLS certificates from being loaded by @yang-z-o (#2999)
|
||||
* Failing to save the cluster config file will no longer exit the process by @enjoy-binbin (#1032)
|
||||
|
||||
### Command and API updates
|
||||
* Makes `CLUSTER KEYSLOT` available in standalone mode by @stockholmux (#3040)
|
||||
* Update HSETEX so that it always issue keyspace notifications after validation by @ranshid (#3001)
|
||||
* Adds HGETDEL command by @roshkhatri (#2851)
|
||||
* Support NX/XX flag in HSETEX command by @hanxizh9910 (#2668)
|
||||
* New `CLUSTERSCAN` command for cluster-wide key scanning across nodes by @nmvk (#2934)
|
||||
* New `MSETEX` command to set multiple keys with a shared expiration by @enjoy-binbin (#3121)
|
||||
* `CLUSTER SHARDS`/`CLUSTER SLOTS` now include an `availability-zone` field by @bandalgomsu (#3156)
|
||||
|
||||
### Performance and Efficiency improvements
|
||||
* Optimize zset memory usage by embedding element in skiplist by @chzhoo (#2508)
|
||||
* Remove internal server object pointer overhead in small strings by @rainsupreme (#2516)
|
||||
* Optimize skiplist query efficiency by embedding the skiplist header by @chzhoo (#2867)
|
||||
* Improve performance during rehashing by @chzhoo (#3073)
|
||||
* Optimize SREM/ZREM/HDEL to pause auto shrink when deleting multiple items by @enjoy-binbin (#3144)
|
||||
* Abort and swap the tables if ht1 is very full during the hashtable shrink rehashing by @enjoy-binbin (#3175)
|
||||
* Improve performance of copy avoidance when command reply tracking is disabled by @dvkashapov (#3086)
|
||||
* Enable hardware clock by default by @dvkashapov (#3103)
|
||||
* Improve `COMMAND` performance by caching responses by @ebarskiy (#2839)
|
||||
* Add support for asynchronous freeing of keys on writable replicas by @Scut-Corgis (#2849)
|
||||
* Faster `XRANGE`/`XREVRANGE` via stream range hot-path optimization by @nesty92 (#3002)
|
||||
* Replicas can reuse the RDB file as AOF preamble after disk-based full sync by @RayaCoo (#1901)
|
||||
|
||||
### Module API changes
|
||||
* Add ValkeyModule_ClusterKeySlotC by @bandalgomsu (#2984)
|
||||
* Add more client info flags to module API by @martinrvisser (#2868)
|
||||
* Add prefix-aware ACL permission checks and new module API by @eifrah-aws (#2796)
|
||||
* Support unsigned 64-bit numeric config values in module API by @artikell (#1546)
|
||||
|
||||
### Observability and logging
|
||||
* Cumulative metrics for active I/O threads usage by @deepakrn (#2463)
|
||||
* Cumulative metric for active main thread usage by @dvkashapov (#2931)
|
||||
* Support whole cluster info for INFO command in cluster_info section by @soloestoy, @ranshid (#2876, #2964)
|
||||
* Add remaining_repl_size field in `CLUSTER GETSLOTMIGRATIONS` output by @enjoy-binbin (#3135)
|
||||
* Add logging helper function to print node's ip:port when nodename not explicitly set by @zhijun42 (#2777)
|
||||
* Dual-channel-replication announces itself at replica-announce-ip if configured by @jdheyburn (#2846)
|
||||
* Show replica dual-channel replication buffer memory in `INFO MEMORY` and `MEMORY STATS` by @enjoy-binbin (#2924)
|
||||
* Add `rdb_transmitted` state to replica state in `INFO` by @enjoy-binbin (#2833)
|
||||
* New INFO section for scripting engines by @rjd15372 (#2738)
|
||||
* Adding json support for log-format config by @jbergstroem (#1791)
|
||||
* Add server side TLS certificate expiry tracking and INFO telemetry by @YiwenZhang12 (#2913)
|
||||
* Add option to use libbacktrace for backtraces in crash reports by @rainsupreme (#3034)
|
||||
|
||||
### Build and Tooling
|
||||
* Show RPS histogram in valkey-benchmark by @hanxizh9910 (#2471)
|
||||
* Add --warmup and --duration parameters to valkey-benchmark by @rainsupreme (#2581)
|
||||
* Lazy loading of RDMA libs in CLI/Benchmark when building as module by @Ada-Church-Closure (#3072)
|
||||
* Add support for atomic slot migration to valkey-cli by @murphyjacob4 (#2755)
|
||||
* Replace C++ fast_float dependency with pure C implementation (ffc) by @lemire (#3329)
|
||||
|
||||
### Bug Fixes
|
||||
* Strictly check CRLF when parsing querybuf by @enjoy-binbin (#2872)
|
||||
|
||||
### Contributors
|
||||
* Adam Fowler @adam-fowler
|
||||
* Aditya Teltia @AdityaTeltia
|
||||
* Alina Liu @asagege
|
||||
* Allen Samuels @allenss-amazon
|
||||
* Alon Arenberg @alon-arenberg
|
||||
* aradz44 @aradz44
|
||||
* Arthur Lee @arthurkiller
|
||||
* bandalgomsu @bandalgomsu
|
||||
* Baswanth @baswanth09
|
||||
* Benson-li @li-benson
|
||||
* Binbin @enjoy-binbin
|
||||
* Björn Svensson @bjosv
|
||||
* bpint @bpint
|
||||
* chzhoo @chzhoo
|
||||
* cjx-zar @cjx-zar
|
||||
* Daniil Kashapov @dvkashapov
|
||||
* Deepak Nandihalli @deepakrn
|
||||
* Diego Ciciani @diegociciani
|
||||
* eifrah-aws @eifrah-aws
|
||||
* Evgeny Barskiy @ebarskiy
|
||||
* Gabi Ganam @gabiganam
|
||||
* Gagan H R @gaganhr94
|
||||
* Hanxi Zhang @hanxizh9910
|
||||
* Harkrishn Patro @hpatro
|
||||
* Harry Lin @harrylin98
|
||||
* hieu2102 @hieu2102
|
||||
* Jacob Murphy @murphyjacob4
|
||||
* jiegang0219 @jiegang0219
|
||||
* Jim Brunner @JimB123
|
||||
* Johan Bergström @jbergstroem
|
||||
* John @johnufida
|
||||
* Joseph Heyburn @jdheyburn
|
||||
* Katie Holly @Fusl
|
||||
* Ken @otherscase
|
||||
* korjeek @korjeek
|
||||
* Kurt McKee @kurtmckee
|
||||
* Kyle J. Davis @stockholmux
|
||||
* Kyle Kim @kyle-yh-kim
|
||||
* Leon Anavi @leon-anavi
|
||||
* Madelyn Olson @madolson
|
||||
* Mangat Singh Toor @immangat
|
||||
* Marc Jakobi @mrcjkb
|
||||
* martinrvisser @martinrvisser
|
||||
* Marvin Rösch @marvinroesch
|
||||
* Murad Shahmammadli @MuradSh
|
||||
* NAM UK KIM @namuk2004
|
||||
* Nikhil Manglore @Nikhil-Manglore
|
||||
* Ouri Half @ouriamzn
|
||||
* Patrik Hermansson @phermansson
|
||||
* Ping Xie @PingXie
|
||||
* Quanye Yang @Ada-Church-Closure
|
||||
* Rain Valentine @rainsupreme
|
||||
* Ran Shidlansik @ranshid
|
||||
* Ricardo Dias @rjd15372
|
||||
* Ritoban Dutta @ritoban23
|
||||
* Roshan Khatri @roshkhatri
|
||||
* ruihong123 @ruihong123
|
||||
* Sachin Venkatesha Murthy @sachinvmurthy
|
||||
* Sarthak Aggarwal @sarthakaggarwal97
|
||||
* Satheesha CH Gowda @satheesha
|
||||
* Seungmin Lee @sungming2
|
||||
* Shinobu Nunotaba @Ada-Church-Closure
|
||||
* Simon Baatz @gmbnomis
|
||||
* skyfirelee @artikell
|
||||
* Sourav Singh Rawat @frostzt
|
||||
* stydxm @stydxm
|
||||
* Ted Lyngmo @TedLyngmo
|
||||
* Tony Wooster @twooster
|
||||
* uriyage @uriyage
|
||||
* Vadym Khoptynets @poiuj
|
||||
* Venkat Pamulapati @ChiliPaneer
|
||||
* Viktor Söderqvist @zuiderkwast
|
||||
* Vitah Lin @vitahlin
|
||||
* Vitali @VitalyAR
|
||||
* withRiver @withRiver
|
||||
* wxmzy88 @wxmzy88
|
||||
* xbasel @xbasel
|
||||
* Yair Gottdenker @yairgott
|
||||
* Yana Molodetsky @yanamolo
|
||||
* Yang Zhao @yang-z-o
|
||||
* Yiwen Zhang @YiwenZhang12
|
||||
* yzc-yzc @yzc-yzc
|
||||
* zhaozhao.zz @soloestoy
|
||||
* Zhijun Liao @zhijun42
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Scope
|
||||
- These instructions apply to the entire repository unless a deeper `AGENTS.md` overrides them.
|
||||
|
||||
## Repo overview
|
||||
- This is the Valkey server codebase.
|
||||
- The main implementation lives under `src/`.
|
||||
- Unit tests live under `src/unit`
|
||||
- Integration tests live under `tests/`.
|
||||
- Top-level `Makefile` forwards most targets into `src/Makefile`.
|
||||
|
||||
## Working guidelines
|
||||
- Keep changes minimal and easy to backport.
|
||||
- Match the style of the surrounding code instead of introducing new patterns.
|
||||
- Avoid unrelated refactors in the same change.
|
||||
|
||||
## Build
|
||||
- Default build: `make`
|
||||
- Clean rebuild when build settings or bundled deps change: `make distclean && make`
|
||||
|
||||
## Unit tests
|
||||
- Unit tests live under `src/unit/` and use GoogleTest (gtest/gmock).
|
||||
- Build and run all unit tests: `make -C src test-unit`
|
||||
- Unit tests cover data-structure and low-level logic changes.
|
||||
- A single test filter can be run with: `make -C src test-unit && ./src/unit/valkey-unit-gtests --gtest_filter='<TestSuite>.<TestName>'`
|
||||
|
||||
## Integration tests
|
||||
- Integration tests live under `tests/` and are written in Tcl.
|
||||
- Run the full integration suite: `make test` (from the repo root).
|
||||
- Run a single test file: `./runtest --single <path/to/test.tcl>`
|
||||
- Additional specialized suites:
|
||||
- Cluster tests: `./runtest-cluster`
|
||||
- Sentinel tests: `./runtest-sentinel`
|
||||
- Module API tests: `./runtest-moduleapi`
|
||||
- For targeted validation, run the smallest relevant test scope first before broader suites.
|
||||
|
||||
## Code style
|
||||
- Follow the repository conventions described in `DEVELOPMENT_GUIDE.md`.
|
||||
- Most formatting is enforced by `clang-format`.
|
||||
- CI uses `clang-format-18` across `*.c`, `*.h`, `*.cpp`, and `*.hpp` files.
|
||||
- When touching C/C++ sources or headers, run `clang-format-18 -i` on the modified files before finalizing when the tool is available.
|
||||
- Use comments for non-obvious behavior and rationale, not for restating code.
|
||||
|
||||
## Tests
|
||||
- Code changes should include relevant tests when the repo already has a matching test location.
|
||||
- Data-structure and low-level logic changes usually belong in `src/unit/` (C++ gtest).
|
||||
- End-to-end behavior changes usually belong in `tests/` (Tcl integration tests).
|
||||
- If behavior or commands change, check whether related documentation also needs updating.
|
||||
|
||||
## Files to avoid touching unless required
|
||||
- Do not commit local runtime artifacts such as `dump.rdb`, `nodes.conf`, `*.log`, or ad hoc cluster directories unless the task explicitly requires them.
|
||||
- Treat vendored dependency code under `deps/` as special-case changes; modify it only when the task clearly requires it.
|
||||
|
||||
## Pull Requests
|
||||
Always push to the user's fork. Never push to the upstream valkey-io/valkey repository. Never push directly to unstable. If a user fork does not exist, ask the contributor to create one.
|
||||
+3
-3
@@ -13,8 +13,8 @@ if (APPLE)
|
||||
endif ()
|
||||
|
||||
# Options
|
||||
option(BUILD_LUA "Build Valkey Lua scripting engine" ON)
|
||||
option(BUILD_UNIT_TESTS "Build valkey-unit-tests" OFF)
|
||||
set(BUILD_LUA "static" CACHE STRING "Build Valkey Lua scripting engine: static (default), module, no")
|
||||
option(BUILD_UNIT_GTESTS "Build valkey-unit-gtests" OFF)
|
||||
option(BUILD_TEST_MODULES "Build all test modules" OFF)
|
||||
option(BUILD_EXAMPLE_MODULES "Build example modules" OFF)
|
||||
|
||||
@@ -40,7 +40,7 @@ unset(CLANGPP CACHE)
|
||||
unset(CLANG CACHE)
|
||||
unset(BUILD_RDMA_MODULE CACHE)
|
||||
unset(BUILD_TLS_MODULE CACHE)
|
||||
unset(BUILD_UNIT_TESTS CACHE)
|
||||
unset(BUILD_UNIT_GTESTS CACHE)
|
||||
unset(BUILD_TEST_MODULES CACHE)
|
||||
unset(BUILD_EXAMPLE_MODULES CACHE)
|
||||
unset(USE_TLS CACHE)
|
||||
|
||||
+2
-1
@@ -16,7 +16,7 @@ Maintainers listed in alphabetical order by their github ID.
|
||||
| ------------------- | ------------- | ----------- |
|
||||
| Binbin Zhu | @enjoy-binbin | Tencent |
|
||||
| Harkrishn Patro | @hpatro | Amazon |
|
||||
| Lucas Yang | @lucasyonge | - |
|
||||
| Lucas Yang | @lucasyonge | Percona |
|
||||
| Madelyn Olson | @madolson | Amazon |
|
||||
| Jacob Murphy | @murphyjacob4 | Google |
|
||||
| Ping Xie | @pingxie | Oracle |
|
||||
@@ -30,6 +30,7 @@ Committers listed in alphabetical order by their github ID.
|
||||
|
||||
| Committer | GitHub ID | Affiliation |
|
||||
| ------------------- | ------------- | ----------- |
|
||||
| Jim Brunner | @JimB123 | Amazon |
|
||||
| Ricardo Dias | @rjd15372 | Percona |
|
||||
|
||||
## Former Maintainers and Committers
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[](https://codecov.io/gh/valkey-io/valkey)
|
||||
[](https://securityscorecards.dev/viewer/?uri=github.com/valkey-io/valkey)
|
||||
|
||||
This project was forked from the open source Redis project right before the transition to their new source available licenses.
|
||||
|
||||
@@ -52,13 +53,6 @@ as libsystemd-dev on Debian/Ubuntu or systemd-devel on CentOS) and run:
|
||||
|
||||
% make USE_SYSTEMD=yes
|
||||
|
||||
Since Valkey version 8.1, `fast_float` has been introduced as an optional
|
||||
dependency, which can speed up sorted sets and other commands that use
|
||||
the double datatype. To build with `fast_float` support, you'll need a
|
||||
C++ compiler and run:
|
||||
|
||||
% make USE_FAST_FLOAT=yes
|
||||
|
||||
To build with enhanced stack traces that include file names and line numbers
|
||||
for all functions (including static functions), use libbacktrace:
|
||||
|
||||
@@ -341,7 +335,7 @@ Other options supported by Valkey's `CMake` build system:
|
||||
- `-DBUILD_RDMA=<no|module>` enable RDMA module build (only module mode supported). Default: `no`
|
||||
- `-DBUILD_MALLOC=<libc|jemalloc|tcmalloc|tcmalloc_minimal>` choose the allocator to use. Default on Linux: `jemalloc`, for other OS: `libc`
|
||||
- `-DBUILD_SANITIZER=<address|thread|undefined>` build with address sanitizer enabled. Default: disabled (no sanitizer)
|
||||
- `-DBUILD_UNIT_TESTS=[yes|no]` when set, the build will produce the executable `valkey-unit-tests`. Default: `no`
|
||||
- `-DBUILD_UNIT_GTESTS=[yes|no]` when set, the build will produce unit tests executable `valkey-unit-gtests`. Default: `no`
|
||||
- `-DBUILD_TEST_MODULES=[yes|no]` when set, the build will include the modules located under the `tests/modules` folder. Default: `no`
|
||||
- `-DBUILD_EXAMPLE_MODULES=[yes|no]` when set, the build will include the example modules located under the `src/modules` folder. Default: `no`
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ set(VALKEY_SERVER_SRCS
|
||||
${CMAKE_SOURCE_DIR}/src/cluster_legacy.c
|
||||
${CMAKE_SOURCE_DIR}/src/cluster_slot_stats.c
|
||||
${CMAKE_SOURCE_DIR}/src/crc16.c
|
||||
${CMAKE_SOURCE_DIR}/src/crc16_slottable.c
|
||||
${CMAKE_SOURCE_DIR}/src/commandlog.c
|
||||
${CMAKE_SOURCE_DIR}/src/eval.c
|
||||
${CMAKE_SOURCE_DIR}/src/bio.c
|
||||
@@ -68,6 +69,7 @@ set(VALKEY_SERVER_SRCS
|
||||
${CMAKE_SOURCE_DIR}/src/sparkline.c
|
||||
${CMAKE_SOURCE_DIR}/src/valkey-check-rdb.c
|
||||
${CMAKE_SOURCE_DIR}/src/valkey-check-aof.c
|
||||
${CMAKE_SOURCE_DIR}/src/valkey_strtod.c
|
||||
${CMAKE_SOURCE_DIR}/src/geo.c
|
||||
${CMAKE_SOURCE_DIR}/src/lazyfree.c
|
||||
${CMAKE_SOURCE_DIR}/src/module.c
|
||||
@@ -119,7 +121,8 @@ set(VALKEY_SERVER_SRCS
|
||||
${CMAKE_SOURCE_DIR}/src/entry.c
|
||||
${CMAKE_SOURCE_DIR}/src/vset.c
|
||||
${CMAKE_SOURCE_DIR}/src/fifo.c
|
||||
${CMAKE_SOURCE_DIR}/src/mutexqueue.c)
|
||||
${CMAKE_SOURCE_DIR}/src/mutexqueue.c
|
||||
${CMAKE_SOURCE_DIR}/src/queues.c)
|
||||
|
||||
|
||||
# valkey-cli
|
||||
@@ -131,6 +134,7 @@ set(VALKEY_CLI_SRCS
|
||||
${CMAKE_SOURCE_DIR}/src/sha256.c
|
||||
${CMAKE_SOURCE_DIR}/src/util.c
|
||||
${CMAKE_SOURCE_DIR}/src/valkey-cli.c
|
||||
${CMAKE_SOURCE_DIR}/src/valkey_strtod.c
|
||||
${CMAKE_SOURCE_DIR}/src/zmalloc.c
|
||||
${CMAKE_SOURCE_DIR}/src/release.c
|
||||
${CMAKE_SOURCE_DIR}/src/ae.c
|
||||
@@ -154,6 +158,7 @@ set(VALKEY_BENCHMARK_SRCS
|
||||
${CMAKE_SOURCE_DIR}/src/sha256.c
|
||||
${CMAKE_SOURCE_DIR}/src/util.c
|
||||
${CMAKE_SOURCE_DIR}/src/valkey-benchmark.c
|
||||
${CMAKE_SOURCE_DIR}/src/valkey_strtod.c
|
||||
${CMAKE_SOURCE_DIR}/src/adlist.c
|
||||
${CMAKE_SOURCE_DIR}/src/dict.c
|
||||
${CMAKE_SOURCE_DIR}/src/zmalloc.c
|
||||
@@ -164,6 +169,7 @@ set(VALKEY_BENCHMARK_SRCS
|
||||
${CMAKE_SOURCE_DIR}/src/crc64.c
|
||||
${CMAKE_SOURCE_DIR}/src/siphash.c
|
||||
${CMAKE_SOURCE_DIR}/src/crc16.c
|
||||
${CMAKE_SOURCE_DIR}/src/crc16_slottable.c
|
||||
${CMAKE_SOURCE_DIR}/src/monotonic.c
|
||||
${CMAKE_SOURCE_DIR}/src/cli_common.c
|
||||
${CMAKE_SOURCE_DIR}/src/mt19937-64.c
|
||||
|
||||
@@ -236,7 +236,7 @@ if (BUILDING_ARM64)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
add_valkey_server_linker_option("-rdynamic")
|
||||
add_valkey_server_linker_option("-Wl,-export_dynamic")
|
||||
add_valkey_server_linker_option("-ldl")
|
||||
elseif (UNIX)
|
||||
add_valkey_server_linker_option("-rdynamic")
|
||||
@@ -330,22 +330,10 @@ if (PYTHON_EXE)
|
||||
COMMAND touch ${CMAKE_BINARY_DIR}/fmtargs_generated
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/src")
|
||||
add_custom_target(generate_fmtargs_h DEPENDS ${CMAKE_BINARY_DIR}/fmtargs_generated)
|
||||
|
||||
# Rule for generating test_files.h
|
||||
message(STATUS "Adding target generate_test_files_h")
|
||||
file(GLOB UNIT_TEST_SRCS "${CMAKE_SOURCE_DIR}/src/unit/*.c")
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/test_files_generated
|
||||
DEPENDS "${UNIT_TEST_SRCS};${CMAKE_SOURCE_DIR}/utils/generate-unit-test-header.py"
|
||||
COMMAND ${PYTHON_EXE} ${CMAKE_SOURCE_DIR}/utils/generate-unit-test-header.py
|
||||
COMMAND touch ${CMAKE_BINARY_DIR}/test_files_generated
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/src")
|
||||
add_custom_target(generate_test_files_h DEPENDS ${CMAKE_BINARY_DIR}/test_files_generated)
|
||||
else ()
|
||||
# Fake targets
|
||||
add_custom_target(generate_commands_def)
|
||||
add_custom_target(generate_fmtargs_h)
|
||||
add_custom_target(generate_test_files_h)
|
||||
endif ()
|
||||
|
||||
# Generate release.h file (always)
|
||||
|
||||
Vendored
+4
@@ -21,6 +21,9 @@ if (USE_RDMA) # Module or no module
|
||||
set(ENABLE_RDMA
|
||||
ON
|
||||
CACHE BOOL "If RDMA support should be compiled or not")
|
||||
if (USE_RDMA EQUAL 2) # Module
|
||||
set(ENABLE_DLOPEN_RDMA ON CACHE BOOL "Build valkey_rdma with dynamic loading")
|
||||
endif()
|
||||
endif ()
|
||||
# Let libvalkey use sds and dict provided by valkey.
|
||||
set(DICT_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src)
|
||||
@@ -30,6 +33,7 @@ add_subdirectory(libvalkey)
|
||||
add_subdirectory(linenoise)
|
||||
add_subdirectory(fpconv)
|
||||
add_subdirectory(hdr_histogram)
|
||||
add_subdirectory(fast_float)
|
||||
|
||||
# Clear any cached variables passed to libvalkey from the cache
|
||||
unset(BUILD_SHARED_LIBS CACHE)
|
||||
|
||||
Vendored
+9
-5
@@ -42,7 +42,6 @@ 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_c_interface && $(MAKE) clean) > /dev/null || true
|
||||
-(rm -f .make-*)
|
||||
|
||||
.PHONY: distclean
|
||||
@@ -54,6 +53,9 @@ endif
|
||||
|
||||
ifneq (,$(filter $(BUILD_RDMA),yes module))
|
||||
LIBVALKEY_MAKE_FLAGS += USE_RDMA=1
|
||||
ifneq (,$(filter $(BUILD_RDMA),module))
|
||||
LIBVALKEY_MAKE_FLAGS += USE_DLOPEN_RDMA=1
|
||||
endif
|
||||
endif
|
||||
|
||||
libvalkey: .make-prerequisites
|
||||
@@ -123,8 +125,10 @@ jemalloc: .make-prerequisites
|
||||
|
||||
.PHONY: jemalloc
|
||||
|
||||
fast_float_c_interface: .make-prerequisites
|
||||
gtest-parallel: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd fast_float_c_interface && $(MAKE)
|
||||
|
||||
.PHONY: fast_float_c_interface
|
||||
@if [ ! -f gtest-parallel/gtest_parallel.py ]; then \
|
||||
echo "Downloading gtest-parallel..."; \
|
||||
rm -rf gtest-parallel; \
|
||||
git clone --depth 1 https://github.com/google/gtest-parallel.git gtest-parallel; \
|
||||
fi
|
||||
|
||||
Vendored
+43
-12
@@ -6,7 +6,8 @@ should be provided by the operating system.
|
||||
* **linenoise** is a readline replacement. It is developed by the same authors of Valkey but is managed as a separated project and updated as needed.
|
||||
* **lua** is Lua 5.1 with minor changes for security and additional libraries.
|
||||
* **hdr_histogram** Used for per-command latency tracking histograms.
|
||||
* **fast_float** is a replacement for strtod to convert strings to floats efficiently.
|
||||
* **ffc.h** is a C99 port of the fast_float library, used as a replacement for strtod to convert strings to floats efficiently.
|
||||
* **gtest-parallel** is a script for running googletest tests in parallel.
|
||||
|
||||
How to upgrade the above dependencies
|
||||
===
|
||||
@@ -107,17 +108,47 @@ We use a customized version based on master branch commit e4448cf6d1cd08fff51981
|
||||
2. Copy updated files from newer version onto files in /hdr_histogram.
|
||||
3. Apply the changes from 1 above to the updated files.
|
||||
|
||||
fast_float
|
||||
ffc.h
|
||||
---
|
||||
The fast_float library provides fast header-only implementations for the C++ from_chars functions for `float` and `double` types as well as integer types. These functions convert ASCII strings representing decimal values (e.g., `1.3e10`) into binary types. The functions are much faster than comparable number-parsing functions from existing C++ standard libraries.
|
||||
|
||||
Specifically, `fast_float` provides the following function to parse floating-point numbers with a C++17-like syntax (the library itself only requires C++11):
|
||||
|
||||
template <typename T, typename UC = char, typename = FASTFLOAT_ENABLE_IF(is_supported_float_type<T>())>
|
||||
from_chars_result_t<UC> from_chars(UC const *first, UC const *last, T &value, chars_format fmt = chars_format::general);
|
||||
ffc.h is a pure C99 port of the fast_float library, providing fast string-to-double
|
||||
conversion without requiring a C++ compiler.
|
||||
|
||||
To upgrade the library,
|
||||
1. Check out https://github.com/fastfloat/fast_float/tree/main
|
||||
2. cd fast_float
|
||||
3. Invoke "python3 ./script/amalgamate.py --output fast_float.h"
|
||||
4. Copy fast_float.h file to "deps/fast_float/".
|
||||
1. Download the latest ffc.h from https://github.com/kolemannix/ffc.h/releases
|
||||
2. Copy ffc.h to "deps/fast_float/".
|
||||
|
||||
gtest-parallel
|
||||
---
|
||||
|
||||
The `deps/gtest-parallel` directory is imported from the upstream
|
||||
https://github.com/google/gtest-parallel repository as a subtree snapshot (not a real Git subtree).
|
||||
|
||||
Current upstream version:
|
||||
- Upstream commit: `cd488bd` (from google/gtest-parallel)
|
||||
|
||||
Updating gtest-parallel
|
||||
|
||||
Run the following from the repository root.
|
||||
|
||||
1. Add the remote and fetch upstream:
|
||||
```sh
|
||||
git remote add gtest-parallel https://github.com/google/gtest-parallel.git
|
||||
git fetch gtest-parallel master
|
||||
```
|
||||
|
||||
2. Remove any previous import and commit (commit A):
|
||||
```sh
|
||||
rm -rf deps/gtest-parallel
|
||||
```
|
||||
|
||||
3. Update the subtree from upstream:
|
||||
```sh
|
||||
git subtree add --prefix=deps/gtest-parallel gtest-parallel master --squash
|
||||
```
|
||||
|
||||
4. Reset back to commit A with proper sign-off:
|
||||
```sh
|
||||
git reset --soft <commit-A-hash>
|
||||
```
|
||||
|
||||
5. Commit the changes.
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
add_library(ffc INTERFACE)
|
||||
target_include_directories(ffc INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
Vendored
-3912
File diff suppressed because it is too large
Load Diff
Vendored
+3235
File diff suppressed because one or more lines are too long
Vendored
-37
@@ -1,37 +0,0 @@
|
||||
CCCOLOR:="\033[34m"
|
||||
SRCCOLOR:="\033[33m"
|
||||
ENDCOLOR:="\033[0m"
|
||||
|
||||
CXX?=c++
|
||||
# we need = instead of := so that $@ in QUIET_CXX gets evaluated in the rule and is assigned appropriate value.
|
||||
TEMP:=$(CXX)
|
||||
QUIET_CXX=@printf ' %b %b\n' $(CCCOLOR)C++$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2;
|
||||
CXX=$(QUIET_CXX)$(TEMP)
|
||||
|
||||
WARN=-Wall -W -Wno-missing-field-initializers
|
||||
|
||||
STD=-pedantic -std=c++11
|
||||
|
||||
OPT?=-O3
|
||||
CLANG := $(findstring clang,$(shell sh -c '$(CC) --version | head -1'))
|
||||
ifeq ($(OPT),-O3)
|
||||
ifeq (clang,$(CLANG))
|
||||
OPT+=-flto
|
||||
else
|
||||
OPT+=-flto=auto -ffat-lto-objects
|
||||
endif
|
||||
endif
|
||||
|
||||
# 1) Today src/Makefile passes -m32 flag for explicit 32-bit build on 64-bit machine, via CFLAGS. For 32-bit build on
|
||||
# 32-bit machine and 64-bit on 64-bit machine, CFLAGS are empty. No other flags are set that can conflict with C++,
|
||||
# therefore let's use CFLAGS without changes for now.
|
||||
# 2) FASTFLOAT_ALLOWS_LEADING_PLUS allows +inf to be parsed as inf, instead of error.
|
||||
CXXFLAGS=$(STD) $(OPT) $(WARN) -static -fPIC -fno-exceptions $(CFLAGS) -D FASTFLOAT_ALLOWS_LEADING_PLUS
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: fast_float_strtod.o
|
||||
|
||||
clean:
|
||||
rm -f *.o || true;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "../fast_float/fast_float.h"
|
||||
#include <cerrno>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
double fast_float_strtod(const char *str, const char** endptr)
|
||||
{
|
||||
double temp = 0;
|
||||
auto answer = fast_float::from_chars(str, str + strlen(str), temp);
|
||||
if (answer.ec != std::errc()) {
|
||||
errno = (answer.ec == std::errc::result_out_of_range) ? ERANGE : EINVAL;
|
||||
}
|
||||
if (endptr) {
|
||||
*endptr = answer.ptr;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
.*.swp
|
||||
*.py[co]
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
[style]
|
||||
based_on_style = pep8
|
||||
indent_width = 2
|
||||
column_limit = 80
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement. You (or your employer) retain the copyright to your contribution;
|
||||
this simply gives us permission to use and redistribute your contributions as
|
||||
part of the project. Head over to <https://cla.developers.google.com/> to see
|
||||
your current agreements on file or to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
## Community Guidelines
|
||||
|
||||
This project follows [Google's Open Source Community
|
||||
Guidelines](https://opensource.google.com/conduct/).
|
||||
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
# gtest-parallel
|
||||
|
||||
_This is not an official Google product._
|
||||
|
||||
`gtest-parallel` is a script that executes [Google
|
||||
Test](https://github.com/google/googletest) binaries in parallel, providing good
|
||||
speedup for single-threaded tests (on multi-core machines) and tests that do not
|
||||
run at 100% CPU (on single- or multi-core machines).
|
||||
|
||||
The script works by listing the tests of each binary, and then executing them on
|
||||
workers in separate processes. This works fine so long as the tests are self
|
||||
contained and do not share resources (reading data is fine, writing to the same
|
||||
log file is probably not).
|
||||
|
||||
## Basic Usage
|
||||
|
||||
_For a full list of options, see `--help`._
|
||||
|
||||
$ ./gtest-parallel path/to/binary...
|
||||
|
||||
This shards all enabled tests across a number of workers, defaulting to the
|
||||
number of cores in the system. If your system uses Python 2, but you have no
|
||||
python2 binary, run `python gtest-parallel` instead of `./gtest-parallel`.
|
||||
|
||||
To run only a select set of tests, run:
|
||||
|
||||
$ ./gtest-parallel path/to/binary... --gtest_filter=Foo.*:Bar.*
|
||||
|
||||
This filter takes the same parameters as Google Test, so -Foo.\* can be used for
|
||||
test exclusion as well. This is especially useful for slow tests (that you're
|
||||
not working on), or tests that may not be able to run in parallel.
|
||||
|
||||
## Flakiness
|
||||
|
||||
Flaky tests (tests that do not deterministically pass or fail) often cause a lot
|
||||
of developer headache. A test that fails only 1% of the time can be very hard to
|
||||
detect as flaky, and even harder to convince yourself of having fixed.
|
||||
|
||||
`gtest-parallel` supports repeating individual tests (`--repeat=`), which can be
|
||||
very useful for flakiness testing. Some tests are also more flaky under high
|
||||
loads (especially tests that use realtime clocks), so raising the number of
|
||||
`--workers=` well above the number of available core can often cause contention
|
||||
and be fruitful for detecting flaky tests as well.
|
||||
|
||||
$ ./gtest-parallel out/{binary1,binary2,binary3} --repeat=1000 --workers=128
|
||||
|
||||
The above command repeats all tests inside `binary1`, `binary2` and `binary3`
|
||||
located in `out/`. The tests are run `1000` times each on `128` workers (this is
|
||||
more than I have cores on my machine anyways). This can often be done and then
|
||||
left overnight if you've no initial guess to which tests are flaky and which
|
||||
ones aren't. When you've figured out which tests are flaky (and want to fix
|
||||
them), repeat the above command with `--gtest_filter=` to only retry the flaky
|
||||
tests that you are fixing.
|
||||
|
||||
Note that repeated tests do run concurrently with themselves for efficiency, and
|
||||
as such they have problem writing to hard-coded files, even if they are only
|
||||
used by that single test. `tmpfile()` and similar library functions are often
|
||||
your friends here.
|
||||
|
||||
### Flakiness Summaries
|
||||
|
||||
Especially for disabled tests, you might wonder how stable a test seems before
|
||||
trying to enable it. `gtest-parallel` prints summaries (number of passed/failed
|
||||
tests) when `--repeat=` is used and at least one test fails. This can be used to
|
||||
generate passed/failed statistics per test. If no statistics are generated then
|
||||
all invocations tests are passing, congratulations!
|
||||
|
||||
For example, to try all disabled tests and see how stable they are:
|
||||
|
||||
$ ./gtest-parallel path/to/binary... -r1000 --gtest_filter=*.DISABLED_* --gtest_also_run_disabled_tests
|
||||
|
||||
Which will generate something like this at the end of the run:
|
||||
|
||||
SUMMARY:
|
||||
path/to/binary... Foo.DISABLED_Bar passed 0 / 1000 times.
|
||||
path/to/binary... FooBar.DISABLED_Baz passed 30 / 1000 times.
|
||||
path/to/binary... Foo.DISABLED_Baz passed 1000 / 1000 times.
|
||||
|
||||
## Running Tests Within Test Cases Sequentially
|
||||
|
||||
Sometimes tests within a single test case use globally-shared resources
|
||||
(hard-coded file paths, sockets, etc.) and cannot be run in parallel. Running
|
||||
such tests in parallel will either fail or be flaky (if they happen to not
|
||||
overlap during execution, they pass). So long as these resources are only shared
|
||||
within the same test case `gtest-parallel` can still provide some parallelism.
|
||||
|
||||
For such binaries where test cases are independent, `gtest-parallel` provides
|
||||
`--serialize_test_cases` that runs tests within the same test case sequentially.
|
||||
While generally not providing as much speedup as fully parallel test execution,
|
||||
this permits such binaries to partially benefit from parallel execution.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2017 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import gtest_parallel
|
||||
import sys
|
||||
|
||||
sys.exit(gtest_parallel.main())
|
||||
+955
@@ -0,0 +1,955 @@
|
||||
# Copyright 2013 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import errno
|
||||
from functools import total_ordering
|
||||
import gzip
|
||||
import io
|
||||
import json
|
||||
import multiprocessing
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
if sys.version_info.major >= 3:
|
||||
long = int
|
||||
import _pickle as cPickle
|
||||
import _thread as thread
|
||||
else:
|
||||
import cPickle
|
||||
import thread
|
||||
|
||||
from pickle import HIGHEST_PROTOCOL as PICKLE_HIGHEST_PROTOCOL
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import msvcrt
|
||||
else:
|
||||
import fcntl
|
||||
|
||||
|
||||
# An object that catches SIGINT sent to the Python process and notices
|
||||
# if processes passed to wait() die by SIGINT (we need to look for
|
||||
# both of those cases, because pressing Ctrl+C can result in either
|
||||
# the main process or one of the subprocesses getting the signal).
|
||||
#
|
||||
# Before a SIGINT is seen, wait(p) will simply call p.wait() and
|
||||
# return the result. Once a SIGINT has been seen (in the main process
|
||||
# or a subprocess, including the one the current call is waiting for),
|
||||
# wait(p) will call p.terminate() and raise ProcessWasInterrupted.
|
||||
class SigintHandler(object):
|
||||
class ProcessWasInterrupted(Exception):
|
||||
pass
|
||||
|
||||
sigint_returncodes = {
|
||||
-signal.SIGINT, # Unix
|
||||
-1073741510, # Windows
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.__lock = threading.Lock()
|
||||
self.__processes = set()
|
||||
self.__got_sigint = False
|
||||
signal.signal(signal.SIGINT, lambda signal_num, frame: self.interrupt())
|
||||
|
||||
def __on_sigint(self):
|
||||
self.__got_sigint = True
|
||||
while self.__processes:
|
||||
try:
|
||||
self.__processes.pop().terminate()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def interrupt(self):
|
||||
with self.__lock:
|
||||
self.__on_sigint()
|
||||
|
||||
def got_sigint(self):
|
||||
with self.__lock:
|
||||
return self.__got_sigint
|
||||
|
||||
def wait(self, p, timeout_per_test):
|
||||
with self.__lock:
|
||||
if self.__got_sigint:
|
||||
p.terminate()
|
||||
self.__processes.add(p)
|
||||
try:
|
||||
code = p.wait(timeout_per_test)
|
||||
except subprocess.TimeoutExpired :
|
||||
p.terminate()
|
||||
self.__processes.remove(p)
|
||||
code = -errno.ETIME
|
||||
with self.__lock:
|
||||
self.__processes.discard(p)
|
||||
if code in self.sigint_returncodes:
|
||||
self.__on_sigint()
|
||||
if self.__got_sigint:
|
||||
raise self.ProcessWasInterrupted
|
||||
return code
|
||||
|
||||
|
||||
sigint_handler = SigintHandler()
|
||||
|
||||
|
||||
# Return the width of the terminal, or None if it couldn't be
|
||||
# determined (e.g. because we're not being run interactively).
|
||||
def term_width(out):
|
||||
if not out.isatty():
|
||||
return None
|
||||
try:
|
||||
p = subprocess.Popen(["stty", "size"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(out, err) = p.communicate()
|
||||
if p.returncode != 0 or err:
|
||||
return None
|
||||
return int(out.split()[1])
|
||||
except (IndexError, OSError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
# Output transient and permanent lines of text. If several transient
|
||||
# lines are written in sequence, the new will overwrite the old. We
|
||||
# use this to ensure that lots of unimportant info (tests passing)
|
||||
# won't drown out important info (tests failing).
|
||||
class Outputter(object):
|
||||
def __init__(self, out_file):
|
||||
self.__out_file = out_file
|
||||
self.__previous_line_was_transient = False
|
||||
self.__width = term_width(out_file) # Line width, or None if not a tty.
|
||||
|
||||
def transient_line(self, msg):
|
||||
if self.__width is None:
|
||||
self.__out_file.write(msg + "\n")
|
||||
self.__out_file.flush()
|
||||
else:
|
||||
self.__out_file.write("\r" + msg[:self.__width].ljust(self.__width))
|
||||
self.__previous_line_was_transient = True
|
||||
|
||||
def flush_transient_output(self):
|
||||
if self.__previous_line_was_transient:
|
||||
self.__out_file.write("\n")
|
||||
self.__previous_line_was_transient = False
|
||||
|
||||
def permanent_line(self, msg):
|
||||
self.flush_transient_output()
|
||||
self.__out_file.write(msg + "\n")
|
||||
if self.__width is None:
|
||||
self.__out_file.flush()
|
||||
|
||||
|
||||
def get_save_file_path():
|
||||
"""Return path to file for saving transient data."""
|
||||
if sys.platform == 'win32':
|
||||
default_cache_path = os.path.join(os.path.expanduser('~'), 'AppData',
|
||||
'Local')
|
||||
cache_path = os.environ.get('LOCALAPPDATA', default_cache_path)
|
||||
else:
|
||||
# We don't use xdg module since it's not a standard.
|
||||
default_cache_path = os.path.join(os.path.expanduser('~'), '.cache')
|
||||
cache_path = os.environ.get('XDG_CACHE_HOME', default_cache_path)
|
||||
|
||||
if os.path.isdir(cache_path):
|
||||
return os.path.join(cache_path, 'gtest-parallel')
|
||||
else:
|
||||
sys.stderr.write('Directory {} does not exist'.format(cache_path))
|
||||
return os.path.join(os.path.expanduser('~'), '.gtest-parallel-times')
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Task(object):
|
||||
"""Stores information about a task (single execution of a test).
|
||||
|
||||
This class stores information about the test to be executed (gtest binary and
|
||||
test name), and its result (log file, exit code and runtime).
|
||||
Each task is uniquely identified by the gtest binary, the test name and an
|
||||
execution number that increases each time the test is executed.
|
||||
Additionaly we store the last execution time, so that next time the test is
|
||||
executed, the slowest tests are run first.
|
||||
"""
|
||||
|
||||
def __init__(self, test_binary, test_name, test_command, execution_number,
|
||||
last_execution_time, output_dir):
|
||||
self.test_name = test_name
|
||||
self.output_dir = output_dir
|
||||
self.test_binary = test_binary
|
||||
self.test_command = test_command
|
||||
self.execution_number = execution_number
|
||||
self.last_execution_time = last_execution_time
|
||||
|
||||
self.exit_code = None
|
||||
self.runtime_ms = None
|
||||
|
||||
self.test_id = (test_binary, test_name)
|
||||
self.task_id = (test_binary, test_name, self.execution_number)
|
||||
|
||||
self.log_file = Task._logname(self.output_dir, self.test_binary, test_name,
|
||||
self.execution_number)
|
||||
|
||||
def __sorting_key(self):
|
||||
# Unseen or failing tests (both missing execution time) take precedence over
|
||||
# execution time. Tests are greater (seen as slower) when missing times so
|
||||
# that they are executed first.
|
||||
return (1 if self.last_execution_time is None else 0,
|
||||
self.last_execution_time)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__sorting_key() == other.__sorting_key()
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.__sorting_key() < other.__sorting_key()
|
||||
|
||||
@staticmethod
|
||||
def _normalize(string):
|
||||
return re.sub('[^A-Za-z0-9]', '_', string)
|
||||
|
||||
@staticmethod
|
||||
def _logname(output_dir, test_binary, test_name, execution_number):
|
||||
# Store logs to temporary files if there is no output_dir.
|
||||
if output_dir is None:
|
||||
(log_handle, log_name) = tempfile.mkstemp(prefix='gtest_parallel_',
|
||||
suffix=".log")
|
||||
os.close(log_handle)
|
||||
return log_name
|
||||
|
||||
log_name = '%s-%s-%d.log' % (Task._normalize(os.path.basename(test_binary)),
|
||||
Task._normalize(test_name), execution_number)
|
||||
|
||||
return os.path.join(output_dir, log_name)
|
||||
|
||||
def run(self, timeout_per_test):
|
||||
begin = time.time()
|
||||
with open(self.log_file, 'w') as log:
|
||||
task = subprocess.Popen(self.test_command, stdout=log, stderr=log)
|
||||
try:
|
||||
self.exit_code = sigint_handler.wait(task, timeout_per_test)
|
||||
except sigint_handler.ProcessWasInterrupted:
|
||||
thread.exit()
|
||||
self.runtime_ms = int(1000 * (time.time() - begin))
|
||||
self.last_execution_time = None if self.exit_code else self.runtime_ms
|
||||
|
||||
|
||||
class TaskManager(object):
|
||||
"""Executes the tasks and stores the passed, failed and interrupted tasks.
|
||||
|
||||
When a task is run, this class keeps track if it passed, failed or was
|
||||
interrupted. After a task finishes it calls the relevant functions of the
|
||||
Logger, TestResults and TestTimes classes, and in case of failure, retries the
|
||||
test as specified by the --retry_failed flag.
|
||||
"""
|
||||
|
||||
def __init__(self, times, logger, test_results, task_factory, times_to_retry,
|
||||
initial_execution_number):
|
||||
self.times = times
|
||||
self.logger = logger
|
||||
self.test_results = test_results
|
||||
self.task_factory = task_factory
|
||||
self.times_to_retry = times_to_retry
|
||||
self.initial_execution_number = initial_execution_number
|
||||
|
||||
self.global_exit_code = 0
|
||||
|
||||
self.passed = []
|
||||
self.failed = []
|
||||
self.started = {}
|
||||
self.timed_out = []
|
||||
self.execution_number = {}
|
||||
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def __get_next_execution_number(self, test_id):
|
||||
with self.lock:
|
||||
next_execution_number = self.execution_number.setdefault(
|
||||
test_id, self.initial_execution_number)
|
||||
self.execution_number[test_id] += 1
|
||||
return next_execution_number
|
||||
|
||||
def __register_start(self, task):
|
||||
with self.lock:
|
||||
self.started[task.task_id] = task
|
||||
|
||||
def register_exit(self, task):
|
||||
self.logger.log_exit(task)
|
||||
self.times.record_test_time(task.test_binary, task.test_name,
|
||||
task.last_execution_time)
|
||||
if self.test_results:
|
||||
self.test_results.log(task.test_name, task.runtime_ms / 1000.0,
|
||||
task.exit_code)
|
||||
|
||||
with self.lock:
|
||||
self.started.pop(task.task_id)
|
||||
if task.exit_code == 0:
|
||||
self.passed.append(task)
|
||||
elif task.exit_code == -errno.ETIME:
|
||||
self.timed_out.append(task)
|
||||
else:
|
||||
self.failed.append(task)
|
||||
|
||||
def run_task(self, task, timeout_per_test):
|
||||
for try_number in range(self.times_to_retry + 1):
|
||||
self.__register_start(task)
|
||||
task.run(timeout_per_test)
|
||||
self.register_exit(task)
|
||||
|
||||
if task.exit_code == 0:
|
||||
break
|
||||
|
||||
if try_number < self.times_to_retry:
|
||||
execution_number = self.__get_next_execution_number(task.test_id)
|
||||
# We need create a new Task instance. Each task represents a single test
|
||||
# execution, with its own runtime, exit code and log file.
|
||||
task = self.task_factory(task.test_binary, task.test_name,
|
||||
task.test_command, execution_number,
|
||||
task.last_execution_time, task.output_dir)
|
||||
|
||||
with self.lock:
|
||||
if task.exit_code != 0:
|
||||
self.global_exit_code = task.exit_code
|
||||
|
||||
|
||||
class FilterFormat(object):
|
||||
def __init__(self, output_dir):
|
||||
if sys.stdout.isatty():
|
||||
# stdout needs to be unbuffered since the output is interactive.
|
||||
if isinstance(sys.stdout, io.TextIOWrapper):
|
||||
# workaround for https://bugs.python.org/issue17404
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.detach(),
|
||||
line_buffering=True,
|
||||
write_through=True,
|
||||
newline='\n')
|
||||
else:
|
||||
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
|
||||
|
||||
self.output_dir = output_dir
|
||||
|
||||
self.total_tasks = 0
|
||||
self.finished_tasks = 0
|
||||
self.out = Outputter(sys.stdout)
|
||||
self.stdout_lock = threading.Lock()
|
||||
|
||||
def move_to(self, destination_dir, tasks):
|
||||
if self.output_dir is None:
|
||||
return
|
||||
|
||||
destination_dir = os.path.join(self.output_dir, destination_dir)
|
||||
os.makedirs(destination_dir)
|
||||
for task in tasks:
|
||||
shutil.move(task.log_file, destination_dir)
|
||||
|
||||
def print_tests(self, message, tasks, print_try_number, print_test_command):
|
||||
self.out.permanent_line("%s (%s/%s):" %
|
||||
(message, len(tasks), self.total_tasks))
|
||||
for task in sorted(tasks):
|
||||
runtime_ms = 'Interrupted'
|
||||
if task.runtime_ms is not None:
|
||||
runtime_ms = '%d ms' % task.runtime_ms
|
||||
if print_test_command:
|
||||
try:
|
||||
cmd_str = " ".join(task.test_command)
|
||||
except TypeError:
|
||||
cmd_str = task.test_command
|
||||
self.out.permanent_line(
|
||||
"%11s: %s%s" %
|
||||
(runtime_ms, cmd_str,
|
||||
(" (try #%d)" % task.execution_number) if print_try_number else ""))
|
||||
else:
|
||||
self.out.permanent_line(
|
||||
"%11s: %s %s%s" %
|
||||
(runtime_ms, task.test_binary, task.test_name,
|
||||
(" (try #%d)" % task.execution_number) if print_try_number else ""))
|
||||
|
||||
def log_exit(self, task):
|
||||
with self.stdout_lock:
|
||||
self.finished_tasks += 1
|
||||
self.out.transient_line("[%d/%d] %s (%d ms)" %
|
||||
(self.finished_tasks, self.total_tasks,
|
||||
task.test_name, task.runtime_ms))
|
||||
if task.exit_code != 0:
|
||||
signal_name = None
|
||||
if task.exit_code < 0:
|
||||
try:
|
||||
signal_name = signal.Signals(-task.exit_code).name
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
with open(task.log_file) as f:
|
||||
for line in f.readlines():
|
||||
self.out.permanent_line(line.rstrip())
|
||||
if task.exit_code is None:
|
||||
self.out.permanent_line("[%d/%d] %s aborted after %d ms" %
|
||||
(self.finished_tasks, self.total_tasks,
|
||||
task.test_name, task.runtime_ms))
|
||||
elif task.exit_code == -errno.ETIME:
|
||||
self.out.permanent_line(
|
||||
"\033[31m[ TIMEOUT ]\033[0m %s timed out after %d s"
|
||||
% (task.test_name, task.runtime_ms/1000))
|
||||
elif signal_name is not None:
|
||||
self.out.permanent_line(
|
||||
"[%d/%d] %s killed by signal %s (%d ms)" %
|
||||
(self.finished_tasks, self.total_tasks, task.test_name,
|
||||
signal_name, task.runtime_ms))
|
||||
else:
|
||||
self.out.permanent_line(
|
||||
"[%d/%d] %s returned with exit code %d (%d ms)" %
|
||||
(self.finished_tasks, self.total_tasks, task.test_name,
|
||||
task.exit_code, task.runtime_ms))
|
||||
|
||||
if self.output_dir is None:
|
||||
# Try to remove the file 100 times (sleeping for 0.1 second in between).
|
||||
# This is a workaround for a process handle seemingly holding on to the
|
||||
# file for too long inside os.subprocess. This workaround is in place
|
||||
# until we figure out a minimal repro to report upstream (or a better
|
||||
# suspect) to prevent os.remove exceptions.
|
||||
num_tries = 100
|
||||
for i in range(num_tries):
|
||||
try:
|
||||
os.remove(task.log_file)
|
||||
except OSError as e:
|
||||
if e.errno is not errno.ENOENT:
|
||||
if i is num_tries - 1:
|
||||
self.out.permanent_line('Could not remove temporary log file: ' +
|
||||
str(e))
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
break
|
||||
|
||||
def log_tasks(self, total_tasks):
|
||||
self.total_tasks += total_tasks
|
||||
self.out.transient_line("[0/%d] Running tests..." % self.total_tasks)
|
||||
|
||||
def summarize(self, passed_tasks, failed_tasks, interrupted_tasks):
|
||||
stats = {}
|
||||
|
||||
def add_stats(stats, task, idx):
|
||||
task_key = (task.test_binary, task.test_name)
|
||||
if not task_key in stats:
|
||||
# (passed, failed, interrupted) task_key is added as tie breaker to get
|
||||
# alphabetic sorting on equally-stable tests
|
||||
stats[task_key] = [0, 0, 0, task_key]
|
||||
stats[task_key][idx] += 1
|
||||
|
||||
for task in passed_tasks:
|
||||
add_stats(stats, task, 0)
|
||||
for task in failed_tasks:
|
||||
add_stats(stats, task, 1)
|
||||
for task in interrupted_tasks:
|
||||
add_stats(stats, task, 2)
|
||||
|
||||
self.out.permanent_line("SUMMARY:")
|
||||
for task_key in sorted(stats, key=stats.__getitem__):
|
||||
(num_passed, num_failed, num_interrupted, _) = stats[task_key]
|
||||
(test_binary, task_name) = task_key
|
||||
total_runs = num_passed + num_failed + num_interrupted
|
||||
if num_passed == total_runs:
|
||||
continue
|
||||
self.out.permanent_line(" %s %s passed %d / %d times%s." %
|
||||
(test_binary, task_name, num_passed, total_runs,
|
||||
"" if num_interrupted == 0 else
|
||||
(" (%d interrupted)" % num_interrupted)))
|
||||
|
||||
def flush(self):
|
||||
self.out.flush_transient_output()
|
||||
|
||||
|
||||
class CollectTestResults(object):
|
||||
def __init__(self, json_dump_filepath):
|
||||
self.test_results_lock = threading.Lock()
|
||||
self.json_dump_file = open(json_dump_filepath, 'w')
|
||||
self.test_results = {
|
||||
"interrupted": False,
|
||||
"path_delimiter": ".",
|
||||
# Third version of the file format. See the link in the flag description
|
||||
# for details.
|
||||
"version": 3,
|
||||
"seconds_since_epoch": int(time.time()),
|
||||
"num_failures_by_type": {
|
||||
"PASS": 0,
|
||||
"FAIL": 0,
|
||||
"TIMEOUT": 0,
|
||||
},
|
||||
"tests": {},
|
||||
}
|
||||
|
||||
def log(self, test, runtime_seconds, exit_code):
|
||||
if exit_code is None:
|
||||
actual_result = "TIMEOUT"
|
||||
elif exit_code == 0:
|
||||
actual_result = "PASS"
|
||||
else:
|
||||
actual_result = "FAIL"
|
||||
with self.test_results_lock:
|
||||
self.test_results['num_failures_by_type'][actual_result] += 1
|
||||
results = self.test_results['tests']
|
||||
for name in test.split('.'):
|
||||
results = results.setdefault(name, {})
|
||||
|
||||
if results:
|
||||
results['actual'] += ' ' + actual_result
|
||||
results['times'].append(runtime_seconds)
|
||||
else: # This is the first invocation of the test
|
||||
results['actual'] = actual_result
|
||||
results['times'] = [runtime_seconds]
|
||||
results['time'] = runtime_seconds
|
||||
results['expected'] = 'PASS'
|
||||
|
||||
def dump_to_file_and_close(self):
|
||||
json.dump(self.test_results, self.json_dump_file)
|
||||
self.json_dump_file.close()
|
||||
|
||||
|
||||
# Record of test runtimes. Has built-in locking.
|
||||
class TestTimes(object):
|
||||
class LockedFile(object):
|
||||
def __init__(self, filename, mode):
|
||||
self._filename = filename
|
||||
self._mode = mode
|
||||
self._fo = None
|
||||
|
||||
def __enter__(self):
|
||||
self._fo = open(self._filename, self._mode)
|
||||
|
||||
# Regardless of opening mode we always seek to the beginning of file.
|
||||
# This simplifies code working with LockedFile and also ensures that
|
||||
# we lock (and unlock below) always the same region in file on win32.
|
||||
self._fo.seek(0)
|
||||
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
# We are locking here fixed location in file to use it as
|
||||
# an exclusive lock on entire file.
|
||||
msvcrt.locking(self._fo.fileno(), msvcrt.LK_LOCK, 1)
|
||||
else:
|
||||
fcntl.flock(self._fo.fileno(), fcntl.LOCK_EX)
|
||||
except IOError:
|
||||
self._fo.close()
|
||||
raise
|
||||
|
||||
return self._fo
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
# Flush any buffered data to disk. This is needed to prevent race
|
||||
# condition which happens from the moment of releasing file lock
|
||||
# till closing the file.
|
||||
self._fo.flush()
|
||||
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
self._fo.seek(0)
|
||||
msvcrt.locking(self._fo.fileno(), msvcrt.LK_UNLCK, 1)
|
||||
else:
|
||||
fcntl.flock(self._fo.fileno(), fcntl.LOCK_UN)
|
||||
finally:
|
||||
self._fo.close()
|
||||
|
||||
return exc_value is None
|
||||
|
||||
def __init__(self, save_file):
|
||||
"Create new object seeded with saved test times from the given file."
|
||||
self.__times = {} # (test binary, test name) -> runtime in ms
|
||||
|
||||
# Protects calls to record_test_time(); other calls are not
|
||||
# expected to be made concurrently.
|
||||
self.__lock = threading.Lock()
|
||||
|
||||
try:
|
||||
with TestTimes.LockedFile(save_file, 'rb') as fd:
|
||||
times = TestTimes.__read_test_times_file(fd)
|
||||
except IOError:
|
||||
# We couldn't obtain the lock.
|
||||
return
|
||||
|
||||
# Discard saved times if the format isn't right.
|
||||
if type(times) is not dict:
|
||||
return
|
||||
for ((test_binary, test_name), runtime) in times.items():
|
||||
if (type(test_binary) is not str or type(test_name) is not str
|
||||
or type(runtime) not in {int, long, type(None)}):
|
||||
return
|
||||
|
||||
self.__times = times
|
||||
|
||||
def get_test_time(self, binary, testname):
|
||||
"""Return the last duration for the given test as an integer number of
|
||||
milliseconds, or None if the test failed or if there's no record for it."""
|
||||
return self.__times.get((binary, testname), None)
|
||||
|
||||
def record_test_time(self, binary, testname, runtime_ms):
|
||||
"""Record that the given test ran in the specified number of
|
||||
milliseconds. If the test failed, runtime_ms should be None."""
|
||||
with self.__lock:
|
||||
self.__times[(binary, testname)] = runtime_ms
|
||||
|
||||
def write_to_file(self, save_file):
|
||||
"Write all the times to file."
|
||||
try:
|
||||
with TestTimes.LockedFile(save_file, 'a+b') as fd:
|
||||
times = TestTimes.__read_test_times_file(fd)
|
||||
|
||||
if times is None:
|
||||
times = self.__times
|
||||
else:
|
||||
times.update(self.__times)
|
||||
|
||||
# We erase data from file while still holding a lock to it. This
|
||||
# way reading old test times and appending new ones are atomic
|
||||
# for external viewer.
|
||||
fd.seek(0)
|
||||
fd.truncate()
|
||||
with gzip.GzipFile(fileobj=fd, mode='wb') as gzf:
|
||||
cPickle.dump(times, gzf, PICKLE_HIGHEST_PROTOCOL)
|
||||
except IOError:
|
||||
pass # ignore errors---saving the times isn't that important
|
||||
|
||||
@staticmethod
|
||||
def __read_test_times_file(fd):
|
||||
try:
|
||||
with gzip.GzipFile(fileobj=fd, mode='rb') as gzf:
|
||||
times = cPickle.load(gzf)
|
||||
except Exception:
|
||||
# File doesn't exist, isn't readable, is malformed---whatever.
|
||||
# Just ignore it.
|
||||
return None
|
||||
else:
|
||||
return times
|
||||
|
||||
|
||||
def find_tests(binaries, additional_args, options, times):
|
||||
test_count = 0
|
||||
tasks = []
|
||||
for test_binary in binaries:
|
||||
command = [test_binary] + additional_args
|
||||
if options.gtest_also_run_disabled_tests:
|
||||
command += ['--gtest_also_run_disabled_tests']
|
||||
|
||||
list_command = command + ['--gtest_list_tests']
|
||||
if options.gtest_filter != '':
|
||||
list_command += ['--gtest_filter=' + options.gtest_filter]
|
||||
|
||||
try:
|
||||
test_list = subprocess.check_output(list_command,
|
||||
stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
sys.exit("%s: %s\n%s" % (test_binary, str(e), e.output))
|
||||
|
||||
try:
|
||||
test_list = test_list.split('\n')
|
||||
except TypeError:
|
||||
# subprocess.check_output() returns bytes in python3
|
||||
test_list = test_list.decode(sys.stdout.encoding).split('\n')
|
||||
|
||||
command += ['--gtest_color=' + options.gtest_color]
|
||||
|
||||
test_group = ''
|
||||
for line in test_list:
|
||||
if not line.strip():
|
||||
continue
|
||||
if line[0] != " ":
|
||||
# Remove comments for typed tests and strip whitespace.
|
||||
test_group = line.split('#')[0].strip()
|
||||
continue
|
||||
# Remove comments for parameterized tests and strip whitespace.
|
||||
line = line.split('#')[0].strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
test_name = test_group + line
|
||||
if not options.gtest_also_run_disabled_tests and 'DISABLED_' in test_name:
|
||||
continue
|
||||
|
||||
# Skip PRE_ tests which are used by Chromium.
|
||||
if '.PRE_' in test_name:
|
||||
continue
|
||||
|
||||
last_execution_time = times.get_test_time(test_binary, test_name)
|
||||
if options.failed and last_execution_time is not None:
|
||||
continue
|
||||
|
||||
test_command = command + ['--gtest_filter=' + test_name]
|
||||
if (test_count - options.shard_index) % options.shard_count == 0:
|
||||
for execution_number in range(options.repeat):
|
||||
tasks.append(
|
||||
Task(test_binary, test_name, test_command, execution_number + 1,
|
||||
last_execution_time, options.output_dir))
|
||||
|
||||
test_count += 1
|
||||
|
||||
# Sort the tasks to run the slowest tests first, so that faster ones can be
|
||||
# finished in parallel.
|
||||
return sorted(tasks, reverse=True)
|
||||
|
||||
|
||||
def execute_tasks(tasks, pool_size, task_manager, timeout_seconds,
|
||||
timeout_per_test, serialize_test_cases):
|
||||
class WorkerFn(object):
|
||||
def __init__(self, tasks, running_groups, timeout_per_test):
|
||||
self.tasks = tasks
|
||||
self.running_groups = running_groups
|
||||
self.timeout_per_test = timeout_per_test
|
||||
self.task_lock = threading.Lock()
|
||||
|
||||
def __call__(self):
|
||||
while True:
|
||||
with self.task_lock:
|
||||
for task_id in range(len(self.tasks)):
|
||||
task = self.tasks[task_id]
|
||||
|
||||
if self.running_groups is not None:
|
||||
test_group = task.test_name.split('.')[0]
|
||||
if test_group in self.running_groups:
|
||||
# Try to find other non-running test group.
|
||||
continue
|
||||
else:
|
||||
self.running_groups.add(test_group)
|
||||
|
||||
del self.tasks[task_id]
|
||||
break
|
||||
else:
|
||||
# Either there is no tasks left or number or remaining test
|
||||
# cases (groups) is less than number or running threads.
|
||||
return
|
||||
|
||||
task_manager.run_task(task, self.timeout_per_test)
|
||||
|
||||
if self.running_groups is not None:
|
||||
with self.task_lock:
|
||||
self.running_groups.remove(test_group)
|
||||
|
||||
def start_daemon(func):
|
||||
t = threading.Thread(target=func)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
return t
|
||||
|
||||
timeout = None
|
||||
try:
|
||||
if timeout_seconds:
|
||||
timeout = threading.Timer(timeout_seconds, sigint_handler.interrupt)
|
||||
timeout.start()
|
||||
running_groups = set() if serialize_test_cases else None
|
||||
worker_fn = WorkerFn(tasks, running_groups, timeout_per_test)
|
||||
workers = [start_daemon(worker_fn) for _ in range(pool_size)]
|
||||
for worker in workers:
|
||||
worker.join()
|
||||
finally:
|
||||
if timeout:
|
||||
timeout.cancel()
|
||||
for task in list(task_manager.started.values()):
|
||||
task.runtime_ms = timeout_seconds * 1000
|
||||
task_manager.register_exit(task)
|
||||
|
||||
|
||||
def default_options_parser():
|
||||
parser = optparse.OptionParser(
|
||||
usage='usage: %prog [options] binary [binary ...] -- [additional args]')
|
||||
|
||||
parser.add_option('-d',
|
||||
'--output_dir',
|
||||
type='string',
|
||||
default=None,
|
||||
help='Output directory for test logs. Logs will be '
|
||||
'available under gtest-parallel-logs/, so '
|
||||
'--output_dir=/tmp will results in all logs being '
|
||||
'available under /tmp/gtest-parallel-logs/.')
|
||||
parser.add_option('-r',
|
||||
'--repeat',
|
||||
type='int',
|
||||
default=1,
|
||||
help='Number of times to execute all the tests.')
|
||||
parser.add_option('--retry_failed',
|
||||
type='int',
|
||||
default=0,
|
||||
help='Number of times to repeat failed tests.')
|
||||
parser.add_option('--failed',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='run only failed and new tests')
|
||||
parser.add_option('-w',
|
||||
'--workers',
|
||||
type='int',
|
||||
default=multiprocessing.cpu_count(),
|
||||
help='number of workers to spawn')
|
||||
parser.add_option('--gtest_color',
|
||||
type='string',
|
||||
default='yes',
|
||||
help='color output')
|
||||
parser.add_option('--gtest_filter',
|
||||
type='string',
|
||||
default='',
|
||||
help='test filter')
|
||||
parser.add_option('--gtest_also_run_disabled_tests',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='run disabled tests too')
|
||||
parser.add_option(
|
||||
'--print_test_times',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='list the run time of each test at the end of execution')
|
||||
parser.add_option(
|
||||
'--print_test_command',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Print full test command instead of name')
|
||||
parser.add_option('--shard_count',
|
||||
type='int',
|
||||
default=1,
|
||||
help='total number of shards (for sharding test execution '
|
||||
'between multiple machines)')
|
||||
parser.add_option('--shard_index',
|
||||
type='int',
|
||||
default=0,
|
||||
help='zero-indexed number identifying this shard (for '
|
||||
'sharding test execution between multiple machines)')
|
||||
parser.add_option(
|
||||
'--dump_json_test_results',
|
||||
type='string',
|
||||
default=None,
|
||||
help='Saves the results of the tests as a JSON machine-'
|
||||
'readable file. The format of the file is specified at '
|
||||
'https://www.chromium.org/developers/the-json-test-results-format')
|
||||
parser.add_option('--timeout',
|
||||
type='int',
|
||||
default=None,
|
||||
help='Interrupt all remaining processes after the given '
|
||||
'time (in seconds).')
|
||||
parser.add_option('--timeout_per_test',
|
||||
type='int',
|
||||
default=None,
|
||||
help='Interrupt single processes after the given '
|
||||
'time (in seconds).')
|
||||
parser.add_option('--serialize_test_cases',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Do not run tests from the same test '
|
||||
'case in parallel.')
|
||||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
# Remove additional arguments (anything after --).
|
||||
additional_args = []
|
||||
|
||||
for i in range(len(sys.argv)):
|
||||
if sys.argv[i] == '--':
|
||||
additional_args = sys.argv[i + 1:]
|
||||
sys.argv = sys.argv[:i]
|
||||
break
|
||||
|
||||
parser = default_options_parser()
|
||||
(options, binaries) = parser.parse_args()
|
||||
|
||||
if (options.output_dir is not None and not os.path.isdir(options.output_dir)):
|
||||
parser.error('--output_dir value must be an existing directory, '
|
||||
'current value is "%s"' % options.output_dir)
|
||||
|
||||
# Append gtest-parallel-logs to log output, this is to avoid deleting user
|
||||
# data if an user passes a directory where files are already present. If a
|
||||
# user specifies --output_dir=Docs/, we'll create Docs/gtest-parallel-logs
|
||||
# and clean that directory out on startup, instead of nuking Docs/.
|
||||
if options.output_dir:
|
||||
options.output_dir = os.path.join(options.output_dir, 'gtest-parallel-logs')
|
||||
|
||||
if binaries == []:
|
||||
parser.print_usage()
|
||||
sys.exit(1)
|
||||
|
||||
if options.shard_count < 1:
|
||||
parser.error("Invalid number of shards: %d. Must be at least 1." %
|
||||
options.shard_count)
|
||||
if not (0 <= options.shard_index < options.shard_count):
|
||||
parser.error("Invalid shard index: %d. Must be between 0 and %d "
|
||||
"(less than the number of shards)." %
|
||||
(options.shard_index, options.shard_count - 1))
|
||||
|
||||
# Check that all test binaries have an unique basename. That way we can ensure
|
||||
# the logs are saved to unique files even when two different binaries have
|
||||
# common tests.
|
||||
unique_binaries = set(os.path.basename(binary) for binary in binaries)
|
||||
assert len(unique_binaries) == len(binaries), (
|
||||
"All test binaries must have an unique basename.")
|
||||
|
||||
if options.output_dir:
|
||||
# Remove files from old test runs.
|
||||
if os.path.isdir(options.output_dir):
|
||||
shutil.rmtree(options.output_dir)
|
||||
# Create directory for test log output.
|
||||
try:
|
||||
os.makedirs(options.output_dir)
|
||||
except OSError as e:
|
||||
# Ignore errors if this directory already exists.
|
||||
if e.errno != errno.EEXIST or not os.path.isdir(options.output_dir):
|
||||
raise e
|
||||
|
||||
test_results = None
|
||||
if options.dump_json_test_results is not None:
|
||||
test_results = CollectTestResults(options.dump_json_test_results)
|
||||
|
||||
save_file = get_save_file_path()
|
||||
|
||||
times = TestTimes(save_file)
|
||||
logger = FilterFormat(options.output_dir)
|
||||
|
||||
task_manager = TaskManager(times, logger, test_results, Task,
|
||||
options.retry_failed, options.repeat + 1)
|
||||
|
||||
tasks = find_tests(binaries, additional_args, options, times)
|
||||
logger.log_tasks(len(tasks))
|
||||
execute_tasks(tasks, options.workers, task_manager, options.timeout,
|
||||
options.timeout_per_test, options.serialize_test_cases)
|
||||
|
||||
print_try_number = options.retry_failed > 0 or options.repeat > 1
|
||||
if task_manager.passed:
|
||||
logger.move_to('passed', task_manager.passed)
|
||||
if options.print_test_times:
|
||||
logger.print_tests('PASSED TESTS', task_manager.passed, print_try_number, options.print_test_command)
|
||||
|
||||
if task_manager.failed:
|
||||
logger.print_tests('FAILED TESTS', task_manager.failed, print_try_number, options.print_test_command)
|
||||
logger.move_to('failed', task_manager.failed)
|
||||
|
||||
if task_manager.timed_out:
|
||||
logger.print_tests('TIMED OUT TESTS', task_manager.timed_out, print_try_number, options.print_test_command)
|
||||
logger.move_to('timed_out', task_manager.timed_out)
|
||||
|
||||
if task_manager.started:
|
||||
logger.print_tests('INTERRUPTED TESTS', task_manager.started.values(),
|
||||
print_try_number, options.print_test_command)
|
||||
logger.move_to('interrupted', task_manager.started.values())
|
||||
|
||||
if options.repeat > 1 and (task_manager.failed or task_manager.started):
|
||||
logger.summarize(task_manager.passed, task_manager.failed,
|
||||
task_manager.started.values())
|
||||
|
||||
logger.flush()
|
||||
times.write_to_file(save_file)
|
||||
if test_results:
|
||||
test_results.dump_to_file_and_close()
|
||||
|
||||
if sigint_handler.got_sigint():
|
||||
return -signal.SIGINT
|
||||
|
||||
return task_manager.global_exit_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
# Copyright 2017 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import collections
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
class LoggerMock(object):
|
||||
def __init__(self, test_lib):
|
||||
self.test_lib = test_lib
|
||||
self.runtimes = collections.defaultdict(list)
|
||||
self.exit_codes = collections.defaultdict(list)
|
||||
self.last_execution_times = collections.defaultdict(list)
|
||||
self.execution_numbers = collections.defaultdict(list)
|
||||
|
||||
def log_exit(self, task):
|
||||
self.runtimes[task.test_id].append(task.runtime_ms)
|
||||
self.exit_codes[task.test_id].append(task.exit_code)
|
||||
self.last_execution_times[task.test_id].append(task.last_execution_time)
|
||||
self.execution_numbers[task.test_id].append(task.execution_number)
|
||||
|
||||
def assertRecorded(self, test_id, expected, retries):
|
||||
self.test_lib.assertIn(test_id, self.runtimes)
|
||||
self.test_lib.assertListEqual(expected['runtime_ms'][:retries],
|
||||
self.runtimes[test_id])
|
||||
self.test_lib.assertListEqual(expected['exit_code'][:retries],
|
||||
self.exit_codes[test_id])
|
||||
self.test_lib.assertListEqual(expected['last_execution_time'][:retries],
|
||||
self.last_execution_times[test_id])
|
||||
self.test_lib.assertListEqual(expected['execution_number'][:retries],
|
||||
self.execution_numbers[test_id])
|
||||
|
||||
|
||||
class TestTimesMock(object):
|
||||
def __init__(self, test_lib, test_data=None):
|
||||
self.test_lib = test_lib
|
||||
self.test_data = test_data or {}
|
||||
self.last_execution_times = collections.defaultdict(list)
|
||||
|
||||
def record_test_time(self, test_binary, test_name, last_execution_time):
|
||||
test_id = (test_binary, test_name)
|
||||
self.last_execution_times[test_id].append(last_execution_time)
|
||||
|
||||
def get_test_time(self, test_binary, test_name):
|
||||
test_group, test = test_name.split('.')
|
||||
return self.test_data.get(test_binary, {}).get(test_group,
|
||||
{}).get(test, None)
|
||||
|
||||
def assertRecorded(self, test_id, expected, retries):
|
||||
self.test_lib.assertIn(test_id, self.last_execution_times)
|
||||
self.test_lib.assertListEqual(expected['last_execution_time'][:retries],
|
||||
self.last_execution_times[test_id])
|
||||
|
||||
|
||||
class TestResultsMock(object):
|
||||
def __init__(self, test_lib):
|
||||
self.results = []
|
||||
self.test_lib = test_lib
|
||||
|
||||
def log(self, test_name, runtime_ms, actual_result):
|
||||
self.results.append((test_name, runtime_ms, actual_result))
|
||||
|
||||
def assertRecorded(self, test_id, expected, retries):
|
||||
test_results = [
|
||||
(test_id[1], runtime_ms / 1000.0, exit_code)
|
||||
for runtime_ms, exit_code in zip(expected['runtime_ms'][:retries],
|
||||
expected['exit_code'][:retries])
|
||||
]
|
||||
for test_result in test_results:
|
||||
self.test_lib.assertIn(test_result, self.results)
|
||||
|
||||
|
||||
class TaskManagerMock(object):
|
||||
def __init__(self):
|
||||
self.running_groups = []
|
||||
self.check_lock = threading.Lock()
|
||||
|
||||
self.had_running_parallel_groups = False
|
||||
self.total_tasks_run = 0
|
||||
self.started = {}
|
||||
|
||||
def __register_start(self, task):
|
||||
self.started[task.task_id] = task
|
||||
|
||||
def register_exit(self, task):
|
||||
self.started.pop(task.task_id)
|
||||
|
||||
def run_task(self, task, timeout_per_test):
|
||||
self.__register_start(task)
|
||||
test_group = task.test_name.split('.')[0]
|
||||
|
||||
with self.check_lock:
|
||||
self.total_tasks_run += 1
|
||||
if test_group in self.running_groups:
|
||||
self.had_running_parallel_groups = True
|
||||
self.running_groups.append(test_group)
|
||||
|
||||
# Delay as if real test were run.
|
||||
time.sleep(0.001)
|
||||
|
||||
with self.check_lock:
|
||||
self.running_groups.remove(test_group)
|
||||
|
||||
|
||||
class TaskMockFactory(object):
|
||||
def __init__(self, test_data):
|
||||
self.data = test_data
|
||||
self.passed = []
|
||||
self.failed = []
|
||||
|
||||
def get_task(self, test_id, execution_number=0):
|
||||
task = TaskMock(test_id, execution_number, self.data[test_id])
|
||||
if task.exit_code == 0:
|
||||
self.passed.append(task)
|
||||
else:
|
||||
self.failed.append(task)
|
||||
return task
|
||||
|
||||
def __call__(self, test_binary, test_name, test_command, execution_number,
|
||||
last_execution_time, output_dir):
|
||||
return self.get_task((test_binary, test_name), execution_number)
|
||||
|
||||
|
||||
class TaskMock(object):
|
||||
def __init__(self, test_id, execution_number, test_data):
|
||||
self.test_id = test_id
|
||||
self.execution_number = execution_number
|
||||
|
||||
self.runtime_ms = test_data['runtime_ms'][execution_number]
|
||||
self.exit_code = test_data['exit_code'][execution_number]
|
||||
self.last_execution_time = (
|
||||
test_data['last_execution_time'][execution_number])
|
||||
if 'log_file' in test_data:
|
||||
self.log_file = test_data['log_file'][execution_number]
|
||||
else:
|
||||
self.log_file = None
|
||||
self.test_command = None
|
||||
self.output_dir = None
|
||||
|
||||
self.test_binary = test_id[0]
|
||||
self.test_name = test_id[1]
|
||||
self.task_id = (test_id[0], test_id[1], execution_number)
|
||||
|
||||
def run(self, timeout_per_test):
|
||||
pass
|
||||
|
||||
|
||||
class SubprocessMock(object):
|
||||
def __init__(self, test_data=None):
|
||||
self._test_data = test_data
|
||||
self.last_invocation = None
|
||||
|
||||
def __call__(self, command, **kwargs):
|
||||
self.last_invocation = command
|
||||
binary = command[0]
|
||||
test_list = []
|
||||
tests_for_binary = sorted(self._test_data.get(binary, {}).items())
|
||||
for test_group, tests in tests_for_binary:
|
||||
test_list.append(test_group + ".")
|
||||
for test in sorted(tests):
|
||||
test_list.append(" " + test)
|
||||
return '\n'.join(test_list)
|
||||
+656
@@ -0,0 +1,656 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2017 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import contextlib
|
||||
import os.path
|
||||
import random
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
import gtest_parallel
|
||||
|
||||
from gtest_parallel_mocks import LoggerMock
|
||||
from gtest_parallel_mocks import SubprocessMock
|
||||
from gtest_parallel_mocks import TestTimesMock
|
||||
from gtest_parallel_mocks import TestResultsMock
|
||||
from gtest_parallel_mocks import TaskManagerMock
|
||||
from gtest_parallel_mocks import TaskMockFactory
|
||||
from gtest_parallel_mocks import TaskMock
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def guard_temp_dir():
|
||||
try:
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
yield temp_dir
|
||||
finally:
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def guard_temp_subdir(temp_dir, *path):
|
||||
assert path, 'Path should not be empty'
|
||||
|
||||
try:
|
||||
temp_subdir = os.path.join(temp_dir, *path)
|
||||
os.makedirs(temp_subdir)
|
||||
yield temp_subdir
|
||||
finally:
|
||||
shutil.rmtree(os.path.join(temp_dir, path[0]))
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def guard_patch_module(import_name, new_val):
|
||||
def patch(module, names, val):
|
||||
if len(names) == 1:
|
||||
old = getattr(module, names[0])
|
||||
setattr(module, names[0], val)
|
||||
return old
|
||||
else:
|
||||
return patch(getattr(module, names[0]), names[1:], val)
|
||||
|
||||
try:
|
||||
old_val = patch(gtest_parallel, import_name.split('.'), new_val)
|
||||
yield old_val
|
||||
finally:
|
||||
patch(gtest_parallel, import_name.split('.'), old_val)
|
||||
|
||||
|
||||
class TestTaskManager(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.passing_task = (('fake_binary', 'Fake.PassingTest'), {
|
||||
'runtime_ms': [10],
|
||||
'exit_code': [0],
|
||||
'last_execution_time': [10],
|
||||
})
|
||||
self.failing_task = (('fake_binary', 'Fake.FailingTest'), {
|
||||
'runtime_ms': [20, 30, 40],
|
||||
'exit_code': [1, 1, 1],
|
||||
'last_execution_time': [None, None, None],
|
||||
})
|
||||
self.fails_once_then_succeeds = (('another_binary', 'Fake.Test.FailOnce'), {
|
||||
'runtime_ms': [21, 22],
|
||||
'exit_code': [1, 0],
|
||||
'last_execution_time': [None, 22],
|
||||
})
|
||||
self.fails_twice_then_succeeds = (('yet_another_binary',
|
||||
'Fake.Test.FailTwice'), {
|
||||
'runtime_ms': [23, 25, 24],
|
||||
'exit_code': [1, 1, 0],
|
||||
'last_execution_time':
|
||||
[None, None, 24],
|
||||
})
|
||||
|
||||
def execute_tasks(self, tasks, retries, expected_exit_code):
|
||||
repeat = 1
|
||||
|
||||
times = TestTimesMock(self)
|
||||
logger = LoggerMock(self)
|
||||
test_results = TestResultsMock(self)
|
||||
|
||||
task_mock_factory = TaskMockFactory(dict(tasks))
|
||||
task_manager = gtest_parallel.TaskManager(times, logger, test_results,
|
||||
task_mock_factory, retries,
|
||||
repeat)
|
||||
|
||||
for test_id, expected in tasks:
|
||||
task = task_mock_factory.get_task(test_id)
|
||||
task_manager.run_task(task, timeout_per_test=30)
|
||||
expected['execution_number'] = list(range(len(expected['exit_code'])))
|
||||
|
||||
logger.assertRecorded(test_id, expected, retries + 1)
|
||||
times.assertRecorded(test_id, expected, retries + 1)
|
||||
test_results.assertRecorded(test_id, expected, retries + 1)
|
||||
|
||||
self.assertEqual(len(task_manager.started), 0)
|
||||
self.assertListEqual(
|
||||
sorted(task.task_id for task in task_manager.passed),
|
||||
sorted(task.task_id for task in task_mock_factory.passed))
|
||||
self.assertListEqual(
|
||||
sorted(task.task_id for task in task_manager.failed),
|
||||
sorted(task.task_id for task in task_mock_factory.failed))
|
||||
|
||||
self.assertEqual(task_manager.global_exit_code, expected_exit_code)
|
||||
|
||||
def test_passing_task_succeeds(self):
|
||||
self.execute_tasks(tasks=[self.passing_task],
|
||||
retries=0,
|
||||
expected_exit_code=0)
|
||||
|
||||
def test_failing_task_fails(self):
|
||||
self.execute_tasks(tasks=[self.failing_task],
|
||||
retries=0,
|
||||
expected_exit_code=1)
|
||||
|
||||
def test_failing_task_fails_even_with_retries(self):
|
||||
self.execute_tasks(tasks=[self.failing_task],
|
||||
retries=2,
|
||||
expected_exit_code=1)
|
||||
|
||||
def test_executing_passing_and_failing_fails(self):
|
||||
# Executing both a faling test and a passing one should make gtest-parallel
|
||||
# fail, no matter if the failing task is run first or last.
|
||||
self.execute_tasks(tasks=[self.failing_task, self.passing_task],
|
||||
retries=2,
|
||||
expected_exit_code=1)
|
||||
|
||||
self.execute_tasks(tasks=[self.passing_task, self.failing_task],
|
||||
retries=2,
|
||||
expected_exit_code=1)
|
||||
|
||||
def test_task_succeeds_with_one_retry(self):
|
||||
# Executes test and retries once. The first run should fail and the second
|
||||
# succeed, so gtest-parallel should succeed.
|
||||
self.execute_tasks(tasks=[self.fails_once_then_succeeds],
|
||||
retries=1,
|
||||
expected_exit_code=0)
|
||||
|
||||
def test_task_fails_with_one_retry(self):
|
||||
# Executes test and retries once, not enough for the test to start passing,
|
||||
# so gtest-parallel should return an error.
|
||||
self.execute_tasks(tasks=[self.fails_twice_then_succeeds],
|
||||
retries=1,
|
||||
expected_exit_code=1)
|
||||
|
||||
def test_runner_succeeds_when_all_tasks_eventually_succeeds(self):
|
||||
# Executes the test and retries twice. One test should pass in the first
|
||||
# attempt, another should take two runs, and the last one should take three
|
||||
# runs. All tests should succeed, so gtest-parallel should succeed too.
|
||||
self.execute_tasks(tasks=[
|
||||
self.passing_task, self.fails_once_then_succeeds,
|
||||
self.fails_twice_then_succeeds
|
||||
],
|
||||
retries=2,
|
||||
expected_exit_code=0)
|
||||
|
||||
|
||||
class TestSaveFilePath(unittest.TestCase):
|
||||
class StreamMock(object):
|
||||
def write(*args):
|
||||
# Suppress any output.
|
||||
pass
|
||||
|
||||
def test_get_save_file_path_unix(self):
|
||||
with guard_temp_dir() as temp_dir, \
|
||||
guard_patch_module('os.path.expanduser', lambda p: temp_dir), \
|
||||
guard_patch_module('sys.stderr', TestSaveFilePath.StreamMock()), \
|
||||
guard_patch_module('sys.platform', 'darwin'):
|
||||
with guard_patch_module('os.environ', {}), \
|
||||
guard_temp_subdir(temp_dir, '.cache'):
|
||||
self.assertEqual(os.path.join(temp_dir, '.cache', 'gtest-parallel'),
|
||||
gtest_parallel.get_save_file_path())
|
||||
|
||||
with guard_patch_module('os.environ', {'XDG_CACHE_HOME': temp_dir}):
|
||||
self.assertEqual(os.path.join(temp_dir, 'gtest-parallel'),
|
||||
gtest_parallel.get_save_file_path())
|
||||
|
||||
with guard_patch_module('os.environ',
|
||||
{'XDG_CACHE_HOME': os.path.realpath(__file__)}):
|
||||
self.assertEqual(os.path.join(temp_dir, '.gtest-parallel-times'),
|
||||
gtest_parallel.get_save_file_path())
|
||||
|
||||
def test_get_save_file_path_win32(self):
|
||||
with guard_temp_dir() as temp_dir, \
|
||||
guard_patch_module('os.path.expanduser', lambda p: temp_dir), \
|
||||
guard_patch_module('sys.stderr', TestSaveFilePath.StreamMock()), \
|
||||
guard_patch_module('sys.platform', 'win32'):
|
||||
with guard_patch_module('os.environ', {}), \
|
||||
guard_temp_subdir(temp_dir, 'AppData', 'Local'):
|
||||
self.assertEqual(
|
||||
os.path.join(temp_dir, 'AppData', 'Local', 'gtest-parallel'),
|
||||
gtest_parallel.get_save_file_path())
|
||||
|
||||
with guard_patch_module('os.environ', {'LOCALAPPDATA': temp_dir}):
|
||||
self.assertEqual(os.path.join(temp_dir, 'gtest-parallel'),
|
||||
gtest_parallel.get_save_file_path())
|
||||
|
||||
with guard_patch_module('os.environ',
|
||||
{'LOCALAPPDATA': os.path.realpath(__file__)}):
|
||||
self.assertEqual(os.path.join(temp_dir, '.gtest-parallel-times'),
|
||||
gtest_parallel.get_save_file_path())
|
||||
|
||||
|
||||
class TestSerializeTestCases(unittest.TestCase):
|
||||
def _execute_tasks(self, max_number_of_test_cases,
|
||||
max_number_of_tests_per_test_case, max_number_of_repeats,
|
||||
max_number_of_workers, timeout_per_test, serialize_test_cases):
|
||||
tasks = []
|
||||
for test_case in range(max_number_of_test_cases):
|
||||
for test_name in range(max_number_of_tests_per_test_case):
|
||||
# All arguments for gtest_parallel.Task except for test_name are fake.
|
||||
test_name = 'TestCase{}.test{}'.format(test_case, test_name)
|
||||
|
||||
for execution_number in range(random.randint(1, max_number_of_repeats)):
|
||||
tasks.append(
|
||||
gtest_parallel.Task('path/to/binary', test_name,
|
||||
['path/to/binary', '--gtest_filter=*'],
|
||||
execution_number + 1, None, 'path/to/output'))
|
||||
|
||||
expected_tasks_number = len(tasks)
|
||||
|
||||
task_manager = TaskManagerMock()
|
||||
|
||||
gtest_parallel.execute_tasks(tasks, max_number_of_workers, task_manager,
|
||||
None, timeout_per_test, serialize_test_cases)
|
||||
|
||||
self.assertEqual(serialize_test_cases,
|
||||
not task_manager.had_running_parallel_groups)
|
||||
self.assertEqual(expected_tasks_number, task_manager.total_tasks_run)
|
||||
|
||||
def test_running_parallel_test_cases_without_repeats(self):
|
||||
self._execute_tasks(max_number_of_test_cases=4,
|
||||
max_number_of_tests_per_test_case=32,
|
||||
max_number_of_repeats=1,
|
||||
max_number_of_workers=16,
|
||||
timeout_per_test=30,
|
||||
serialize_test_cases=True)
|
||||
|
||||
def test_running_parallel_test_cases_with_repeats(self):
|
||||
self._execute_tasks(max_number_of_test_cases=4,
|
||||
max_number_of_tests_per_test_case=32,
|
||||
max_number_of_repeats=4,
|
||||
max_number_of_workers=16,
|
||||
timeout_per_test=30,
|
||||
serialize_test_cases=True)
|
||||
|
||||
def test_running_parallel_tests(self):
|
||||
self._execute_tasks(max_number_of_test_cases=4,
|
||||
max_number_of_tests_per_test_case=128,
|
||||
max_number_of_repeats=1,
|
||||
max_number_of_workers=16,
|
||||
timeout_per_test=30,
|
||||
serialize_test_cases=False)
|
||||
|
||||
|
||||
class TestTestTimes(unittest.TestCase):
|
||||
def test_race_in_test_times_load_save(self):
|
||||
max_number_of_workers = 8
|
||||
max_number_of_read_write_cycles = 64
|
||||
test_times_file_name = 'test_times.pickle'
|
||||
|
||||
def start_worker(save_file):
|
||||
def test_times_worker():
|
||||
thread_id = threading.current_thread().ident
|
||||
path_to_binary = 'path/to/binary' + hex(thread_id)
|
||||
|
||||
for cnt in range(max_number_of_read_write_cycles):
|
||||
times = gtest_parallel.TestTimes(save_file)
|
||||
|
||||
threads_test_times = [
|
||||
binary for (binary, _) in times._TestTimes__times.keys()
|
||||
if binary.startswith(path_to_binary)
|
||||
]
|
||||
|
||||
self.assertEqual(cnt, len(threads_test_times))
|
||||
|
||||
times.record_test_time('{}-{}'.format(path_to_binary, cnt),
|
||||
'TestFoo.testBar', 1000)
|
||||
|
||||
times.write_to_file(save_file)
|
||||
|
||||
self.assertEqual(
|
||||
1000,
|
||||
times.get_test_time('{}-{}'.format(path_to_binary, cnt),
|
||||
'TestFoo.testBar'))
|
||||
self.assertIsNone(
|
||||
times.get_test_time('{}-{}'.format(path_to_binary, cnt), 'baz'))
|
||||
|
||||
t = threading.Thread(target=test_times_worker)
|
||||
t.start()
|
||||
return t
|
||||
|
||||
with guard_temp_dir() as temp_dir:
|
||||
try:
|
||||
workers = [
|
||||
start_worker(os.path.join(temp_dir, test_times_file_name))
|
||||
for _ in range(max_number_of_workers)
|
||||
]
|
||||
finally:
|
||||
for worker in workers:
|
||||
worker.join()
|
||||
|
||||
|
||||
class TestTimeoutTestCases(unittest.TestCase):
|
||||
def test_task_timeout(self):
|
||||
timeout = 1
|
||||
pool_size = 1
|
||||
timeout_per_test = 30
|
||||
task = gtest_parallel.Task('test_binary', 'test_name', ['test_command'], 1,
|
||||
None, 'output_dir')
|
||||
tasks = [task]
|
||||
|
||||
task_manager = TaskManagerMock()
|
||||
gtest_parallel.execute_tasks(tasks, pool_size, task_manager, timeout, timeout_per_test, True)
|
||||
|
||||
self.assertEqual(1, task_manager.total_tasks_run)
|
||||
self.assertEqual(None, task.exit_code)
|
||||
self.assertEqual(1000, task.runtime_ms)
|
||||
|
||||
|
||||
class TestTask(unittest.TestCase):
|
||||
def test_log_file_names(self):
|
||||
def root():
|
||||
return 'C:\\' if sys.platform == 'win32' else '/'
|
||||
|
||||
self.assertEqual(os.path.join('.', 'bin-Test_case-100.log'),
|
||||
gtest_parallel.Task._logname('.', 'bin', 'Test.case', 100))
|
||||
|
||||
self.assertEqual(
|
||||
os.path.join('..', 'a', 'b', 'bin-Test_case_2-1.log'),
|
||||
gtest_parallel.Task._logname(os.path.join('..', 'a', 'b'),
|
||||
os.path.join('..', 'bin'), 'Test.case/2',
|
||||
1))
|
||||
|
||||
self.assertEqual(
|
||||
os.path.join('..', 'a', 'b', 'bin-Test_case_2-5.log'),
|
||||
gtest_parallel.Task._logname(os.path.join('..', 'a', 'b'),
|
||||
os.path.join(root(), 'c', 'd', 'bin'),
|
||||
'Test.case/2', 5))
|
||||
|
||||
self.assertEqual(
|
||||
os.path.join(root(), 'a', 'b', 'bin-Instantiation_Test_case_2-3.log'),
|
||||
gtest_parallel.Task._logname(os.path.join(root(), 'a', 'b'),
|
||||
os.path.join('..', 'c', 'bin'),
|
||||
'Instantiation/Test.case/2', 3))
|
||||
|
||||
self.assertEqual(
|
||||
os.path.join(root(), 'a', 'b', 'bin-Test_case-1.log'),
|
||||
gtest_parallel.Task._logname(os.path.join(root(), 'a', 'b'),
|
||||
os.path.join(root(), 'c', 'd', 'bin'),
|
||||
'Test.case', 1))
|
||||
|
||||
def test_logs_to_temporary_files_without_output_dir(self):
|
||||
log_file = gtest_parallel.Task._logname(None, None, None, None)
|
||||
self.assertEqual(tempfile.gettempdir(), os.path.dirname(log_file))
|
||||
os.remove(log_file)
|
||||
|
||||
def _execute_run_test(self, run_test_body, interrupt_test):
|
||||
def popen_mock(*_args, **_kwargs):
|
||||
return None
|
||||
|
||||
class SigHandlerMock(object):
|
||||
class ProcessWasInterrupted(Exception):
|
||||
pass
|
||||
|
||||
def wait(*_args):
|
||||
if interrupt_test:
|
||||
raise SigHandlerMock.ProcessWasInterrupted()
|
||||
|
||||
return 42
|
||||
|
||||
with guard_temp_dir() as temp_dir, \
|
||||
guard_patch_module('subprocess.Popen', popen_mock), \
|
||||
guard_patch_module('sigint_handler', SigHandlerMock()), \
|
||||
guard_patch_module('thread.exit', lambda: None):
|
||||
run_test_body(temp_dir)
|
||||
|
||||
def test_run_normal_task(self):
|
||||
def run_test(temp_dir):
|
||||
task = gtest_parallel.Task('fake/binary', 'test', ['fake/binary'], 1,
|
||||
None, temp_dir)
|
||||
|
||||
self.assertFalse(os.path.isfile(task.log_file))
|
||||
|
||||
task.run(timeout_per_test=30)
|
||||
|
||||
self.assertTrue(os.path.isfile(task.log_file))
|
||||
self.assertEqual(42, task.exit_code)
|
||||
|
||||
self._execute_run_test(run_test, False)
|
||||
|
||||
def test_run_interrupted_task_with_transient_log(self):
|
||||
def run_test(_):
|
||||
task = gtest_parallel.Task('fake/binary', 'test', ['fake/binary'], 1,
|
||||
None, None)
|
||||
|
||||
self.assertTrue(os.path.isfile(task.log_file))
|
||||
|
||||
task.run(timeout_per_test=30)
|
||||
|
||||
self.assertTrue(os.path.isfile(task.log_file))
|
||||
self.assertIsNone(task.exit_code)
|
||||
|
||||
self._execute_run_test(run_test, True)
|
||||
|
||||
|
||||
class TestFilterFormat(unittest.TestCase):
|
||||
def _execute_test(self, test_body, drop_output):
|
||||
class StdoutMock(object):
|
||||
def isatty(*_args):
|
||||
return False
|
||||
|
||||
def write(*args):
|
||||
pass
|
||||
|
||||
def flush(*args):
|
||||
pass
|
||||
|
||||
with guard_temp_dir() as temp_dir, \
|
||||
guard_patch_module('sys.stdout', StdoutMock()):
|
||||
logger = gtest_parallel.FilterFormat(None if drop_output else temp_dir)
|
||||
logger.log_tasks(42)
|
||||
|
||||
test_body(logger)
|
||||
|
||||
logger.flush()
|
||||
|
||||
def test_no_output_dir(self):
|
||||
def run_test(logger):
|
||||
passed = [
|
||||
TaskMock(
|
||||
('fake/binary', 'FakeTest'), 0, {
|
||||
'runtime_ms': [10],
|
||||
'exit_code': [0],
|
||||
'last_execution_time': [10],
|
||||
'log_file': [os.path.join(tempfile.gettempdir(), 'fake.log')]
|
||||
})
|
||||
]
|
||||
|
||||
open(passed[0].log_file, 'w').close()
|
||||
self.assertTrue(os.path.isfile(passed[0].log_file))
|
||||
|
||||
logger.log_exit(passed[0])
|
||||
|
||||
self.assertFalse(os.path.isfile(passed[0].log_file))
|
||||
|
||||
logger.print_tests('', passed, print_try_number=True, print_test_command=True)
|
||||
logger.move_to(None, passed)
|
||||
|
||||
logger.summarize(passed, [], [])
|
||||
|
||||
self._execute_test(run_test, True)
|
||||
|
||||
def test_with_output_dir(self):
|
||||
def run_test(logger):
|
||||
failed = [
|
||||
TaskMock(
|
||||
('fake/binary', 'FakeTest'), 0, {
|
||||
'runtime_ms': [10],
|
||||
'exit_code': [1],
|
||||
'last_execution_time': [10],
|
||||
'log_file': [os.path.join(logger.output_dir, 'fake.log')]
|
||||
})
|
||||
]
|
||||
|
||||
open(failed[0].log_file, 'w').close()
|
||||
self.assertTrue(os.path.isfile(failed[0].log_file))
|
||||
|
||||
logger.log_exit(failed[0])
|
||||
|
||||
self.assertTrue(os.path.isfile(failed[0].log_file))
|
||||
|
||||
logger.print_tests('', failed, print_try_number=True, print_test_command=True)
|
||||
logger.move_to('failed', failed)
|
||||
|
||||
self.assertFalse(os.path.isfile(failed[0].log_file))
|
||||
self.assertTrue(
|
||||
os.path.isfile(os.path.join(logger.output_dir, 'failed', 'fake.log')))
|
||||
|
||||
logger.summarize([], failed, [])
|
||||
|
||||
self._execute_test(run_test, False)
|
||||
|
||||
|
||||
class TestFindTests(unittest.TestCase):
|
||||
ONE_DISABLED_ONE_ENABLED_TEST = {
|
||||
"fake_unittests": {
|
||||
"FakeTest": {
|
||||
"Test1": None,
|
||||
"DISABLED_Test2": None,
|
||||
}
|
||||
}
|
||||
}
|
||||
ONE_FAILED_ONE_PASSED_TEST = {
|
||||
"fake_unittests": {
|
||||
"FakeTest": {
|
||||
# Failed (and new) tests have no recorded runtime.
|
||||
"FailedTest": None,
|
||||
"Test": 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
ONE_TEST = {
|
||||
"fake_unittests": {
|
||||
"FakeTest": {
|
||||
"TestSomething": None,
|
||||
}
|
||||
}
|
||||
}
|
||||
MULTIPLE_BINARIES_MULTIPLE_TESTS_ONE_FAILURE = {
|
||||
"fake_unittests": {
|
||||
"FakeTest": {
|
||||
"TestSomething": None,
|
||||
"TestSomethingElse": 2,
|
||||
},
|
||||
"SomeOtherTest": {
|
||||
"YetAnotherTest": 3,
|
||||
},
|
||||
},
|
||||
"fake_tests": {
|
||||
"Foo": {
|
||||
"Bar": 4,
|
||||
"Baz": 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def _process_options(self, options):
|
||||
parser = gtest_parallel.default_options_parser()
|
||||
options, binaries = parser.parse_args(options)
|
||||
self.assertEqual(len(binaries), 0)
|
||||
return options
|
||||
|
||||
def _call_find_tests(self, test_data, options=None):
|
||||
subprocess_mock = SubprocessMock(test_data)
|
||||
options = self._process_options(options or [])
|
||||
with guard_patch_module('subprocess.check_output', subprocess_mock):
|
||||
tasks = gtest_parallel.find_tests(test_data.keys(), [], options,
|
||||
TestTimesMock(self, test_data))
|
||||
# Clean transient tasks' log files created because
|
||||
# by default now output_dir is None.
|
||||
for task in tasks:
|
||||
if os.path.isfile(task.log_file):
|
||||
os.remove(task.log_file)
|
||||
return tasks, subprocess_mock
|
||||
|
||||
def test_tasks_are_sorted(self):
|
||||
tasks, _ = self._call_find_tests(
|
||||
self.MULTIPLE_BINARIES_MULTIPLE_TESTS_ONE_FAILURE)
|
||||
self.assertEqual([task.last_execution_time for task in tasks],
|
||||
[None, 4, 4, 3, 2])
|
||||
|
||||
def test_does_not_run_disabled_tests_by_default(self):
|
||||
tasks, subprocess_mock = self._call_find_tests(
|
||||
self.ONE_DISABLED_ONE_ENABLED_TEST)
|
||||
self.assertEqual(len(tasks), 1)
|
||||
self.assertFalse("DISABLED_" in tasks[0].test_name)
|
||||
self.assertNotIn("--gtest_also_run_disabled_tests",
|
||||
subprocess_mock.last_invocation)
|
||||
|
||||
def test_runs_disabled_tests_when_asked(self):
|
||||
tasks, subprocess_mock = self._call_find_tests(
|
||||
self.ONE_DISABLED_ONE_ENABLED_TEST, ['--gtest_also_run_disabled_tests'])
|
||||
self.assertEqual(len(tasks), 2)
|
||||
self.assertEqual(sorted([task.test_name for task in tasks]),
|
||||
["FakeTest.DISABLED_Test2", "FakeTest.Test1"])
|
||||
self.assertIn("--gtest_also_run_disabled_tests",
|
||||
subprocess_mock.last_invocation)
|
||||
|
||||
def test_runs_failed_tests_by_default(self):
|
||||
tasks, _ = self._call_find_tests(self.ONE_FAILED_ONE_PASSED_TEST)
|
||||
self.assertEqual(len(tasks), 2)
|
||||
self.assertEqual(sorted([task.test_name for task in tasks]),
|
||||
["FakeTest.FailedTest", "FakeTest.Test"])
|
||||
self.assertEqual({task.last_execution_time for task in tasks}, {None, 1})
|
||||
|
||||
def test_runs_only_failed_tests_when_asked(self):
|
||||
tasks, _ = self._call_find_tests(self.ONE_FAILED_ONE_PASSED_TEST,
|
||||
['--failed'])
|
||||
self.assertEqual(len(tasks), 1)
|
||||
self.assertEqual(tasks[0].test_binary, "fake_unittests")
|
||||
self.assertEqual(tasks[0].test_name, "FakeTest.FailedTest")
|
||||
self.assertIsNone(tasks[0].last_execution_time)
|
||||
|
||||
def test_does_not_apply_gtest_filter_by_default(self):
|
||||
_, subprocess_mock = self._call_find_tests(self.ONE_TEST)
|
||||
self.assertFalse(
|
||||
any(
|
||||
arg.startswith('--gtest_filter=SomeFilter')
|
||||
for arg in subprocess_mock.last_invocation))
|
||||
|
||||
def test_applies_gtest_filter(self):
|
||||
_, subprocess_mock = self._call_find_tests(self.ONE_TEST,
|
||||
['--gtest_filter=SomeFilter'])
|
||||
self.assertIn('--gtest_filter=SomeFilter', subprocess_mock.last_invocation)
|
||||
|
||||
def test_applies_gtest_color_by_default(self):
|
||||
tasks, _ = self._call_find_tests(self.ONE_TEST)
|
||||
self.assertEqual(len(tasks), 1)
|
||||
self.assertIn('--gtest_color=yes', tasks[0].test_command)
|
||||
|
||||
def test_applies_gtest_color(self):
|
||||
tasks, _ = self._call_find_tests(self.ONE_TEST, ['--gtest_color=Lemur'])
|
||||
self.assertEqual(len(tasks), 1)
|
||||
self.assertIn('--gtest_color=Lemur', tasks[0].test_command)
|
||||
|
||||
def test_repeats_tasks_once_by_default(self):
|
||||
tasks, _ = self._call_find_tests(self.ONE_TEST)
|
||||
self.assertEqual(len(tasks), 1)
|
||||
|
||||
def test_repeats_tasks_multiple_times(self):
|
||||
tasks, _ = self._call_find_tests(self.ONE_TEST, ['--repeat=3'])
|
||||
self.assertEqual(len(tasks), 3)
|
||||
# Test all tasks have the same test_name, test_binary and test_command
|
||||
all_tasks_set = set(
|
||||
(task.test_name, task.test_binary, tuple(task.test_command))
|
||||
for task in tasks)
|
||||
self.assertEqual(len(all_tasks_set), 1)
|
||||
# Test tasks have consecutive execution_numbers starting from 1
|
||||
self.assertEqual(sorted(task.execution_number for task in tasks), [1, 2, 3])
|
||||
|
||||
def test_gtest_list_tests_fails(self):
|
||||
def exit_mock(*args):
|
||||
raise AssertionError('Foo')
|
||||
|
||||
options = self._process_options([])
|
||||
with guard_patch_module('sys.exit', exit_mock):
|
||||
self.assertRaises(AssertionError, gtest_parallel.find_tests,
|
||||
[sys.executable], [], options, None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Vendored
+1
@@ -9,6 +9,7 @@ extend-exclude = [
|
||||
[type.c.extend-identifiers]
|
||||
clen = "clen"
|
||||
[type.c.extend-words]
|
||||
cpy = "cpy"
|
||||
fo = "fo" # Used in sds.c testcase.
|
||||
|
||||
# Header files (sv = *.h)
|
||||
|
||||
+73
-13
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: almalinux:8
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -35,24 +35,21 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: rockylinux:8
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
dnf -y upgrade --refresh
|
||||
dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
|
||||
dnf -y module install redis:remi-6.0
|
||||
dnf -y group install "Development Tools"
|
||||
dnf -y install openssl-devel cmake libevent-devel valkey
|
||||
|
||||
- name: Build using cmake
|
||||
- name: Build using CMake
|
||||
env:
|
||||
EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_TLS:BOOL=ON
|
||||
run: mkdir build && cd build && cmake .. && make
|
||||
|
||||
run: |
|
||||
mkdir build && cd build && cmake .. && make
|
||||
cpack -G RPM
|
||||
- name: Build using Makefile
|
||||
run: USE_TLS=1 TEST_ASYNC=1 make
|
||||
|
||||
- name: Run tests
|
||||
working-directory: tests
|
||||
env:
|
||||
@@ -64,16 +61,79 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: FreeBSD
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Build in FreeBSD
|
||||
uses: vmactions/freebsd-vm@05856381fab64eeee9b038a0818f6cec649ca17a # v1.2.3
|
||||
uses: vmactions/freebsd-vm@c9f815bc7aa0d34c9fdd0619b034a32d6ca7b57e # v1.4.2
|
||||
with:
|
||||
prepare: pkg install -y gmake cmake
|
||||
run: |
|
||||
gmake
|
||||
mkdir build && cd build && cmake .. && gmake
|
||||
|
||||
solaris:
|
||||
runs-on: ubuntu-latest
|
||||
name: Solaris
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Build on Solaris
|
||||
uses: vmactions/solaris-vm@69d382b4a775b25ea5955e6c1730e9d05047ca0d # v1.3.1
|
||||
with:
|
||||
prepare: pkgutil -y -i gmake gcc5core openssl_utils
|
||||
run: USE_TLS=1 gmake
|
||||
|
||||
solaris-developer-studio:
|
||||
name: Oracle DeveloperStudio
|
||||
runs-on: ubuntu-24.04
|
||||
# Secrets containing certificates are not available in forks,
|
||||
# or for PRs created by dependabot or externally.
|
||||
if: |
|
||||
github.repository == 'valkey-io/libvalkey' &&
|
||||
github.actor != 'dependabot[bot]' &&
|
||||
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||
env:
|
||||
PKG_ORACLE_CERT: ${{ secrets.PKG_ORACLE_CERT }}
|
||||
PKG_ORACLE_KEY: ${{ secrets.PKG_ORACLE_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Transfer Oracle Studio certificates
|
||||
run: |
|
||||
set -e
|
||||
printf '%s\n' "$PKG_ORACLE_CERT" \
|
||||
> pkg.oracle.com.certificate.pem
|
||||
printf '%s\n' "$PKG_ORACLE_KEY" \
|
||||
> pkg.oracle.com.key.pem
|
||||
|
||||
- name: Build on Solaris
|
||||
uses: vmactions/solaris-vm@69d382b4a775b25ea5955e6c1730e9d05047ca0d # v1.3.1
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
set -e
|
||||
|
||||
cp "$GITHUB_WORKSPACE/pkg.oracle.com.key.pem" \
|
||||
/root/pkg.oracle.com.key.pem
|
||||
cp "$GITHUB_WORKSPACE/pkg.oracle.com.certificate.pem" \
|
||||
/root/pkg.oracle.com.certificate.pem
|
||||
|
||||
sudo pkg set-publisher \
|
||||
-k /root/pkg.oracle.com.key.pem \
|
||||
-c /root/pkg.oracle.com.certificate.pem \
|
||||
-G "*" \
|
||||
-g https://pkg.oracle.com/solarisstudio/release \
|
||||
solarisstudio
|
||||
|
||||
sudo pkg install --accept developerstudio-126/cc
|
||||
|
||||
run: |
|
||||
set -e
|
||||
PATH=/opt/developerstudio12.6/bin:"$PATH"
|
||||
export PATH
|
||||
gmake USE_THREADS=1 USE_TLS=1 -j"$(psrinfo -p)"
|
||||
|
||||
build-cross:
|
||||
name: Cross-compile ${{ matrix.config.target }}
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -94,9 +154,9 @@ jobs:
|
||||
- {target: mipsel, host: mipsel-linux-gnu, qemu: mipsel, gccver: 10 }
|
||||
- {target: mips64el, host: mips64el-linux-gnuabi64, qemu: mips64el, gccver: 10 }
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Prepare
|
||||
uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: gcc-${{ matrix.config.gccver }}-${{ matrix.config.host }}
|
||||
version: ${{ matrix.config.target }}-1.0
|
||||
|
||||
+33
-26
@@ -9,12 +9,19 @@ jobs:
|
||||
checkers:
|
||||
name: Run static checkers
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
LLVM_VERSION: '18'
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh ${{ env.LLVM_VERSION }}
|
||||
sudo apt install -y clang-format-${{ env.LLVM_VERSION }}
|
||||
- name: Run clang-format style check (.c and .h)
|
||||
uses: jidicula/clang-format-action@4726374d1aa3c6aecf132e5197e498979588ebc8 # v4.15.0
|
||||
with:
|
||||
clang-format-version: '18'
|
||||
run: |
|
||||
find . -regex '.*\.\(c\|h\)' -exec clang-format-${{ env.LLVM_VERSION }} -style=file --dry-run --Werror {} +
|
||||
|
||||
ubuntu-cmake:
|
||||
name: Build with CMake ${{ matrix.sanitizer && format('and {0}-sanitizer', matrix.sanitizer ) }} [${{ matrix.compiler }}, cmake-${{ matrix.cmake-version }}, ${{ matrix.cmake-build-type }}]
|
||||
@@ -43,18 +50,18 @@ jobs:
|
||||
cmake-build-type: [RelWithDebInfo]
|
||||
sanitizer: [thread, undefined, leak, address]
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Prepare
|
||||
uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: libevent-dev libuv1-dev libev-dev libglib2.0-dev valkey-server
|
||||
version: 1.0
|
||||
- name: Setup compiler
|
||||
uses: aminya/setup-cpp@a276e6e3d1db9160db5edc458e99a30d3b109949 # v1.7.1
|
||||
uses: aminya/setup-cpp@1f17f92d6a52bfcb1a25348e2c526c2e5cbb1134 # v1.8.0
|
||||
with:
|
||||
compiler: ${{ matrix.compiler }}
|
||||
- name: Setup CMake
|
||||
uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
|
||||
uses: jwlawson/actions-setup-cmake@3a6cbe35ba64df7ca70c51365c4aff65db9a9037 # v2.1.1
|
||||
with:
|
||||
cmake-version: ${{ matrix.cmake-version }}
|
||||
- name: Generate makefiles
|
||||
@@ -84,14 +91,14 @@ jobs:
|
||||
name: Build with make
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Prepare
|
||||
uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: libevent-dev valgrind valkey-server
|
||||
version: 1.0
|
||||
- name: Build
|
||||
run: USE_TLS=1 TEST_ASYNC=1 make
|
||||
run: USE_TLS=1 TEST_ASYNC=1 make -j$(nproc)
|
||||
- name: Run tests
|
||||
working-directory: tests
|
||||
env:
|
||||
@@ -110,9 +117,9 @@ jobs:
|
||||
name: Build for 32-bit
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Prepare
|
||||
uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: gcc-multilib valkey-server
|
||||
version: 1.0
|
||||
@@ -128,9 +135,9 @@ jobs:
|
||||
name: Installation tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Prepare
|
||||
uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: libevent-dev libuv1-dev libev-dev libglib2.0-dev
|
||||
version: 1.0
|
||||
@@ -174,9 +181,9 @@ jobs:
|
||||
name: RDMA support enabled
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Prepare
|
||||
uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: librdmacm-dev libibverbs-dev
|
||||
version: 1.0
|
||||
@@ -198,9 +205,9 @@ jobs:
|
||||
name: CMake 3.7.0 (min. required)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup CMake
|
||||
uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
|
||||
uses: jwlawson/actions-setup-cmake@3a6cbe35ba64df7ca70c51365c4aff65db9a9037 # v2.1.1
|
||||
with:
|
||||
cmake-version: '3.7.0'
|
||||
- name: Generate makefiles
|
||||
@@ -213,7 +220,7 @@ jobs:
|
||||
name: macOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew update
|
||||
@@ -221,7 +228,7 @@ jobs:
|
||||
- name: Build and install using CMake
|
||||
run: |
|
||||
mkdir build && cd build
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_TLS=ON
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_TLS=ON -DENABLE_EXAMPLES=ON
|
||||
sudo ninja -v install
|
||||
- name: Build using Makefile
|
||||
run: USE_TLS=1 make
|
||||
@@ -235,7 +242,7 @@ jobs:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
|
||||
- name: Remove installed OpenSSL 1.1.1 (has reached End of Life)
|
||||
shell: bash
|
||||
@@ -266,9 +273,9 @@ jobs:
|
||||
run: |
|
||||
git config --global core.autocrlf input
|
||||
choco install -y memurai-developer
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install Cygwin
|
||||
uses: cygwin/cygwin-install-action@f2009323764960f80959895c7bc3bb30210afe4d # v6
|
||||
uses: cygwin/cygwin-install-action@711d29f3da23c9f4a1798e369a6f01198c13b11a # v6
|
||||
with:
|
||||
packages: make gcc-core cmake libssl-devel
|
||||
- name: Build with CMake using Cygwin
|
||||
@@ -284,9 +291,9 @@ jobs:
|
||||
name: Windows (MinGW64)
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Set up MinGW
|
||||
uses: msys2/setup-msys2@40677d36a502eb2cf0fb808cc9dec31bf6152638 # v2.28.0
|
||||
uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0
|
||||
with:
|
||||
msystem: mingw64
|
||||
install: |
|
||||
|
||||
+2
-2
@@ -12,9 +12,9 @@ jobs:
|
||||
if: github.repository == 'valkey-io/libvalkey'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: libevent-dev
|
||||
version: 1.0
|
||||
|
||||
+4
-4
@@ -18,7 +18,7 @@ jobs:
|
||||
- valkey-version: '7.2.8'
|
||||
steps:
|
||||
- name: Prepare
|
||||
uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: libevent-dev
|
||||
version: 1.0
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
run: |
|
||||
git clone --depth 1 --branch ${{ matrix.valkey-version }} https://github.com/valkey-io/valkey.git
|
||||
cd valkey && BUILD_TLS=yes make install
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Create build folder
|
||||
run: cmake -E make_directory build
|
||||
- name: Generate makefiles
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
- redis-version: '6.2.14'
|
||||
steps:
|
||||
- name: Prepare
|
||||
uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0
|
||||
with:
|
||||
packages: libevent-dev
|
||||
version: 1.0
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
run: |
|
||||
git clone --depth 1 --branch ${{ matrix.redis-version }} https://github.com/redis/redis.git
|
||||
cd redis && BUILD_TLS=yes make install
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Create build folder
|
||||
run: cmake -E make_directory build
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Drafts your next Release notes as Pull Requests are merged into "master"
|
||||
- uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
|
||||
- uses: release-drafter/release-drafter@6db134d15f3909ccc9eefd369f02bd1e9cffdf97 # v6.2.0
|
||||
with:
|
||||
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
|
||||
config-name: release-drafter-config.yml
|
||||
|
||||
+4
-4
@@ -10,18 +10,18 @@ jobs:
|
||||
spellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Run spellcheck
|
||||
uses: rojopolis/spellcheck-github-actions@35a02bae020e6999c5c37fabaf447f2eb8822ca7 # 0.51.0
|
||||
uses: rojopolis/spellcheck-github-actions@0bf4b2f91efa259b52c202b09b0c3845c524ff36 # 0.58.0
|
||||
with:
|
||||
config_path: .github/spellcheck-settings.yml
|
||||
task_name: Markdown
|
||||
typos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install typos
|
||||
uses: taiki-e/install-action@ad95d4e02e061d4390c4b66ef5ed56c7fee3d2ce # v2.58.17
|
||||
uses: taiki-e/install-action@288875dd3d64326724fa6d9593062d9f8ba0b131 # v2.67.30
|
||||
with:
|
||||
tool: typos
|
||||
- name: Run typos
|
||||
|
||||
Vendored
+88
-52
@@ -11,17 +11,19 @@ getDefinedVersion(VERSION_MAJOR)
|
||||
getDefinedVersion(VERSION_MINOR)
|
||||
getDefinedVersion(VERSION_PATCH)
|
||||
set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
|
||||
MESSAGE("Detected version: ${VERSION}")
|
||||
message("Detected libvalkey version: ${VERSION}")
|
||||
|
||||
PROJECT(valkey LANGUAGES "C" VERSION "${VERSION}")
|
||||
project(libvalkey LANGUAGES "C" VERSION "${VERSION}")
|
||||
INCLUDE(GNUInstallDirs)
|
||||
|
||||
option(ENABLE_THREADS "Enable thread-safe initialization" ON)
|
||||
OPTION(BUILD_SHARED_LIBS "Build shared libraries" ON)
|
||||
OPTION(ENABLE_TLS "Build valkey_tls for TLS support" OFF)
|
||||
OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF)
|
||||
OPTION(ENABLE_EXAMPLES "Enable building valkey examples" OFF)
|
||||
option(ENABLE_IPV6_TESTS "Enable IPv6 tests requiring special prerequisites" OFF)
|
||||
OPTION(ENABLE_RDMA "Build valkey_rdma for RDMA support" OFF)
|
||||
OPTION(ENABLE_DLOPEN_RDMA "Build valkey_rdma with dynamic loading" OFF)
|
||||
|
||||
# Libvalkey requires C99 (-std=c99)
|
||||
SET(CMAKE_C_STANDARD 99)
|
||||
@@ -70,18 +72,36 @@ set_target_properties(valkey PROPERTIES
|
||||
SOVERSION "${VERSION_MAJOR}"
|
||||
VERSION "${VERSION}")
|
||||
|
||||
IF(MSVC)
|
||||
SET_TARGET_PROPERTIES(valkey
|
||||
PROPERTIES COMPILE_FLAGS /Z7)
|
||||
ENDIF()
|
||||
if(MSVC)
|
||||
# Produce object files that contain debug info.
|
||||
target_compile_options(valkey PRIVATE /Z7)
|
||||
endif()
|
||||
|
||||
set(valkey_link_libraries)
|
||||
set(valkey_compile_definitions)
|
||||
|
||||
IF(WIN32)
|
||||
TARGET_LINK_LIBRARIES(valkey PUBLIC ws2_32 crypt32)
|
||||
list(APPEND valkey_link_libraries ws2_32 crypt32)
|
||||
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
TARGET_LINK_LIBRARIES(valkey PUBLIC m)
|
||||
list(APPEND valkey_link_libraries m)
|
||||
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "SunOS")
|
||||
TARGET_LINK_LIBRARIES(valkey PUBLIC socket)
|
||||
list(APPEND valkey_link_libraries socket)
|
||||
ENDIF()
|
||||
|
||||
if(ENABLE_THREADS)
|
||||
find_package(Threads REQUIRED)
|
||||
list(APPEND valkey_link_libraries Threads::Threads)
|
||||
list(APPEND valkey_compile_definitions VALKEY_USE_THREADS)
|
||||
endif()
|
||||
|
||||
if(valkey_link_libraries)
|
||||
target_link_libraries(valkey PUBLIC ${valkey_link_libraries})
|
||||
endif()
|
||||
|
||||
if(valkey_compile_definitions)
|
||||
target_compile_definitions(valkey PRIVATE ${valkey_compile_definitions})
|
||||
endif()
|
||||
|
||||
TARGET_INCLUDE_DIRECTORIES(valkey
|
||||
PUBLIC
|
||||
$<INSTALL_INTERFACE:include>
|
||||
@@ -94,8 +114,9 @@ TARGET_INCLUDE_DIRECTORIES(valkey
|
||||
CONFIGURE_FILE(valkey.pc.in valkey.pc @ONLY)
|
||||
|
||||
set(CPACK_PACKAGE_VENDOR "Valkey")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Minimalistic C client library for Valkey")
|
||||
set(CPACK_PACKAGE_DESCRIPTION "\
|
||||
Libvalkey is a minimalistic C client library for the Valkey, KeyDB, and Redis databases
|
||||
Libvalkey is a minimalistic C client library for the Valkey, KeyDB, and Redis databases.
|
||||
|
||||
It is minimalistic because it just adds minimal support for the protocol, \
|
||||
but at the same time it uses a high level printf-alike API in order to make \
|
||||
@@ -107,7 +128,7 @@ reply parser that is decoupled from the I/O layer. It is a stream parser designe
|
||||
for easy reusability, which can for instance be used in higher level language bindings \
|
||||
for efficient reply parsing.
|
||||
|
||||
valkey only supports the binary-safe RESP protocol, so you can use it with any Redis \
|
||||
Libvalkey only supports the binary-safe RESP protocol, so you can use it with any Redis \
|
||||
compatible server >= 1.2.0.
|
||||
|
||||
The library comes with multiple APIs. There is the synchronous API, the asynchronous API \
|
||||
@@ -116,7 +137,9 @@ set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/valkey-io/libvalkey")
|
||||
set(CPACK_PACKAGE_CONTACT "michael dot grunder at gmail dot com")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
set(CPACK_RPM_PACKAGE_AUTOREQPROV ON)
|
||||
|
||||
set(CPACK_RPM_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
|
||||
set(CPACK_RPM_PACKAGE_GROUP "Productivity/Databases/Clients")
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "BSD-3-Clause")
|
||||
include(CPack)
|
||||
|
||||
INSTALL(TARGETS valkey
|
||||
@@ -125,17 +148,23 @@ INSTALL(TARGETS valkey
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
if (MSVC AND BUILD_SHARED_LIBS)
|
||||
INSTALL(FILES $<TARGET_PDB_FILE:valkey>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
CONFIGURATIONS Debug RelWithDebInfo)
|
||||
if(MSVC AND BUILD_SHARED_LIBS)
|
||||
# Install linker generated program database file.
|
||||
install(FILES $<TARGET_PDB_FILE:valkey>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
CONFIGURATIONS Debug RelWithDebInfo)
|
||||
endif()
|
||||
|
||||
# Install public headers
|
||||
install(DIRECTORY include/valkey
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
PATTERN "tls.h" EXCLUDE
|
||||
PATTERN "rdma.h" EXCLUDE)
|
||||
PATTERN "rdma.h" EXCLUDE
|
||||
PATTERN "adapters/macosx.h" EXCLUDE)
|
||||
if(APPLE)
|
||||
install(FILES include/valkey/adapters/macosx.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/valkey/adapters)
|
||||
endif()
|
||||
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/valkey.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
@@ -187,23 +216,16 @@ IF(ENABLE_TLS)
|
||||
$<BUILD_INTERFACE:${SDS_INCLUDE_DIR}>
|
||||
)
|
||||
|
||||
IF (APPLE AND BUILD_SHARED_LIBS)
|
||||
SET_PROPERTY(TARGET valkey_tls PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup")
|
||||
ENDIF()
|
||||
|
||||
set_target_properties(valkey_tls PROPERTIES
|
||||
C_VISIBILITY_PRESET hidden
|
||||
WINDOWS_EXPORT_ALL_SYMBOLS TRUE
|
||||
SOVERSION "${VERSION_MAJOR}"
|
||||
VERSION "${VERSION}")
|
||||
IF(MSVC)
|
||||
SET_TARGET_PROPERTIES(valkey_tls
|
||||
PROPERTIES COMPILE_FLAGS /Z7)
|
||||
ENDIF()
|
||||
TARGET_LINK_LIBRARIES(valkey_tls PRIVATE OpenSSL::SSL)
|
||||
if(WIN32 OR CYGWIN)
|
||||
target_link_libraries(valkey_tls PRIVATE valkey)
|
||||
if(MSVC)
|
||||
# Produce object files that contain debug info.
|
||||
target_compile_options(valkey_tls PRIVATE /Z7)
|
||||
endif()
|
||||
target_link_libraries(valkey_tls PRIVATE valkey::valkey OpenSSL::SSL)
|
||||
CONFIGURE_FILE(valkey_tls.pc.in valkey_tls.pc @ONLY)
|
||||
|
||||
INSTALL(TARGETS valkey_tls
|
||||
@@ -216,10 +238,11 @@ IF(ENABLE_TLS)
|
||||
install(FILES include/valkey/tls.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/valkey)
|
||||
|
||||
if (MSVC AND BUILD_SHARED_LIBS)
|
||||
INSTALL(FILES $<TARGET_PDB_FILE:valkey_tls>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
CONFIGURATIONS Debug RelWithDebInfo)
|
||||
if(MSVC AND BUILD_SHARED_LIBS)
|
||||
# Install linker generated program database file.
|
||||
install(FILES $<TARGET_PDB_FILE:valkey_tls>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
CONFIGURATIONS Debug RelWithDebInfo)
|
||||
endif()
|
||||
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/valkey_tls.pc
|
||||
@@ -248,13 +271,20 @@ IF(ENABLE_TLS)
|
||||
ENDIF()
|
||||
|
||||
if(ENABLE_RDMA)
|
||||
find_library(RDMACM_LIBRARIES rdmacm REQUIRED)
|
||||
find_library(IBVERBS_LIBRARIES ibverbs REQUIRED)
|
||||
set(valkey_rdma_sources src/rdma.c)
|
||||
add_library(valkey_rdma ${valkey_rdma_sources})
|
||||
add_library(valkey::valkey_rdma ALIAS valkey_rdma)
|
||||
|
||||
if (ENABLE_DLOPEN_RDMA)
|
||||
message(STATUS "libvalkey: Building RDMA with dynamic loading (dlopen)")
|
||||
target_compile_definitions(valkey_rdma PRIVATE DLOPEN_RDMA)
|
||||
else()
|
||||
message(STATUS "libvalkey: Building RDMA with static/hard linking")
|
||||
find_library(RDMACM_LIBRARIES rdmacm REQUIRED)
|
||||
find_library(IBVERBS_LIBRARIES ibverbs REQUIRED)
|
||||
target_link_libraries(valkey_rdma LINK_PRIVATE ${RDMACM_LIBRARIES} ${IBVERBS_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_link_libraries(valkey_rdma LINK_PRIVATE ${RDMACM_LIBRARIES} ${IBVERBS_LIBRARIES})
|
||||
target_include_directories(valkey_rdma
|
||||
PRIVATE
|
||||
$<INSTALL_INTERFACE:include>
|
||||
@@ -303,26 +333,32 @@ endif()
|
||||
|
||||
# Add tests
|
||||
if(NOT DISABLE_TESTS)
|
||||
if(BUILD_SHARED_LIBS)
|
||||
# Test using a static library since symbols are not hidden then.
|
||||
# Use same source, include dirs and dependencies as the shared library.
|
||||
add_library(valkey_unittest STATIC ${valkey_sources})
|
||||
get_target_property(include_directories valkey::valkey INCLUDE_DIRECTORIES)
|
||||
target_include_directories(valkey_unittest PUBLIC ${include_directories})
|
||||
get_target_property(link_libraries valkey::valkey LINK_LIBRARIES)
|
||||
if(link_libraries)
|
||||
target_link_libraries(valkey_unittest PUBLIC ${link_libraries})
|
||||
endif()
|
||||
# Create libvalkey_unittest.a in the tests directory.
|
||||
set_target_properties(valkey_unittest PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/tests")
|
||||
else()
|
||||
# Target is an alias for the static library.
|
||||
add_library(valkey_unittest ALIAS valkey)
|
||||
# Unit tests uses a static library to ensure all symbols are visible.
|
||||
# This single library also bundles TLS and RDMA when enabled.
|
||||
add_library(valkey_unittest STATIC ${valkey_sources} ${valkey_tls_sources} ${valkey_rdma_sources})
|
||||
|
||||
# Mirror the include directories.
|
||||
get_target_property(include_directories valkey::valkey INCLUDE_DIRECTORIES)
|
||||
target_include_directories(valkey_unittest PUBLIC ${include_directories})
|
||||
|
||||
if(valkey_compile_definitions)
|
||||
target_compile_definitions(valkey_unittest PRIVATE ${valkey_compile_definitions})
|
||||
endif()
|
||||
if(valkey_link_libraries)
|
||||
target_link_libraries(valkey_unittest PUBLIC ${valkey_link_libraries})
|
||||
endif()
|
||||
if(ENABLE_TLS)
|
||||
target_link_libraries(valkey_unittest PRIVATE OpenSSL::SSL)
|
||||
endif()
|
||||
if(ENABLE_RDMA)
|
||||
target_link_libraries(valkey_unittest PRIVATE ${RDMACM_LIBRARIES} ${IBVERBS_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# Create libvalkey_unittest.a in the tests directory.
|
||||
set_target_properties(valkey_unittest PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/tests")
|
||||
|
||||
# Make sure ctest prints the output when a test fails.
|
||||
# Must be set before including CTest.
|
||||
set(CMAKE_CTEST_ARGUMENTS "--output-on-failure")
|
||||
include(CTest)
|
||||
add_subdirectory(tests)
|
||||
|
||||
Vendored
+1
-1
@@ -69,7 +69,7 @@ Adhere to the existing coding style and make sure to mimic best possible.
|
||||
### Code formatting
|
||||
|
||||
When making a change, please use `git clang-format` or [format-files.sh](./scripts/format-files.sh) to format your changes properly.
|
||||
This repository is currently using `clang-format` 18.1.3 to format the code, which can be installed using `pip install clang-format==18.1.3` or other preferred method.
|
||||
This repository is currently using `clang-format` 18.1.8 to format the code, which can be installed using `pip install clang-format==18.1.8` or other preferred method.
|
||||
|
||||
## Running cluster tests
|
||||
|
||||
|
||||
Vendored
+68
-20
@@ -86,7 +86,7 @@ DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(LIBVALKEY_MAJOR)
|
||||
DYLIB_ROOT_NAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||
DYLIBNAME=$(LIB_DIR)/$(DYLIB_ROOT_NAME)
|
||||
|
||||
DYLIB_MAKE_CMD=$(CC) $(OPTIMIZATION) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(DYLIB_MAJOR_NAME)
|
||||
DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(DYLIB_MAJOR_NAME)
|
||||
STLIBNAME=$(LIB_DIR)/$(LIBNAME).$(STLIBSUFFIX)
|
||||
STLIB_MAKE_CMD=$(AR) rcs
|
||||
|
||||
@@ -99,7 +99,7 @@ TLS_DYLIB_MAJOR_NAME=$(TLS_LIBNAME).$(DYLIBSUFFIX).$(LIBVALKEY_MAJOR)
|
||||
TLS_DYLIB_ROOT_NAME=$(TLS_LIBNAME).$(DYLIBSUFFIX)
|
||||
TLS_DYLIBNAME=$(LIB_DIR)/$(TLS_LIBNAME).$(DYLIBSUFFIX)
|
||||
TLS_STLIBNAME=$(LIB_DIR)/$(TLS_LIBNAME).$(STLIBSUFFIX)
|
||||
TLS_DYLIB_MAKE_CMD=$(CC) $(OPTIMIZATION) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(TLS_DYLIB_MAJOR_NAME)
|
||||
TLS_DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(TLS_DYLIB_MAJOR_NAME)
|
||||
|
||||
USE_TLS?=0
|
||||
|
||||
@@ -130,14 +130,20 @@ RDMA_DYLIB_MAJOR_NAME=$(RDMA_LIBNAME).$(DYLIBSUFFIX).$(LIBVALKEY_MAJOR)
|
||||
RDMA_DYLIB_ROOT_NAME=$(RDMA_LIBNAME).$(DYLIBSUFFIX)
|
||||
RDMA_DYLIBNAME=$(LIB_DIR)/$(RDMA_LIBNAME).$(DYLIBSUFFIX)
|
||||
RDMA_STLIBNAME=$(LIB_DIR)/$(RDMA_LIBNAME).$(STLIBSUFFIX)
|
||||
RDMA_DYLIB_MAKE_CMD=$(CC) $(OPTIMIZATION) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(RDMA_DYLIB_MAJOR_NAME)
|
||||
RDMA_DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(RDMA_DYLIB_MAJOR_NAME)
|
||||
|
||||
USE_RDMA?=0
|
||||
USE_DLOPEN_RDMA?=0
|
||||
|
||||
ifeq ($(USE_RDMA),1)
|
||||
RDMA_SOURCES=$(SRC_DIR)/rdma.c
|
||||
RDMA_OBJS=$(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(RDMA_SOURCES))
|
||||
RDMA_LDFLAGS=-lrdmacm -libverbs
|
||||
ifeq ($(USE_DLOPEN_RDMA),1)
|
||||
CFLAGS += -DDLOPEN_RDMA
|
||||
RDMA_LDFLAGS=
|
||||
else
|
||||
RDMA_LDFLAGS=-lrdmacm -libverbs
|
||||
endif
|
||||
# This is required for test.c only
|
||||
CFLAGS+=-DVALKEY_TEST_RDMA
|
||||
RDMA_STLIB=$(RDMA_STLIBNAME)
|
||||
@@ -182,28 +188,63 @@ ifeq ($(USE_TLS),1)
|
||||
TLS_LDFLAGS+=-lssl -lcrypto
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),FreeBSD)
|
||||
LDFLAGS += -lm
|
||||
else ifeq ($(UNAME_S),SunOS)
|
||||
ifeq ($(shell $(CC) -V 2>&1 | grep -iq 'sun\|studio' && echo true),true)
|
||||
USE_THREADS?=1
|
||||
ifeq ($(USE_THREADS),1)
|
||||
CFLAGS+=-DVALKEY_USE_THREADS
|
||||
endif
|
||||
|
||||
PTHREAD_FLAGS :=
|
||||
|
||||
ifeq ($(uname_S),Linux)
|
||||
ifeq ($(USE_THREADS),1)
|
||||
PTHREAD_FLAGS += -pthread
|
||||
endif
|
||||
else ifeq ($(uname_S),FreeBSD)
|
||||
REAL_LDFLAGS += -lm
|
||||
ifeq ($(USE_THREADS),1)
|
||||
PTHREAD_FLAGS += -pthread
|
||||
endif
|
||||
else ifeq ($(uname_S),SunOS)
|
||||
# Solaris' default grep doesn't have -E so we need two checks
|
||||
CC_VERSION := $(shell $(CC) -V 2>&1 || echo unknown)
|
||||
ifneq (,$(findstring Sun C,$(CC_VERSION)))
|
||||
HAVE_SUN_CC := 1
|
||||
else ifneq (,$(findstring Studio,$(CC_VERSION)))
|
||||
HAVE_SUN_CC := 1
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SUN_CC),1)
|
||||
SUN_SHARED_FLAG = -G
|
||||
REAL_CFLAGS += -mt
|
||||
ifeq ($(USE_THREADS),1)
|
||||
PTHREAD_FLAGS += -mt
|
||||
endif
|
||||
else
|
||||
SUN_SHARED_FLAG = -shared
|
||||
ifeq ($(USE_THREADS),1)
|
||||
PTHREAD_FLAGS += -pthread
|
||||
endif
|
||||
endif
|
||||
REAL_LDFLAGS += -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD = $(CC) $(OPTIMIZATION) $(SUN_SHARED_FLAG) -o $(DYLIBNAME) -h $(DYLIB_PATCH_NAME) $(LDFLAGS)
|
||||
TLS_DYLIB_MAKE_CMD = $(CC) $(SUN_SHARED_FLAG) -o $(TLS_DYLIBNAME) -h $(TLS_DYLIB_PATCH_NAME) $(LDFLAGS) $(TLS_LDFLAGS)
|
||||
DYLIB_MAKE_CMD = $(CC) $(SUN_SHARED_FLAG) -h $(DYLIB_PATCH_NAME)
|
||||
TLS_DYLIB_MAKE_CMD = $(CC) $(SUN_SHARED_FLAG) -h $(TLS_DYLIB_PATCH_NAME)
|
||||
else ifeq ($(uname_S),Darwin)
|
||||
ifeq ($(USE_THREADS),1)
|
||||
PTHREAD_FLAGS += -pthread
|
||||
endif
|
||||
DYLIBSUFFIX=dylib
|
||||
DYLIB_PATCH_NAME=$(LIBNAME).$(LIBVALKEY_MAJOR).$(LIBVALKEY_MINOR).$(LIBVALKEY_PATCH).$(DYLIBSUFFIX)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(LIBVALKEY_MAJOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_PATCH_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
DYLIB_MAKE_CMD=$(CC) -dynamiclib \
|
||||
-Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_PATCH_NAME)
|
||||
TLS_DYLIB_PATCH_NAME=$(TLS_LIBNAME).$(LIBVALKEY_MAJOR).$(LIBVALKEY_MINOR).$(LIBVALKEY_PATCH).$(DYLIBSUFFIX)
|
||||
TLS_DYLIB_MAJOR_NAME=$(TLS_LIBNAME).$(LIBVALKEY_MAJOR).$(DYLIBSUFFIX)
|
||||
TLS_DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(TLS_DYLIB_PATCH_NAME) -o $(TLS_DYLIBNAME) $(LDFLAGS) $(TLS_LDFLAGS)
|
||||
DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup
|
||||
TLS_DYLIB_MAKE_CMD=$(CC) -dynamiclib \
|
||||
-Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(TLS_DYLIB_PATCH_NAME)
|
||||
endif
|
||||
|
||||
REAL_LDFLAGS += $(PTHREAD_FLAGS)
|
||||
|
||||
all: dynamic static pkgconfig tests
|
||||
|
||||
$(DYLIBNAME): $(OBJS) | $(LIB_DIR)
|
||||
@@ -212,14 +253,16 @@ $(DYLIBNAME): $(OBJS) | $(LIB_DIR)
|
||||
$(STLIBNAME): $(OBJS) | $(LIB_DIR)
|
||||
$(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJS)
|
||||
|
||||
$(TLS_DYLIBNAME): $(TLS_OBJS)
|
||||
$(TLS_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(TLS_DYLIBNAME) $(TLS_OBJS) $(REAL_LDFLAGS) $(LDFLAGS) $(TLS_LDFLAGS)
|
||||
$(TLS_DYLIBNAME): $(TLS_OBJS) $(DYLIBNAME) | $(LIB_DIR)
|
||||
$(TLS_DYLIB_MAKE_CMD) -o $(TLS_DYLIBNAME) \
|
||||
$(TLS_OBJS) $(REAL_LDFLAGS) $(DYLIBNAME) $(TLS_LDFLAGS)
|
||||
|
||||
$(TLS_STLIBNAME): $(TLS_OBJS)
|
||||
$(STLIB_MAKE_CMD) $(TLS_STLIBNAME) $(TLS_OBJS)
|
||||
|
||||
$(RDMA_DYLIBNAME): $(RDMA_OBJS)
|
||||
$(RDMA_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(RDMA_DYLIBNAME) $(RDMA_OBJS) $(REAL_LDFLAGS) $(LDFLAGS) $(RDMA_LDFLAGS)
|
||||
$(RDMA_DYLIBNAME): $(RDMA_OBJS) $(DYLIBNAME) | $(LIB_DIR)
|
||||
$(RDMA_DYLIB_MAKE_CMD) -o $(RDMA_DYLIBNAME) \
|
||||
$(RDMA_OBJS) $(REAL_LDFLAGS) $(DYLIBNAME) $(RDMA_LDFLAGS)
|
||||
|
||||
$(RDMA_STLIBNAME): $(RDMA_OBJS)
|
||||
$(STLIB_MAKE_CMD) $(RDMA_STLIBNAME) $(RDMA_OBJS)
|
||||
@@ -230,8 +273,8 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
|
||||
$(OBJ_DIR)/%.o: $(TEST_DIR)/%.c | $(OBJ_DIR)
|
||||
$(CC) -std=c99 -pedantic $(REAL_CFLAGS) -I$(INCLUDE_DIR) -I$(SDS_INCLUDE_DIR) -I$(DICT_INCLUDE_DIR) -I$(SRC_DIR) -MMD -MP -c $< -o $@
|
||||
|
||||
$(TEST_DIR)/%: $(OBJ_DIR)/%.o $(STLIBNAME)
|
||||
$(CC) -o $@ $< $(RDMA_STLIB) $(STLIBNAME) $(TLS_STLIB) $(LDFLAGS) $(TEST_LDFLAGS)
|
||||
$(TEST_DIR)/%: $(OBJ_DIR)/%.o $(STLIBNAME) $(TLS_STLIB)
|
||||
$(CC) -o $@ $< $(RDMA_STLIB) $(STLIBNAME) $(TLS_STLIB) $(REAL_LDFLAGS) $(TEST_LDFLAGS)
|
||||
|
||||
$(OBJ_DIR):
|
||||
mkdir -p $(OBJ_DIR)
|
||||
@@ -249,7 +292,12 @@ pkgconfig: $(PKGCONFNAME) $(TLS_PKGCONF) $(RDMA_PKGCONF)
|
||||
|
||||
TEST_LDFLAGS = $(TLS_LDFLAGS) $(RDMA_LDFLAGS)
|
||||
ifeq ($(USE_TLS),1)
|
||||
TEST_LDFLAGS += -pthread
|
||||
# Tests need pthreads if TLS is enabled, but only add it once
|
||||
ifeq ($(HAVE_SUN_CC),1)
|
||||
TEST_LDFLAGS += -mt
|
||||
else ifeq (,$(findstring -pthread,$(REAL_LDFLAGS) $(TEST_LDFLAGS)))
|
||||
TEST_LDFLAGS += -pthread
|
||||
endif
|
||||
endif
|
||||
ifeq ($(TEST_ASYNC),1)
|
||||
TEST_LDFLAGS += -levent
|
||||
|
||||
+20
-9
@@ -1,6 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.7.0)
|
||||
project(examples LANGUAGES C)
|
||||
|
||||
option(ENABLE_THREADS "Enable thread-safe initialization" ON)
|
||||
|
||||
if(ENABLE_THREADS)
|
||||
find_package(Threads REQUIRED)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
|
||||
# This CMakeLists.txt is used standalone to build the examples.
|
||||
# Find required valkey package to get include paths.
|
||||
@@ -47,23 +53,26 @@ if(LIBEV)
|
||||
endif()
|
||||
|
||||
# Examples using libevent
|
||||
find_path(LIBEVENT event.h)
|
||||
if(LIBEVENT)
|
||||
find_path(LIBEVENT event2/event.h)
|
||||
find_library(LIBEVENT_LIBRARY event)
|
||||
if(LIBEVENT AND LIBEVENT_LIBRARY)
|
||||
include_directories(${LIBEVENT})
|
||||
|
||||
add_executable(example-async-libevent async-libevent.c)
|
||||
target_link_libraries(example-async-libevent valkey::valkey event)
|
||||
target_link_libraries(example-async-libevent valkey::valkey ${LIBEVENT_LIBRARY})
|
||||
|
||||
add_executable(example-cluster-async cluster-async.c)
|
||||
target_link_libraries(example-cluster-async valkey::valkey event)
|
||||
target_link_libraries(example-cluster-async valkey::valkey ${LIBEVENT_LIBRARY})
|
||||
|
||||
add_executable(example-cluster-clientside-caching-async cluster-clientside-caching-async.c)
|
||||
target_link_libraries(example-cluster-clientside-caching-async valkey::valkey event)
|
||||
target_link_libraries(example-cluster-clientside-caching-async valkey::valkey ${LIBEVENT_LIBRARY})
|
||||
|
||||
if(ENABLE_TLS)
|
||||
add_executable(example-async-libevent-tls async-libevent-tls.c)
|
||||
target_link_libraries(example-async-libevent-tls valkey::valkey valkey::valkey_tls event)
|
||||
target_link_libraries(example-async-libevent-tls valkey::valkey valkey::valkey_tls ${LIBEVENT_LIBRARY})
|
||||
|
||||
add_executable(example-cluster-async-tls cluster-async-tls.c)
|
||||
target_link_libraries(example-cluster-async-tls valkey::valkey valkey::valkey_tls event)
|
||||
target_link_libraries(example-cluster-async-tls valkey::valkey valkey::valkey_tls ${LIBEVENT_LIBRARY})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -76,9 +85,11 @@ endif()
|
||||
|
||||
# Examples using libuv
|
||||
find_path(LIBUV uv.h)
|
||||
if(LIBUV)
|
||||
find_library(LIBUV_LIBRARY uv)
|
||||
if(LIBUV AND LIBUV_LIBRARY)
|
||||
include_directories(${LIBUV})
|
||||
add_executable(example-async-libuv async-libuv.c)
|
||||
target_link_libraries(example-async-libuv valkey::valkey uv)
|
||||
target_link_libraries(example-async-libuv valkey::valkey ${LIBUV_LIBRARY})
|
||||
endif()
|
||||
|
||||
# Examples using libsystemd
|
||||
|
||||
@@ -26,6 +26,7 @@ void modifyKey(const char *key, const char *value);
|
||||
* and sets the push callback in the libvalkey context. */
|
||||
void connectCallback(valkeyAsyncContext *ac, int status) {
|
||||
assert(status == VALKEY_OK);
|
||||
(void)status; /* Suppress unused warning when NDEBUG is defined. */
|
||||
valkeyAsyncSetPushCallback(ac, pushCallback);
|
||||
valkeyAsyncCommand(ac, NULL, NULL, "HELLO 3");
|
||||
valkeyAsyncCommand(ac, NULL, NULL, "CLIENT TRACKING ON");
|
||||
@@ -48,6 +49,7 @@ void eventCallback(const valkeyClusterContext *cc, int event, void *privdata) {
|
||||
int status =
|
||||
valkeyClusterAsyncCommand(acc, setCallback, NULL, "SET %s 1", KEY);
|
||||
assert(status == VALKEY_OK);
|
||||
(void)status; /* Suppress unused warning when NDEBUG is defined. */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +64,7 @@ void setCallback(valkeyClusterAsyncContext *acc, void *r, void *privdata) {
|
||||
int status =
|
||||
valkeyClusterAsyncCommand(acc, getCallback1, NULL, "GET %s", KEY);
|
||||
assert(status == VALKEY_OK);
|
||||
(void)status; /* Suppress unused warning when NDEBUG is defined. */
|
||||
}
|
||||
|
||||
/* Message callback for the first 'GET' command. Modifies the key to
|
||||
@@ -82,6 +85,7 @@ void getCallback1(valkeyClusterAsyncContext *acc, void *r, void *privdata) {
|
||||
int status =
|
||||
valkeyClusterAsyncCommand(acc, getCallback2, NULL, "GET %s", KEY);
|
||||
assert(status == VALKEY_OK);
|
||||
(void)status; /* Suppress unused warning when NDEBUG is defined. */
|
||||
}
|
||||
|
||||
/* Push message callback handling invalidation messages. */
|
||||
@@ -121,6 +125,7 @@ void getCallback2(valkeyClusterAsyncContext *acc, void *r, void *privdata) {
|
||||
/* A disconnect callback should invalidate all cached keys. */
|
||||
void disconnectCallback(const valkeyAsyncContext *ac, int status) {
|
||||
assert(status == VALKEY_OK);
|
||||
(void)status; /* Suppress unused warning when NDEBUG is defined. */
|
||||
printf("Disconnected from %s:%d\n", ac->c.tcp.host, ac->c.tcp.port);
|
||||
|
||||
printf("Invalidate all\n");
|
||||
|
||||
+5
@@ -93,6 +93,7 @@ typedef struct valkeyClusterContext {
|
||||
int max_retry_count; /* Allowed retry attempts */
|
||||
char *username; /* Authenticate using user */
|
||||
char *password; /* Authentication password */
|
||||
int select_db;
|
||||
|
||||
struct dict *nodes; /* Known valkeyClusterNode's */
|
||||
uint64_t route_version; /* Increased when the node lookup table changes */
|
||||
@@ -163,6 +164,10 @@ typedef struct {
|
||||
const char *password; /* Authentication password. */
|
||||
int max_retry; /* Allowed retry attempts. */
|
||||
|
||||
/* Select a logical database after a successful connect.
|
||||
* Default 0, i.e. the SELECT command is not sent. */
|
||||
int select_db;
|
||||
|
||||
/* Common callbacks. */
|
||||
|
||||
/* A hook to get notified when certain events occur. The `event` is set to
|
||||
|
||||
+8
-2
@@ -50,8 +50,8 @@ typedef SSIZE_T ssize_t;
|
||||
#include <stdint.h> /* uintXX_t, etc */
|
||||
|
||||
#define LIBVALKEY_VERSION_MAJOR 0
|
||||
#define LIBVALKEY_VERSION_MINOR 2
|
||||
#define LIBVALKEY_VERSION_PATCH 1
|
||||
#define LIBVALKEY_VERSION_MINOR 4
|
||||
#define LIBVALKEY_VERSION_PATCH 0
|
||||
|
||||
/* Connection type can be blocking or non-blocking and is set in the
|
||||
* least significant bit of the flags field in valkeyContext. */
|
||||
@@ -270,6 +270,12 @@ typedef struct valkeyContextFuncs {
|
||||
* these functions shall return a value < 0. In the event of a
|
||||
* recoverable error, they should return 0. */
|
||||
ssize_t (*read)(struct valkeyContext *, char *, size_t);
|
||||
/* ZC means zero copy, it provides underlay transport layer buffer directly,
|
||||
* so it has better performance than generic read. After consuming the read
|
||||
* buffer, it's necessary to notify the underlay transport to advance the
|
||||
* read buffer by read_zc_done. */
|
||||
ssize_t (*read_zc)(struct valkeyContext *, char **);
|
||||
ssize_t (*read_zc_done)(struct valkeyContext *);
|
||||
ssize_t (*write)(struct valkeyContext *);
|
||||
int (*set_timeout)(struct valkeyContext *, const struct timeval);
|
||||
} valkeyContextFuncs;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
find examples include libvalkey src tests \
|
||||
find examples include src tests \
|
||||
\( -name '*.c' -or -name '*.cpp' -or -name '*.h' \) \
|
||||
-exec clang-format -i {} + ;
|
||||
|
||||
Vendored
+92
-34
@@ -46,6 +46,7 @@
|
||||
#include "async_private.h"
|
||||
#include "net.h"
|
||||
#include "valkey_private.h"
|
||||
#include "vkutil.h"
|
||||
|
||||
#include <dict.h>
|
||||
#include <sds.h>
|
||||
@@ -61,6 +62,7 @@
|
||||
|
||||
typedef struct {
|
||||
sds command;
|
||||
size_t len;
|
||||
valkeyCallbackFn *user_callback;
|
||||
void *user_priv_data;
|
||||
} ssubscribeCallbackData;
|
||||
@@ -808,21 +810,78 @@ void valkeyAsyncHandleTimeout(valkeyAsyncContext *ac) {
|
||||
valkeyAsyncDisconnectInternal(ac);
|
||||
}
|
||||
|
||||
/* Sets a pointer to the first argument and its length starting at p. Returns
|
||||
* the number of bytes to skip to get to the following argument. */
|
||||
static const char *nextArgument(const char *start, const char **str, size_t *len) {
|
||||
const char *p = start;
|
||||
if (p[0] != '$') {
|
||||
p = strchr(p, '$');
|
||||
if (p == NULL)
|
||||
static inline int vk_isdigit_ascii(char c) {
|
||||
return (unsigned)(c - '0') < 10;
|
||||
}
|
||||
|
||||
#define MAX_BULK_LEN (512ULL * 1024ULL * 1024ULL)
|
||||
vk_static_assert(MAX_BULK_LEN < (UINT64_MAX - 9U) / 10);
|
||||
static const char *parseBulkLen(const char *p, const char *end, uint64_t *len) {
|
||||
uint64_t acc = 0;
|
||||
|
||||
assert(p != NULL && end != NULL && end - p >= 0 && len != NULL);
|
||||
|
||||
if (end == p || !vk_isdigit_ascii(*p))
|
||||
return NULL;
|
||||
|
||||
while (p < end && vk_isdigit_ascii(*p)) {
|
||||
unsigned d = *p - '0';
|
||||
|
||||
acc = acc * 10 + d;
|
||||
if (acc > (uint64_t)MAX_BULK_LEN)
|
||||
return NULL;
|
||||
|
||||
p++;
|
||||
}
|
||||
|
||||
*len = (int)strtol(p + 1, NULL, 10);
|
||||
p = strchr(p, '\r');
|
||||
assert(p);
|
||||
*len = acc;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Find the next argument in a command buffer, i.e. find the next bulkstring
|
||||
* in an array of bulkstrings.
|
||||
* Returns a pointer to the end of a found argument, which can be used when
|
||||
* finding following arguments, or NULL when an argument is not found.
|
||||
* The found string is returned by pointer via `str` and length in `strlen`. */
|
||||
static const char *nextArgument(const char *buf, size_t buflen, const char **str, size_t *strlen) {
|
||||
if (buf == NULL || buflen == 0)
|
||||
goto error;
|
||||
|
||||
const char *p = buf;
|
||||
|
||||
/* Find a bulkstring identifier. */
|
||||
if (p[0] != '$') {
|
||||
if ((p = memchr(p, '$', buflen)) == NULL)
|
||||
goto error;
|
||||
}
|
||||
p++; /* Skip found '$' */
|
||||
|
||||
uint64_t len;
|
||||
|
||||
p = parseBulkLen(p, buf + buflen, &len);
|
||||
if (p == NULL)
|
||||
goto error;
|
||||
|
||||
/* Calculate end pointer for \r\n<payload>\r\n */
|
||||
const char *end = p + 2 + len + 2;
|
||||
|
||||
/* Validate the parsed length and field separators. */
|
||||
if ((size_t)(end - buf) > buflen || p[0] != '\r' || p[len + 2] != '\r')
|
||||
goto error;
|
||||
|
||||
/* Return pointer to the string, length, and pointer to next element. */
|
||||
*str = p + 2;
|
||||
return p + 2 + (*len) + 2;
|
||||
*strlen = len;
|
||||
|
||||
if ((size_t)(end - buf) == buflen) /* No more data in buffer? */
|
||||
return NULL;
|
||||
|
||||
return end;
|
||||
|
||||
error:
|
||||
*str = NULL;
|
||||
*strlen = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void valkeySsubscribeCallback(struct valkeyAsyncContext *ac, void *reply, void *privdata) {
|
||||
@@ -846,8 +905,8 @@ void valkeySsubscribeCallback(struct valkeyAsyncContext *ac, void *reply, void *
|
||||
assert(r != NULL);
|
||||
if (r->type == VALKEY_REPLY_ERROR) {
|
||||
/*/ On CROSSSLOT, MOVED and other errors */
|
||||
p = nextArgument(data->command, &cstr, &clen);
|
||||
while ((p = nextArgument(p, &astr, &alen)) != NULL) {
|
||||
p = nextArgument(data->command, data->len, &cstr, &clen);
|
||||
while ((p = nextArgument(p, data->len - (p - data->command), &astr, &alen)) != NULL || astr != NULL) {
|
||||
sname = sdsnewlen(astr, alen);
|
||||
if (sname == NULL)
|
||||
goto oom;
|
||||
@@ -865,8 +924,8 @@ void valkeySsubscribeCallback(struct valkeyAsyncContext *ac, void *reply, void *
|
||||
}
|
||||
} else {
|
||||
if ((r->type == VALKEY_REPLY_ARRAY || r->type == VALKEY_REPLY_PUSH) && strncasecmp(r->element[0]->str, "ssubscribe", 10) == 0) {
|
||||
p = nextArgument(data->command, &cstr, &clen);
|
||||
while ((p = nextArgument(p, &astr, &alen)) != NULL) {
|
||||
p = nextArgument(data->command, data->len, &cstr, &clen);
|
||||
while ((p = nextArgument(p, data->len - (p - data->command), &astr, &alen)) != NULL || astr != NULL) {
|
||||
sname = sdsnewlen(astr, alen);
|
||||
if (sname == NULL)
|
||||
goto oom;
|
||||
@@ -921,6 +980,18 @@ static int valkeyAsyncAppendCmdLen(valkeyAsyncContext *ac, valkeyCallbackFn *fn,
|
||||
if (c->flags & (VALKEY_DISCONNECTING | VALKEY_FREEING))
|
||||
return VALKEY_ERR;
|
||||
|
||||
/* Get the first string in the command, and don't accept empty commands. */
|
||||
p = nextArgument(cmd, len, &cstr, &clen);
|
||||
if (cstr == NULL)
|
||||
return VALKEY_ERR;
|
||||
|
||||
hasnext = (p && (p[0] == '$'));
|
||||
pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
|
||||
svariant = valkeyIsShardedVariant(cstr);
|
||||
hasprefix = svariant || pvariant;
|
||||
cstr += hasprefix;
|
||||
clen -= hasprefix;
|
||||
|
||||
/* Setup callback */
|
||||
cb.fn = fn;
|
||||
cb.privdata = privdata;
|
||||
@@ -928,22 +999,12 @@ static int valkeyAsyncAppendCmdLen(valkeyAsyncContext *ac, valkeyCallbackFn *fn,
|
||||
cb.unsubscribe_sent = 0;
|
||||
cb.subscribed = 0;
|
||||
|
||||
/* Find out which command will be appended. */
|
||||
p = nextArgument(cmd, &cstr, &clen);
|
||||
assert(p != NULL);
|
||||
hasnext = (p[0] == '$');
|
||||
pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
|
||||
svariant = valkeyIsShardedVariant(cstr);
|
||||
hasprefix = svariant || pvariant;
|
||||
cstr += hasprefix;
|
||||
clen -= hasprefix;
|
||||
|
||||
if (hasnext && strncasecmp(cstr, "subscribe\r\n", 11) == 0) {
|
||||
int was_subscribed = c->flags & VALKEY_SUBSCRIBED;
|
||||
c->flags |= VALKEY_SUBSCRIBED;
|
||||
|
||||
/* Add every channel/pattern to the list of subscription callbacks. */
|
||||
while ((p = nextArgument(p, &astr, &alen)) != NULL) {
|
||||
while ((p = nextArgument(p, len - (p - cmd), &astr, &alen)) != NULL || astr != NULL) {
|
||||
sname = sdsnewlen(astr, alen);
|
||||
if (sname == NULL)
|
||||
goto oom;
|
||||
@@ -977,15 +1038,12 @@ static int valkeyAsyncAppendCmdLen(valkeyAsyncContext *ac, valkeyCallbackFn *fn,
|
||||
if (ssubscribe_data == NULL)
|
||||
goto oom;
|
||||
|
||||
/* copy command to iterate over all channels.
|
||||
* actual length of cmd is actually len + 1 (see valkeyvFormatCommand).
|
||||
* last byte important in nextArgument function.
|
||||
*/
|
||||
ssubscribe_data->command = vk_malloc(len + 1);
|
||||
/* Copy command to iterate over all channels. */
|
||||
ssubscribe_data->command = vk_malloc(len);
|
||||
if (ssubscribe_data->command == NULL)
|
||||
goto oom;
|
||||
memcpy(ssubscribe_data->command, cmd, len + 1);
|
||||
|
||||
memcpy(ssubscribe_data->command, cmd, len);
|
||||
ssubscribe_data->len = len;
|
||||
ssubscribe_data->user_callback = fn;
|
||||
ssubscribe_data->user_priv_data = privdata;
|
||||
|
||||
@@ -1015,7 +1073,7 @@ static int valkeyAsyncAppendCmdLen(valkeyAsyncContext *ac, valkeyCallbackFn *fn,
|
||||
if (hasnext) {
|
||||
/* Send an unsubscribe with specific channels/patterns.
|
||||
* Bookkeeping the number of expected replies */
|
||||
while ((p = nextArgument(p, &astr, &alen)) != NULL) {
|
||||
while ((p = nextArgument(p, len - (p - cmd), &astr, &alen)) != NULL || astr != NULL) {
|
||||
sname = sdsnewlen(astr, alen);
|
||||
if (sname == NULL)
|
||||
goto oom;
|
||||
|
||||
Vendored
+52
-9
@@ -358,6 +358,25 @@ error:
|
||||
return VALKEY_ERR;
|
||||
}
|
||||
|
||||
/* Select a logical database by sending the SELECT command. */
|
||||
static int select_db(valkeyClusterContext *cc, valkeyContext *c) {
|
||||
if (cc->select_db == 0)
|
||||
return VALKEY_OK;
|
||||
|
||||
valkeyReply *reply = valkeyCommand(c, "SELECT %d", cc->select_db);
|
||||
if (reply == NULL) {
|
||||
valkeyClusterSetError(cc, VALKEY_ERR_OTHER, "Failed to select logical database");
|
||||
return VALKEY_ERR;
|
||||
}
|
||||
if (reply->type == VALKEY_REPLY_ERROR) {
|
||||
valkeyClusterSetError(cc, VALKEY_ERR_OTHER, reply->str);
|
||||
freeReplyObject(reply);
|
||||
return VALKEY_ERR;
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return VALKEY_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new node with the "cluster slots" command reply.
|
||||
*/
|
||||
@@ -902,7 +921,7 @@ static dict *parse_cluster_nodes(valkeyClusterContext *cc, valkeyContext *c, val
|
||||
int add_replicas = cc->flags & VALKEY_FLAG_PARSE_REPLICAS;
|
||||
dict *replicas = NULL;
|
||||
|
||||
if (reply->type != VALKEY_REPLY_STRING) {
|
||||
if (reply->type != VALKEY_REPLY_STRING && reply->type != VALKEY_REPLY_VERB) {
|
||||
valkeyClusterSetError(cc, VALKEY_ERR_OTHER, "Unexpected reply type");
|
||||
goto error;
|
||||
}
|
||||
@@ -1189,6 +1208,9 @@ static int valkeyClusterContextInit(valkeyClusterContext *cc,
|
||||
} else {
|
||||
cc->max_retry_count = CLUSTER_DEFAULT_MAX_RETRY_COUNT;
|
||||
}
|
||||
if (options->select_db > 0) {
|
||||
cc->select_db = options->select_db;
|
||||
}
|
||||
if (options->initial_nodes != NULL &&
|
||||
valkeyClusterSetOptionAddNodes(cc, options->initial_nodes) != VALKEY_OK) {
|
||||
return VALKEY_ERR; /* err and errstr already set. */
|
||||
@@ -1563,8 +1585,10 @@ valkeyContext *valkeyClusterGetValkeyContext(valkeyClusterContext *cc,
|
||||
if (cc->tls && cc->tls_init_fn(c, cc->tls) != VALKEY_OK) {
|
||||
valkeyClusterSetError(cc, c->err, c->errstr);
|
||||
}
|
||||
|
||||
authenticate(cc, c); // err and errstr handled in function
|
||||
/* Authenticate and select a logical database when configured.
|
||||
* cc->err and cc->errstr are set when failing. */
|
||||
authenticate(cc, c);
|
||||
select_db(cc, c);
|
||||
}
|
||||
|
||||
return c;
|
||||
@@ -1606,6 +1630,10 @@ valkeyContext *valkeyClusterGetValkeyContext(valkeyClusterContext *cc,
|
||||
valkeyFree(c);
|
||||
return NULL;
|
||||
}
|
||||
if (select_db(cc, c) != VALKEY_OK) {
|
||||
valkeyFree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node->con = c;
|
||||
|
||||
@@ -1951,8 +1979,6 @@ ask_retry:
|
||||
}
|
||||
|
||||
goto moved_retry;
|
||||
|
||||
break;
|
||||
case CLUSTER_ERR_ASK:
|
||||
node = getNodeFromRedirectReply(cc, c, reply, NULL);
|
||||
if (node == NULL) {
|
||||
@@ -1980,15 +2006,12 @@ ask_retry:
|
||||
reply = NULL;
|
||||
|
||||
goto ask_retry;
|
||||
|
||||
break;
|
||||
case CLUSTER_ERR_TRYAGAIN:
|
||||
case CLUSTER_ERR_CLUSTERDOWN:
|
||||
freeReplyObject(reply);
|
||||
reply = NULL;
|
||||
goto retry;
|
||||
|
||||
break;
|
||||
default:
|
||||
|
||||
break;
|
||||
@@ -2582,6 +2605,18 @@ static void unlinkAsyncContextAndNode(void *data) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Reply callback function for SELECT */
|
||||
void selectReplyCallback(valkeyAsyncContext *ac, void *r, void *privdata) {
|
||||
valkeyReply *reply = (valkeyReply *)r;
|
||||
valkeyClusterAsyncContext *acc = (valkeyClusterAsyncContext *)privdata;
|
||||
|
||||
if (reply == NULL || reply->type == VALKEY_REPLY_ERROR) {
|
||||
valkeyClusterAsyncSetError(acc, VALKEY_ERR_OTHER,
|
||||
"Failed to select logical database");
|
||||
valkeyAsyncDisconnect(ac);
|
||||
}
|
||||
}
|
||||
|
||||
valkeyAsyncContext *
|
||||
valkeyClusterGetValkeyAsyncContext(valkeyClusterAsyncContext *acc,
|
||||
valkeyClusterNode *node) {
|
||||
@@ -2658,6 +2693,15 @@ valkeyClusterGetValkeyAsyncContext(valkeyClusterAsyncContext *acc,
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
// Select logical database when needed
|
||||
if (acc->cc.select_db > 0) {
|
||||
ret = valkeyAsyncCommand(ac, selectReplyCallback, acc, "SELECT %d", acc->cc.select_db);
|
||||
if (ret != VALKEY_OK) {
|
||||
valkeyClusterAsyncSetError(acc, ac->c.err, ac->c.errstr);
|
||||
valkeyAsyncFree(ac);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (acc->attach_fn) {
|
||||
ret = acc->attach_fn(ac, acc->attach_data);
|
||||
@@ -2996,7 +3040,6 @@ static void valkeyClusterAsyncCallback(valkeyAsyncContext *ac, void *r,
|
||||
default:
|
||||
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
|
||||
goto retry;
|
||||
|
||||
Vendored
+68
-2
@@ -17,6 +17,15 @@ COMMAND(ACL_WHOAMI, "ACL", "WHOAMI", 2, NONE, 0)
|
||||
COMMAND(APPEND, "APPEND", NULL, 3, INDEX, 1)
|
||||
COMMAND(ASKING, "ASKING", NULL, 1, NONE, 0)
|
||||
COMMAND(AUTH, "AUTH", NULL, -2, NONE, 0)
|
||||
COMMAND(BF_ADD, "BF.ADD", NULL, 3, INDEX, 1)
|
||||
COMMAND(BF_CARD, "BF.CARD", NULL, 2, NONE, 0)
|
||||
COMMAND(BF_EXISTS, "BF.EXISTS", NULL, 3, INDEX, 1)
|
||||
COMMAND(BF_INFO, "BF.INFO", NULL, -2, INDEX, 1)
|
||||
COMMAND(BF_INSERT, "BF.INSERT", NULL, -2, INDEX, 1)
|
||||
COMMAND(BF_LOAD, "BF.LOAD", NULL, 3, INDEX, 1)
|
||||
COMMAND(BF_MADD, "BF.MADD", NULL, 3, INDEX, 1)
|
||||
COMMAND(BF_MEXISTS, "BF.MEXISTS", NULL, 3, INDEX, 1)
|
||||
COMMAND(BF_RESERVE, "BF.RESERVE", NULL, -4, INDEX, 1)
|
||||
COMMAND(BGREWRITEAOF, "BGREWRITEAOF", NULL, 1, NONE, 0)
|
||||
COMMAND(BGSAVE, "BGSAVE", NULL, -1, NONE, 0)
|
||||
COMMAND(BITCOUNT, "BITCOUNT", NULL, -2, INDEX, 1)
|
||||
@@ -33,10 +42,12 @@ COMMAND(BZMPOP, "BZMPOP", NULL, -5, KEYNUM, 2)
|
||||
COMMAND(BZPOPMAX, "BZPOPMAX", NULL, -3, INDEX, 1)
|
||||
COMMAND(BZPOPMIN, "BZPOPMIN", NULL, -3, INDEX, 1)
|
||||
COMMAND(CLIENT_CACHING, "CLIENT", "CACHING", 3, NONE, 0)
|
||||
COMMAND(CLIENT_CAPA, "CLIENT", "CAPA", -3, NONE, 0)
|
||||
COMMAND(CLIENT_GETNAME, "CLIENT", "GETNAME", 2, NONE, 0)
|
||||
COMMAND(CLIENT_GETREDIR, "CLIENT", "GETREDIR", 2, NONE, 0)
|
||||
COMMAND(CLIENT_HELP, "CLIENT", "HELP", 2, NONE, 0)
|
||||
COMMAND(CLIENT_ID, "CLIENT", "ID", 2, NONE, 0)
|
||||
COMMAND(CLIENT_IMPORT_SOURCE, "CLIENT", "IMPORT-SOURCE", 3, NONE, 0)
|
||||
COMMAND(CLIENT_INFO, "CLIENT", "INFO", 2, NONE, 0)
|
||||
COMMAND(CLIENT_KILL, "CLIENT", "KILL", -3, NONE, 0)
|
||||
COMMAND(CLIENT_LIST, "CLIENT", "LIST", -2, NONE, 0)
|
||||
@@ -53,31 +64,41 @@ COMMAND(CLIENT_UNPAUSE, "CLIENT", "UNPAUSE", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_ADDSLOTS, "CLUSTER", "ADDSLOTS", -3, NONE, 0)
|
||||
COMMAND(CLUSTER_ADDSLOTSRANGE, "CLUSTER", "ADDSLOTSRANGE", -4, NONE, 0)
|
||||
COMMAND(CLUSTER_BUMPEPOCH, "CLUSTER", "BUMPEPOCH", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_CANCELSLOTMIGRATIONS, "CLUSTER", "CANCELSLOTMIGRATIONS", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_COUNT_FAILURE_REPORTS, "CLUSTER", "COUNT-FAILURE-REPORTS", 3, NONE, 0)
|
||||
COMMAND(CLUSTER_COUNTKEYSINSLOT, "CLUSTER", "COUNTKEYSINSLOT", 3, NONE, 0)
|
||||
COMMAND(CLUSTER_DELSLOTS, "CLUSTER", "DELSLOTS", -3, NONE, 0)
|
||||
COMMAND(CLUSTER_DELSLOTSRANGE, "CLUSTER", "DELSLOTSRANGE", -4, NONE, 0)
|
||||
COMMAND(CLUSTER_FAILOVER, "CLUSTER", "FAILOVER", -2, NONE, 0)
|
||||
COMMAND(CLUSTER_FLUSHSLOT, "CLUSTER", "FLUSHSLOT", -3, NONE, 0)
|
||||
COMMAND(CLUSTER_FLUSHSLOTS, "CLUSTER", "FLUSHSLOTS", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_FORGET, "CLUSTER", "FORGET", 3, NONE, 0)
|
||||
COMMAND(CLUSTER_GETKEYSINSLOT, "CLUSTER", "GETKEYSINSLOT", 4, NONE, 0)
|
||||
COMMAND(CLUSTER_GETSLOTMIGRATIONS, "CLUSTER", "GETSLOTMIGRATIONS", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_HELP, "CLUSTER", "HELP", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_INFO, "CLUSTER", "INFO", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_KEYSLOT, "CLUSTER", "KEYSLOT", 3, NONE, 0)
|
||||
COMMAND(CLUSTER_LINKS, "CLUSTER", "LINKS", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_MEET, "CLUSTER", "MEET", -4, NONE, 0)
|
||||
COMMAND(CLUSTER_MIGRATESLOTS, "CLUSTER", "MIGRATESLOTS", -4, NONE, 0)
|
||||
COMMAND(CLUSTER_MYID, "CLUSTER", "MYID", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_MYSHARDID, "CLUSTER", "MYSHARDID", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_NODES, "CLUSTER", "NODES", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_REPLICAS, "CLUSTER", "REPLICAS", 3, NONE, 0)
|
||||
COMMAND(CLUSTER_REPLICATE, "CLUSTER", "REPLICATE", 3, NONE, 0)
|
||||
COMMAND(CLUSTER_REPLICATE, "CLUSTER", "REPLICATE", -3, NONE, 0)
|
||||
COMMAND(CLUSTER_RESET, "CLUSTER", "RESET", -2, NONE, 0)
|
||||
COMMAND(CLUSTER_SAVECONFIG, "CLUSTER", "SAVECONFIG", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_SET_CONFIG_EPOCH, "CLUSTER", "SET-CONFIG-EPOCH", 3, NONE, 0)
|
||||
COMMAND(CLUSTER_SETSLOT, "CLUSTER", "SETSLOT", -4, NONE, 0)
|
||||
COMMAND(CLUSTER_SHARDS, "CLUSTER", "SHARDS", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_SLAVES, "CLUSTER", "SLAVES", 3, NONE, 0)
|
||||
COMMAND(CLUSTER_SLOT_STATS, "CLUSTER", "SLOT-STATS", -4, NONE, 0)
|
||||
COMMAND(CLUSTER_SLOTS, "CLUSTER", "SLOTS", 2, NONE, 0)
|
||||
COMMAND(CLUSTER_SYNCSLOTS, "CLUSTER", "SYNCSLOTS", -3, NONE, 0)
|
||||
COMMAND(COMMANDLOG_GET, "COMMANDLOG", "GET", 4, NONE, 0)
|
||||
COMMAND(COMMANDLOG_HELP, "COMMANDLOG", "HELP", 2, NONE, 0)
|
||||
COMMAND(COMMANDLOG_LEN, "COMMANDLOG", "LEN", 3, NONE, 0)
|
||||
COMMAND(COMMANDLOG_RESET, "COMMANDLOG", "RESET", 3, NONE, 0)
|
||||
COMMAND(COMMAND_COUNT, "COMMAND", "COUNT", 2, NONE, 0)
|
||||
COMMAND(COMMAND_DOCS, "COMMAND", "DOCS", -2, NONE, 0)
|
||||
COMMAND(COMMAND_GETKEYS, "COMMAND", "GETKEYS", -3, NONE, 0)
|
||||
@@ -96,6 +117,7 @@ COMMAND(DEBUG, "DEBUG", NULL, -2, NONE, 0)
|
||||
COMMAND(DECR, "DECR", NULL, 2, INDEX, 1)
|
||||
COMMAND(DECRBY, "DECRBY", NULL, 3, INDEX, 1)
|
||||
COMMAND(DEL, "DEL", NULL, -2, INDEX, 1)
|
||||
COMMAND(DELIFEQ, "DELIFEQ", NULL, 3, INDEX, 1)
|
||||
COMMAND(DISCARD, "DISCARD", NULL, 1, NONE, 0)
|
||||
COMMAND(DUMP, "DUMP", NULL, 2, INDEX, 1)
|
||||
COMMAND(ECHO, "ECHO", NULL, 2, NONE, 0)
|
||||
@@ -113,6 +135,11 @@ COMMAND(FCALL, "FCALL", NULL, -3, KEYNUM, 2)
|
||||
COMMAND(FCALL_RO, "FCALL_RO", NULL, -3, KEYNUM, 2)
|
||||
COMMAND(FLUSHALL, "FLUSHALL", NULL, -1, NONE, 0)
|
||||
COMMAND(FLUSHDB, "FLUSHDB", NULL, -1, NONE, 0)
|
||||
COMMAND(FT_CREATE, "FT.CREATE", NULL, -1, NONE, 0)
|
||||
COMMAND(FT_DROPINDEX, "FT.DROPINDEX", NULL, 2, NONE, 0)
|
||||
COMMAND(FT_INFO, "FT.INFO", NULL, 2, NONE, 0)
|
||||
COMMAND(FT_SEARCH, "FT.SEARCH", NULL, -3, INDEX, 1)
|
||||
COMMAND(FT__LIST, "FT._LIST", NULL, 1, NONE, 0)
|
||||
COMMAND(FUNCTION_DELETE, "FUNCTION", "DELETE", 3, NONE, 0)
|
||||
COMMAND(FUNCTION_DUMP, "FUNCTION", "DUMP", 2, NONE, 0)
|
||||
COMMAND(FUNCTION_FLUSH, "FUNCTION", "FLUSH", -2, NONE, 0)
|
||||
@@ -141,24 +168,58 @@ COMMAND(GETSET, "GETSET", NULL, 3, INDEX, 1)
|
||||
COMMAND(HDEL, "HDEL", NULL, -3, INDEX, 1)
|
||||
COMMAND(HELLO, "HELLO", NULL, -1, NONE, 0)
|
||||
COMMAND(HEXISTS, "HEXISTS", NULL, 3, INDEX, 1)
|
||||
COMMAND(HEXPIRE, "HEXPIRE", NULL, -6, INDEX, 1)
|
||||
COMMAND(HEXPIREAT, "HEXPIREAT", NULL, -6, INDEX, 1)
|
||||
COMMAND(HEXPIRETIME, "HEXPIRETIME", NULL, -5, INDEX, 1)
|
||||
COMMAND(HGET, "HGET", NULL, 3, INDEX, 1)
|
||||
COMMAND(HGETALL, "HGETALL", NULL, 2, INDEX, 1)
|
||||
COMMAND(HGETEX, "HGETEX", NULL, -5, INDEX, 1)
|
||||
COMMAND(HINCRBY, "HINCRBY", NULL, 4, INDEX, 1)
|
||||
COMMAND(HINCRBYFLOAT, "HINCRBYFLOAT", NULL, 4, INDEX, 1)
|
||||
COMMAND(HKEYS, "HKEYS", NULL, 2, INDEX, 1)
|
||||
COMMAND(HLEN, "HLEN", NULL, 2, INDEX, 1)
|
||||
COMMAND(HMGET, "HMGET", NULL, -3, INDEX, 1)
|
||||
COMMAND(HMSET, "HMSET", NULL, -4, INDEX, 1)
|
||||
COMMAND(HPERSIST, "HPERSIST", NULL, -5, INDEX, 1)
|
||||
COMMAND(HPEXPIRE, "HPEXPIRE", NULL, -6, INDEX, 1)
|
||||
COMMAND(HPEXPIREAT, "HPEXPIREAT", NULL, -6, INDEX, 1)
|
||||
COMMAND(HPEXPIRETIME, "HPEXPIRETIME", NULL, -5, INDEX, 1)
|
||||
COMMAND(HPTTL, "HPTTL", NULL, -5, INDEX, 1)
|
||||
COMMAND(HRANDFIELD, "HRANDFIELD", NULL, -2, INDEX, 1)
|
||||
COMMAND(HSCAN, "HSCAN", NULL, -3, INDEX, 1)
|
||||
COMMAND(HSET, "HSET", NULL, -4, INDEX, 1)
|
||||
COMMAND(HSETEX, "HSETEX", NULL, -6, INDEX, 1)
|
||||
COMMAND(HSETNX, "HSETNX", NULL, 4, INDEX, 1)
|
||||
COMMAND(HSTRLEN, "HSTRLEN", NULL, 3, INDEX, 1)
|
||||
COMMAND(HTTL, "HTTL", NULL, -5, INDEX, 1)
|
||||
COMMAND(HVALS, "HVALS", NULL, 2, INDEX, 1)
|
||||
COMMAND(INCR, "INCR", NULL, 2, INDEX, 1)
|
||||
COMMAND(INCRBY, "INCRBY", NULL, 3, INDEX, 1)
|
||||
COMMAND(INCRBYFLOAT, "INCRBYFLOAT", NULL, 3, INDEX, 1)
|
||||
COMMAND(INFO, "INFO", NULL, -1, NONE, 0)
|
||||
COMMAND(JSON_ARRAPPEND, "JSON.ARRAPPEND", NULL, -4, INDEX, 1)
|
||||
COMMAND(JSON_ARRINDEX, "JSON.ARRINDEX", NULL, 4, INDEX, 1)
|
||||
COMMAND(JSON_ARRINSERT, "JSON.ARRINSERT", NULL, -5, INDEX, 1)
|
||||
COMMAND(JSON_ARRLEN, "JSON.ARRLEN", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_ARRPOP, "JSON.ARRPOP", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_ARRTRIM, "JSON.ARRTRIM", NULL, 5, INDEX, 1)
|
||||
COMMAND(JSON_CLEAR, "JSON.CLEAR", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_DEBUG, "JSON.DEBUG", NULL, 2, NONE, 0)
|
||||
COMMAND(JSON_DEL, "JSON.DEL", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_FORGET, "JSON.FORGET", NULL, -1, NONE, 0)
|
||||
COMMAND(JSON_GET, "JSON.GET", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_MGET, "JSON.MGET", NULL, -3, INDEX, 1)
|
||||
COMMAND(JSON_MSET, "JSON.MSET", NULL, -4, NONE, 0)
|
||||
COMMAND(JSON_NUMINCRBY, "JSON.NUMINCRBY", NULL, 4, INDEX, 1)
|
||||
COMMAND(JSON_NUMMULTBY, "JSON.NUMMULTBY", NULL, 4, INDEX, 1)
|
||||
COMMAND(JSON_OBJKEYS, "JSON.OBJKEYS", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_OBJLEN, "JSON.OBJLEN", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_RESP, "JSON.RESP", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_SET, "JSON.SET", NULL, 4, INDEX, 1)
|
||||
COMMAND(JSON_STRAPPEND, "JSON.STRAPPEND", NULL, 3, INDEX, 1)
|
||||
COMMAND(JSON_STRLEN, "JSON.STRLEN", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_TOGGLE, "JSON.TOGGLE", NULL, 2, INDEX, 1)
|
||||
COMMAND(JSON_TYPE, "JSON.TYPE", NULL, 2, INDEX, 1)
|
||||
COMMAND(KEYS, "KEYS", NULL, 2, NONE, 0)
|
||||
COMMAND(LASTSAVE, "LASTSAVE", NULL, 1, NONE, 0)
|
||||
COMMAND(LATENCY_DOCTOR, "LATENCY", "DOCTOR", 2, NONE, 0)
|
||||
@@ -254,23 +315,28 @@ COMMAND(SCRIPT_FLUSH, "SCRIPT", "FLUSH", -2, NONE, 0)
|
||||
COMMAND(SCRIPT_HELP, "SCRIPT", "HELP", 2, NONE, 0)
|
||||
COMMAND(SCRIPT_KILL, "SCRIPT", "KILL", 2, NONE, 0)
|
||||
COMMAND(SCRIPT_LOAD, "SCRIPT", "LOAD", 3, NONE, 0)
|
||||
COMMAND(SCRIPT_SHOW, "SCRIPT", "SHOW", 3, NONE, 0)
|
||||
COMMAND(SDIFF, "SDIFF", NULL, -2, INDEX, 1)
|
||||
COMMAND(SDIFFSTORE, "SDIFFSTORE", NULL, -3, INDEX, 1)
|
||||
COMMAND(SELECT, "SELECT", NULL, 2, NONE, 0)
|
||||
COMMAND(SENTINEL_CKQUORUM, "SENTINEL", "CKQUORUM", 3, NONE, 0)
|
||||
COMMAND(SENTINEL_CONFIG, "SENTINEL", "CONFIG", -4, NONE, 0)
|
||||
COMMAND(SENTINEL_DEBUG, "SENTINEL", "DEBUG", -2, NONE, 0)
|
||||
COMMAND(SENTINEL_FAILOVER, "SENTINEL", "FAILOVER", 3, NONE, 0)
|
||||
COMMAND(SENTINEL_FAILOVER, "SENTINEL", "FAILOVER", -3, NONE, 0)
|
||||
COMMAND(SENTINEL_FLUSHCONFIG, "SENTINEL", "FLUSHCONFIG", 2, NONE, 0)
|
||||
COMMAND(SENTINEL_GET_MASTER_ADDR_BY_NAME, "SENTINEL", "GET-MASTER-ADDR-BY-NAME", 3, NONE, 0)
|
||||
COMMAND(SENTINEL_GET_PRIMARY_ADDR_BY_NAME, "SENTINEL", "GET-PRIMARY-ADDR-BY-NAME", 3, NONE, 0)
|
||||
COMMAND(SENTINEL_HELP, "SENTINEL", "HELP", 2, NONE, 0)
|
||||
COMMAND(SENTINEL_INFO_CACHE, "SENTINEL", "INFO-CACHE", -3, NONE, 0)
|
||||
COMMAND(SENTINEL_IS_MASTER_DOWN_BY_ADDR, "SENTINEL", "IS-MASTER-DOWN-BY-ADDR", 6, NONE, 0)
|
||||
COMMAND(SENTINEL_IS_PRIMARY_DOWN_BY_ADDR, "SENTINEL", "IS-PRIMARY-DOWN-BY-ADDR", 6, NONE, 0)
|
||||
COMMAND(SENTINEL_MASTER, "SENTINEL", "MASTER", 3, NONE, 0)
|
||||
COMMAND(SENTINEL_MASTERS, "SENTINEL", "MASTERS", 2, NONE, 0)
|
||||
COMMAND(SENTINEL_MONITOR, "SENTINEL", "MONITOR", 6, NONE, 0)
|
||||
COMMAND(SENTINEL_MYID, "SENTINEL", "MYID", 2, NONE, 0)
|
||||
COMMAND(SENTINEL_PENDING_SCRIPTS, "SENTINEL", "PENDING-SCRIPTS", 2, NONE, 0)
|
||||
COMMAND(SENTINEL_PRIMARIES, "SENTINEL", "PRIMARIES", 2, NONE, 0)
|
||||
COMMAND(SENTINEL_PRIMARY, "SENTINEL", "PRIMARY", 3, NONE, 0)
|
||||
COMMAND(SENTINEL_REMOVE, "SENTINEL", "REMOVE", 3, NONE, 0)
|
||||
COMMAND(SENTINEL_REPLICAS, "SENTINEL", "REPLICAS", 3, NONE, 0)
|
||||
COMMAND(SENTINEL_RESET, "SENTINEL", "RESET", 3, NONE, 0)
|
||||
|
||||
Vendored
+10
-12
@@ -28,15 +28,12 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "fmacros.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#if !defined(__FreeBSD__)
|
||||
#include <alloca.h>
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#include <strings.h>
|
||||
#else
|
||||
#include <malloc.h>
|
||||
@@ -52,6 +49,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_COMMAND_LEN 64
|
||||
#define LF (uint8_t)10
|
||||
#define CR (uint8_t)13
|
||||
|
||||
@@ -99,11 +97,13 @@ static inline void to_upper(char *dst, const char *src, uint32_t len) {
|
||||
* or NULL on failure. */
|
||||
cmddef *valkey_lookup_cmd(const char *arg0, uint32_t arg0_len, const char *arg1,
|
||||
uint32_t arg1_len) {
|
||||
if (arg0_len > MAX_COMMAND_LEN)
|
||||
return NULL;
|
||||
char cmd[MAX_COMMAND_LEN];
|
||||
char subcmd[MAX_COMMAND_LEN] = "";
|
||||
int num_commands = sizeof(server_commands) / sizeof(cmddef);
|
||||
/* Compare command name in uppercase. */
|
||||
char *cmd = alloca(arg0_len);
|
||||
to_upper(cmd, arg0, arg0_len);
|
||||
char *subcmd = NULL; /* Alloca later on demand. */
|
||||
/* Find the command using binary search. */
|
||||
int left = 0, right = num_commands - 1;
|
||||
while (left <= right) {
|
||||
@@ -116,14 +116,12 @@ cmddef *valkey_lookup_cmd(const char *arg0, uint32_t arg0_len, const char *arg1,
|
||||
|
||||
/* If command name matches, compare subcommand if any */
|
||||
if (cmp == 0 && c->subname != NULL) {
|
||||
if (arg1 == NULL) {
|
||||
/* Command has subcommands, but none given. */
|
||||
if (arg1 == NULL || arg1_len == 0 || arg1_len > MAX_COMMAND_LEN) {
|
||||
/* Command has subcommands, but none given or is too long. */
|
||||
return NULL;
|
||||
}
|
||||
if (subcmd == NULL) {
|
||||
subcmd = alloca(arg1_len);
|
||||
if (subcmd[0] == '\0')
|
||||
to_upper(subcmd, arg1, arg1_len);
|
||||
}
|
||||
cmp = strncmp(c->subname, subcmd, arg1_len);
|
||||
if (cmp == 0 && strlen(c->subname) > arg1_len)
|
||||
cmp = 1;
|
||||
|
||||
Vendored
+55
-6
@@ -32,6 +32,53 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef VALKEY_USE_THREADS
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__) || defined(__sun)
|
||||
#define VALKEY_PTHREADS_ONCE 1
|
||||
#elif defined(_WIN32)
|
||||
#define VALKEY_WINDOWS_ONCE 1
|
||||
#else
|
||||
#error "No call-once implementation available on this platform"
|
||||
#endif
|
||||
|
||||
#endif // VALKEY_USE_THREADS
|
||||
|
||||
static void vkRegisterFuncs(void) {
|
||||
valkeyContextRegisterTcpFuncs();
|
||||
valkeyContextRegisterUnixFuncs();
|
||||
valkeyContextRegisterUserfdFuncs();
|
||||
}
|
||||
|
||||
#if VALKEY_PTHREADS_ONCE
|
||||
#include <pthread.h>
|
||||
|
||||
static void vkRegisterFuncsOnce(void) {
|
||||
static pthread_once_t flag = PTHREAD_ONCE_INIT;
|
||||
pthread_once(&flag, vkRegisterFuncs);
|
||||
}
|
||||
|
||||
#elif VALKEY_WINDOWS_ONCE
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
/* Windows uses a different signature for the init function */
|
||||
static BOOL CALLBACK
|
||||
vkRegisterFuncsWrapper(PINIT_ONCE once, PVOID param, PVOID *ctx) {
|
||||
(void)once;
|
||||
(void)param;
|
||||
(void)ctx;
|
||||
vkRegisterFuncs();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void vkRegisterFuncsOnce(void) {
|
||||
static INIT_ONCE flag = INIT_ONCE_STATIC_INIT;
|
||||
InitOnceExecuteOnce(&flag, vkRegisterFuncsWrapper, NULL, NULL);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static valkeyContextFuncs *valkeyContextFuncsArray[VALKEY_CONN_MAX];
|
||||
|
||||
int valkeyContextRegisterFuncs(valkeyContextFuncs *funcs, enum valkeyConnectionType type) {
|
||||
@@ -43,14 +90,16 @@ int valkeyContextRegisterFuncs(valkeyContextFuncs *funcs, enum valkeyConnectionT
|
||||
}
|
||||
|
||||
void valkeyContextSetFuncs(valkeyContext *c) {
|
||||
static int initialized;
|
||||
#ifdef VALKEY_USE_THREADS
|
||||
vkRegisterFuncsOnce();
|
||||
#else
|
||||
static int registered = 0;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = 1;
|
||||
valkeyContextRegisterTcpFuncs();
|
||||
valkeyContextRegisterUnixFuncs();
|
||||
valkeyContextRegisterUserfdFuncs();
|
||||
if (!registered) {
|
||||
registered = 1;
|
||||
vkRegisterFuncs();
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(c->connection_type < VALKEY_CONN_MAX);
|
||||
assert(!c->funcs);
|
||||
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
#ifndef VALKEY_FMACRO_H
|
||||
#define VALKEY_FMACRO_H
|
||||
|
||||
#ifndef _AIX
|
||||
#if !defined(_AIX) && !defined(__FreeBSD__)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#endif
|
||||
|
||||
Vendored
+182
-18
@@ -47,10 +47,163 @@
|
||||
#include <netdb.h>
|
||||
#include <poll.h>
|
||||
#include <rdma/rdma_cma.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef DLOPEN_RDMA
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ====================================================================
|
||||
* RDMA Dynamic Symbol Table
|
||||
* ====================================================================
|
||||
* * Why wrap these in a struct and use #define macros?
|
||||
* * We must include <rdma/rdma_cma.h> and <infiniband/verbs.h> to access
|
||||
* essential data structures. However, these headers also contain the
|
||||
* native function prototypes. If we declare global function pointers
|
||||
* with the exact same names, the compiler will throw a redeclaration
|
||||
* error (symbol conflict).
|
||||
* * By wrapping them in a struct and using #define macros *after* the
|
||||
* includes, we cleanly intercept the function calls without violating
|
||||
* C namespace rules, leaving all original call sites completely untouched.
|
||||
* ==================================================================== */
|
||||
static struct rdma_dyn_syms {
|
||||
/* ====================================================================
|
||||
* Symbols from: <rdma/rdma_cma.h>
|
||||
* Compatible shared library: librdmacm.so.1
|
||||
* ==================================================================== */
|
||||
|
||||
struct rdma_event_channel *(*rdma_create_event_channel)(void);
|
||||
void (*rdma_destroy_event_channel)(struct rdma_event_channel *channel);
|
||||
int (*rdma_create_id)(struct rdma_event_channel *channel, struct rdma_cm_id **id, void *context, enum rdma_port_space ps);
|
||||
int (*rdma_create_qp)(struct rdma_cm_id *id, struct ibv_pd *pd, struct ibv_qp_init_attr *qp_init_attr);
|
||||
int (*rdma_destroy_id)(struct rdma_cm_id *id);
|
||||
int (*rdma_bind_addr)(struct rdma_cm_id *id, struct sockaddr *addr);
|
||||
int (*rdma_resolve_addr)(struct rdma_cm_id *id, struct sockaddr *src_addr, struct sockaddr *dst_addr, int timeout_ms);
|
||||
int (*rdma_resolve_route)(struct rdma_cm_id *id, int timeout_ms);
|
||||
int (*rdma_connect)(struct rdma_cm_id *id, struct rdma_conn_param *conn_param);
|
||||
int (*rdma_disconnect)(struct rdma_cm_id *id);
|
||||
int (*rdma_get_cm_event)(struct rdma_event_channel *channel, struct rdma_cm_event **event);
|
||||
int (*rdma_ack_cm_event)(struct rdma_cm_event *event);
|
||||
int (*rdma_getaddrinfo)(const char *node, const char *service, struct rdma_addrinfo *hints, struct rdma_addrinfo **res);
|
||||
void (*rdma_freeaddrinfo)(struct rdma_addrinfo *res);
|
||||
const char *(*rdma_event_str)(enum rdma_cm_event_type event);
|
||||
|
||||
/* ====================================================================
|
||||
* Symbols from: <infiniband/verbs.h>
|
||||
* Compatible shared library: libibverbs.so.1
|
||||
* ==================================================================== */
|
||||
|
||||
struct ibv_pd *(*ibv_alloc_pd)(struct ibv_context *context);
|
||||
int (*ibv_dealloc_pd)(struct ibv_pd *pd);
|
||||
struct ibv_comp_channel *(*ibv_create_comp_channel)(struct ibv_context *context);
|
||||
int (*ibv_destroy_comp_channel)(struct ibv_comp_channel *channel);
|
||||
struct ibv_cq *(*ibv_create_cq)(struct ibv_context *context, int cqe, void *cq_context, struct ibv_comp_channel *channel, int comp_vector);
|
||||
int (*ibv_destroy_cq)(struct ibv_cq *cq);
|
||||
struct ibv_mr *(*ibv_reg_mr)(struct ibv_pd *pd, void *addr, size_t length, int access);
|
||||
int (*ibv_dereg_mr)(struct ibv_mr *mr);
|
||||
int (*ibv_get_cq_event)(struct ibv_comp_channel *channel, struct ibv_cq **cq, void **cq_context);
|
||||
void (*ibv_ack_cq_events)(struct ibv_cq *cq, unsigned int nevents);
|
||||
int (*ibv_destroy_qp)(struct ibv_qp *qp);
|
||||
} rdma_syms;
|
||||
|
||||
static void *rdmacm_handle = NULL;
|
||||
static void *ibverbs_handle = NULL;
|
||||
|
||||
#define LOAD_SYM(handle, name) \
|
||||
do { \
|
||||
void *tmp_sym = dlsym(handle, #name); \
|
||||
if (!tmp_sym) { \
|
||||
fprintf(stderr, "RDMA Init Error: missing symbol %s\n", #name); \
|
||||
return -1; \
|
||||
} \
|
||||
*(void **)(&rdma_syms.name) = tmp_sym; \
|
||||
} while (0)
|
||||
|
||||
static int rdma_dyn_load_libs(void) {
|
||||
if (rdmacm_handle && ibverbs_handle)
|
||||
return 0;
|
||||
|
||||
const char *rdmacm_candidates[] = {"librdmacm.so.1", "librdmacm.so", NULL};
|
||||
const char *verbs_candidates[] = {"libibverbs.so.1", "libibverbs.so", NULL};
|
||||
|
||||
for (int i = 0; !rdmacm_handle && rdmacm_candidates[i]; i++)
|
||||
rdmacm_handle = dlopen(rdmacm_candidates[i], RTLD_NOW);
|
||||
for (int i = 0; !ibverbs_handle && verbs_candidates[i]; i++)
|
||||
ibverbs_handle = dlopen(verbs_candidates[i], RTLD_NOW);
|
||||
|
||||
if (!rdmacm_handle || !ibverbs_handle) {
|
||||
fprintf(stderr, "Error: Required RDMA libraries (librdmacm/libibverbs) not found on this system.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOAD_SYM(rdmacm_handle, rdma_create_event_channel);
|
||||
LOAD_SYM(rdmacm_handle, rdma_destroy_event_channel);
|
||||
LOAD_SYM(rdmacm_handle, rdma_create_id);
|
||||
LOAD_SYM(rdmacm_handle, rdma_create_qp);
|
||||
LOAD_SYM(rdmacm_handle, rdma_destroy_id);
|
||||
LOAD_SYM(rdmacm_handle, rdma_bind_addr);
|
||||
LOAD_SYM(rdmacm_handle, rdma_resolve_addr);
|
||||
LOAD_SYM(rdmacm_handle, rdma_resolve_route);
|
||||
LOAD_SYM(rdmacm_handle, rdma_connect);
|
||||
LOAD_SYM(rdmacm_handle, rdma_disconnect);
|
||||
LOAD_SYM(rdmacm_handle, rdma_get_cm_event);
|
||||
LOAD_SYM(rdmacm_handle, rdma_ack_cm_event);
|
||||
LOAD_SYM(rdmacm_handle, rdma_getaddrinfo);
|
||||
LOAD_SYM(rdmacm_handle, rdma_freeaddrinfo);
|
||||
LOAD_SYM(rdmacm_handle, rdma_event_str);
|
||||
|
||||
LOAD_SYM(ibverbs_handle, ibv_alloc_pd);
|
||||
LOAD_SYM(ibverbs_handle, ibv_dealloc_pd);
|
||||
LOAD_SYM(ibverbs_handle, ibv_create_comp_channel);
|
||||
LOAD_SYM(ibverbs_handle, ibv_destroy_comp_channel);
|
||||
LOAD_SYM(ibverbs_handle, ibv_create_cq);
|
||||
LOAD_SYM(ibverbs_handle, ibv_destroy_cq);
|
||||
LOAD_SYM(ibverbs_handle, ibv_reg_mr);
|
||||
LOAD_SYM(ibverbs_handle, ibv_dereg_mr);
|
||||
LOAD_SYM(ibverbs_handle, ibv_get_cq_event);
|
||||
LOAD_SYM(ibverbs_handle, ibv_ack_cq_events);
|
||||
LOAD_SYM(ibverbs_handle, ibv_destroy_qp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define rdma_create_event_channel rdma_syms.rdma_create_event_channel
|
||||
#define rdma_destroy_event_channel rdma_syms.rdma_destroy_event_channel
|
||||
#define rdma_create_id rdma_syms.rdma_create_id
|
||||
#define rdma_create_qp rdma_syms.rdma_create_qp
|
||||
#define rdma_destroy_id rdma_syms.rdma_destroy_id
|
||||
#define rdma_bind_addr rdma_syms.rdma_bind_addr
|
||||
#define rdma_resolve_addr rdma_syms.rdma_resolve_addr
|
||||
#define rdma_resolve_route rdma_syms.rdma_resolve_route
|
||||
#define rdma_connect rdma_syms.rdma_connect
|
||||
#define rdma_disconnect rdma_syms.rdma_disconnect
|
||||
#define rdma_get_cm_event rdma_syms.rdma_get_cm_event
|
||||
#define rdma_ack_cm_event rdma_syms.rdma_ack_cm_event
|
||||
#define rdma_getaddrinfo rdma_syms.rdma_getaddrinfo
|
||||
#define rdma_freeaddrinfo rdma_syms.rdma_freeaddrinfo
|
||||
#define rdma_event_str rdma_syms.rdma_event_str
|
||||
|
||||
#ifdef ibv_reg_mr
|
||||
#undef ibv_reg_mr
|
||||
#endif
|
||||
|
||||
#define ibv_alloc_pd rdma_syms.ibv_alloc_pd
|
||||
#define ibv_dealloc_pd rdma_syms.ibv_dealloc_pd
|
||||
#define ibv_create_comp_channel rdma_syms.ibv_create_comp_channel
|
||||
#define ibv_destroy_comp_channel rdma_syms.ibv_destroy_comp_channel
|
||||
#define ibv_create_cq rdma_syms.ibv_create_cq
|
||||
#define ibv_destroy_cq rdma_syms.ibv_destroy_cq
|
||||
#define ibv_reg_mr rdma_syms.ibv_reg_mr
|
||||
#define ibv_dereg_mr rdma_syms.ibv_dereg_mr
|
||||
#define ibv_get_cq_event rdma_syms.ibv_get_cq_event
|
||||
#define ibv_ack_cq_events rdma_syms.ibv_ack_cq_events
|
||||
#define ibv_destroy_qp rdma_syms.ibv_destroy_qp
|
||||
|
||||
#endif /* DLOPEN_RDMA */
|
||||
|
||||
static valkeyContextFuncs valkeyContextRdmaFuncs;
|
||||
|
||||
typedef struct valkeyRdmaFeature {
|
||||
@@ -129,6 +282,9 @@ typedef struct RdmaContext {
|
||||
struct ibv_mr *cmd_mr;
|
||||
} RdmaContext;
|
||||
|
||||
/* Apparently CHERI uintptr_t can be 128 bits */
|
||||
vk_static_assert(sizeof(uintptr_t) <= sizeof(uint64_t));
|
||||
|
||||
static int valkeyRdmaCM(valkeyContext *c, long timeout);
|
||||
|
||||
static int valkeyRdmaSetFdBlocking(valkeyContext *c, int fd, int blocking) {
|
||||
@@ -161,7 +317,7 @@ static int rdmaPostRecv(RdmaContext *ctx, struct rdma_cm_id *cm_id, valkeyRdmaCm
|
||||
sge.length = length;
|
||||
sge.lkey = ctx->cmd_mr->lkey;
|
||||
|
||||
recv_wr.wr_id = (uint64_t)cmd;
|
||||
recv_wr.wr_id = (uint64_t)(uintptr_t)cmd;
|
||||
recv_wr.sg_list = &sge;
|
||||
recv_wr.num_sge = 1;
|
||||
recv_wr.next = NULL;
|
||||
@@ -302,7 +458,7 @@ static int rdmaSendCommand(valkeyContext *c, struct rdma_cm_id *cm_id, valkeyRdm
|
||||
|
||||
send_wr.sg_list = &sge;
|
||||
send_wr.num_sge = 1;
|
||||
send_wr.wr_id = (uint64_t)_cmd;
|
||||
send_wr.wr_id = (uint64_t)(uintptr_t)_cmd;
|
||||
send_wr.opcode = IBV_WR_SEND;
|
||||
send_wr.send_flags = IBV_SEND_SIGNALED;
|
||||
send_wr.next = NULL;
|
||||
@@ -320,7 +476,7 @@ static int connRdmaRegisterRx(valkeyContext *c, struct rdma_cm_id *cm_id) {
|
||||
valkeyRdmaCmd cmd = {0};
|
||||
|
||||
cmd.memory.opcode = htons(RegisterXferMemory);
|
||||
cmd.memory.addr = htobe64((uint64_t)ctx->recv_buf);
|
||||
cmd.memory.addr = htobe64((uint64_t)(uintptr_t)ctx->recv_buf);
|
||||
cmd.memory.length = htonl(ctx->recv_length);
|
||||
cmd.memory.key = htonl(ctx->recv_mr->rkey);
|
||||
|
||||
@@ -338,7 +494,7 @@ static int connRdmaHandleRecv(valkeyContext *c, RdmaContext *ctx, struct rdma_cm
|
||||
|
||||
switch (ntohs(cmd->keepalive.opcode)) {
|
||||
case RegisterXferMemory:
|
||||
ctx->tx_addr = (char *)be64toh(cmd->memory.addr);
|
||||
ctx->tx_addr = (char *)(uintptr_t)be64toh(cmd->memory.addr);
|
||||
ctx->tx_length = ntohl(cmd->memory.length);
|
||||
ctx->tx_key = ntohl(cmd->memory.key);
|
||||
ctx->tx_offset = 0;
|
||||
@@ -501,11 +657,10 @@ static int valkeyRdmaPollCqCm(valkeyContext *c, long timed) {
|
||||
return VALKEY_OK;
|
||||
}
|
||||
|
||||
static ssize_t valkeyRdmaRead(valkeyContext *c, char *buf, size_t bufcap) {
|
||||
static ssize_t valkeyRdmaReadZC(valkeyContext *c, char **buf) {
|
||||
RdmaContext *ctx = c->privctx;
|
||||
struct rdma_cm_id *cm_id = ctx->cm_id;
|
||||
long timed, end;
|
||||
uint32_t toread, remained;
|
||||
uint32_t remained;
|
||||
|
||||
if (valkeyCommandTimeoutMsec(c, &timed)) {
|
||||
return VALKEY_ERR;
|
||||
@@ -521,16 +676,9 @@ pollcq:
|
||||
|
||||
if (ctx->recv_offset < ctx->rx_offset) {
|
||||
remained = ctx->rx_offset - ctx->recv_offset;
|
||||
toread = valkeyMin(remained, bufcap);
|
||||
|
||||
memcpy(buf, ctx->recv_buf + ctx->recv_offset, toread);
|
||||
ctx->recv_offset += toread;
|
||||
|
||||
if (ctx->recv_offset == ctx->recv_length) {
|
||||
connRdmaRegisterRx(c, cm_id);
|
||||
}
|
||||
|
||||
return toread;
|
||||
*buf = ctx->recv_buf + ctx->recv_offset;
|
||||
ctx->recv_offset = ctx->rx_offset;
|
||||
return remained;
|
||||
}
|
||||
|
||||
if (valkeyRdmaPollCqCm(c, end) == VALKEY_OK) {
|
||||
@@ -540,6 +688,16 @@ pollcq:
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t valkeyRdmaReadZCDone(valkeyContext *c) {
|
||||
RdmaContext *ctx = c->privctx;
|
||||
struct rdma_cm_id *cm_id = ctx->cm_id;
|
||||
|
||||
if (ctx->recv_offset == ctx->recv_length) {
|
||||
return connRdmaRegisterRx(c, cm_id);
|
||||
}
|
||||
return VALKEY_OK;
|
||||
}
|
||||
|
||||
static size_t connRdmaSend(RdmaContext *ctx, struct rdma_cm_id *cm_id, const void *data, size_t data_len) {
|
||||
struct ibv_send_wr send_wr, *bad_wr;
|
||||
struct ibv_sge sge;
|
||||
@@ -990,11 +1148,17 @@ static valkeyContextFuncs valkeyContextRdmaFuncs = {
|
||||
.free_privctx = valkeyRdmaFree,
|
||||
.async_read = valkeyRdmaAsyncRead,
|
||||
.async_write = valkeyRdmaAsyncWrite,
|
||||
.read = valkeyRdmaRead,
|
||||
.read_zc = valkeyRdmaReadZC,
|
||||
.read_zc_done = valkeyRdmaReadZCDone,
|
||||
.write = valkeyRdmaWrite,
|
||||
.set_timeout = valkeyRdmaSetTimeout};
|
||||
|
||||
int valkeyInitiateRdma(void) {
|
||||
#ifdef DLOPEN_RDMA
|
||||
if (rdma_dyn_load_libs() != 0) {
|
||||
return VALKEY_ERR;
|
||||
}
|
||||
#endif
|
||||
valkeyContextRegisterFuncs(&valkeyContextRdmaFuncs, VALKEY_CONN_RDMA);
|
||||
|
||||
return VALKEY_OK;
|
||||
|
||||
Vendored
+21
-14
@@ -453,7 +453,7 @@ static uint32_t digits10(uint64_t v) {
|
||||
return 2;
|
||||
if (v < 1000)
|
||||
return 3;
|
||||
if (v < 1000000000000UL) {
|
||||
if (v < 1000000000000ULL) {
|
||||
if (v < 100000000UL) {
|
||||
if (v < 1000000) {
|
||||
if (v < 10000)
|
||||
@@ -462,12 +462,12 @@ static uint32_t digits10(uint64_t v) {
|
||||
}
|
||||
return 7 + (v >= 10000000UL);
|
||||
}
|
||||
if (v < 10000000000UL) {
|
||||
if (v < 10000000000ULL) {
|
||||
return 9 + (v >= 1000000000UL);
|
||||
}
|
||||
return 11 + (v >= 100000000000UL);
|
||||
return 11 + (v >= 100000000000ULL);
|
||||
}
|
||||
return 12 + digits10(v / 1000000000000UL);
|
||||
return 12 + digits10(v / 1000000000000ULL);
|
||||
}
|
||||
|
||||
/* Convert a unsigned long long into a string. Returns the number of
|
||||
@@ -487,13 +487,16 @@ static int ull2string(char *dst, size_t dstlen, unsigned long long value) {
|
||||
|
||||
/* Check length. */
|
||||
uint32_t length = digits10(value);
|
||||
if (length >= dstlen)
|
||||
if (length == 0 || length >= dstlen)
|
||||
goto err;
|
||||
|
||||
/* Null term. */
|
||||
uint32_t next = length - 1;
|
||||
int next = length - 1;
|
||||
dst[next + 1] = '\0';
|
||||
while (value >= 100) {
|
||||
while (next >= 1) {
|
||||
/* value should not be 0 while next >= 1 */
|
||||
if (value == 0)
|
||||
goto err;
|
||||
int const i = (value % 100) * 2;
|
||||
value /= 100;
|
||||
dst[next] = digits[i + 1];
|
||||
@@ -501,14 +504,18 @@ static int ull2string(char *dst, size_t dstlen, unsigned long long value) {
|
||||
next -= 2;
|
||||
}
|
||||
|
||||
/* Handle last 1-2 digits. */
|
||||
if (value < 10) {
|
||||
/* still have digits to process, but out of room */
|
||||
if (value > 0 && next < 0)
|
||||
goto err;
|
||||
|
||||
/* even number of digits should have been processed in the loop */
|
||||
if (value >= 10)
|
||||
goto err;
|
||||
|
||||
/* if next is 0, process last digit */
|
||||
if (next == 0)
|
||||
dst[next] = '0' + (uint32_t)value;
|
||||
} else {
|
||||
int i = (uint32_t)value * 2;
|
||||
dst[next] = digits[i + 1];
|
||||
dst[next - 1] = digits[i];
|
||||
}
|
||||
|
||||
return length;
|
||||
err:
|
||||
/* force add Null termination */
|
||||
|
||||
Vendored
+12
@@ -1009,6 +1009,18 @@ int valkeyBufferRead(valkeyContext *c) {
|
||||
if (c->err)
|
||||
return VALKEY_ERR;
|
||||
|
||||
if (c->funcs->read_zc) {
|
||||
char *zc_buf;
|
||||
nread = c->funcs->read_zc(c, &zc_buf);
|
||||
if (nread < 0) {
|
||||
return VALKEY_ERR;
|
||||
}
|
||||
if (nread > 0 && valkeyReaderFeed(c->reader, zc_buf, nread) != VALKEY_OK) {
|
||||
valkeySetError(c, c->reader->err, c->reader->errstr);
|
||||
return VALKEY_ERR;
|
||||
}
|
||||
return c->funcs->read_zc_done(c);
|
||||
}
|
||||
nread = c->funcs->read(c, buf, sizeof(buf));
|
||||
if (nread < 0) {
|
||||
return VALKEY_ERR;
|
||||
|
||||
Vendored
+2
-1
@@ -124,7 +124,8 @@ static inline int valkeyContextUpdateCommandTimeout(valkeyContext *c,
|
||||
return VALKEY_OK;
|
||||
}
|
||||
|
||||
int valkeyContextRegisterFuncs(valkeyContextFuncs *funcs, enum valkeyConnectionType type);
|
||||
/* Visible although private since required by libvalkey_rdma.so */
|
||||
LIBVALKEY_API int valkeyContextRegisterFuncs(valkeyContextFuncs *funcs, enum valkeyConnectionType type);
|
||||
void valkeyContextRegisterTcpFuncs(void);
|
||||
void valkeyContextRegisterUnixFuncs(void);
|
||||
void valkeyContextRegisterUserfdFuncs(void);
|
||||
|
||||
Vendored
-4
@@ -46,10 +46,6 @@
|
||||
#define strncasecmp _strnicmp
|
||||
#endif
|
||||
|
||||
#ifndef alloca
|
||||
#define alloca _alloca
|
||||
#endif
|
||||
|
||||
#ifndef va_copy
|
||||
#define va_copy(d, s) ((d) = (s))
|
||||
#endif
|
||||
|
||||
Vendored
+7
-18
@@ -63,39 +63,28 @@ else()
|
||||
add_compile_options("-UNDEBUG")
|
||||
endif()
|
||||
|
||||
# Make sure ctest gives the output when tests fail
|
||||
list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure")
|
||||
|
||||
# Add non-cluster tests
|
||||
# Add the non-cluster test binary.
|
||||
# Some tests are unit tests which require us to include private headers.
|
||||
add_executable(client_test client_test.c)
|
||||
target_include_directories(client_test PRIVATE "${PROJECT_SOURCE_DIR}/src")
|
||||
target_link_libraries(client_test valkey_unittest)
|
||||
if(TLS_LIBRARY)
|
||||
add_test(NAME client_test COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/test.sh")
|
||||
if(ENABLE_TLS)
|
||||
target_compile_definitions(client_test PUBLIC VALKEY_TEST_TLS=1)
|
||||
target_link_libraries(client_test ${TLS_LIBRARY})
|
||||
set_property(TEST client_test APPEND PROPERTY ENVIRONMENT "TEST_TLS=1")
|
||||
endif()
|
||||
if(ENABLE_RDMA)
|
||||
target_compile_definitions(client_test PUBLIC VALKEY_TEST_RDMA=1)
|
||||
target_link_libraries(client_test valkey_rdma)
|
||||
set_property(TEST client_test APPEND PROPERTY ENVIRONMENT "TEST_RDMA=1")
|
||||
endif()
|
||||
if(LIBEVENT_LIBRARY)
|
||||
target_compile_definitions(client_test PUBLIC VALKEY_TEST_ASYNC=1)
|
||||
target_link_libraries(client_test ${LIBEVENT_LIBRARY})
|
||||
set_property(TEST client_test APPEND PROPERTY ENVIRONMENT "TEST_CLUSTER=1")
|
||||
endif()
|
||||
|
||||
add_test(NAME client_test COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/test.sh")
|
||||
if(TEST_WITH_REDIS_VERSION)
|
||||
set_property(TEST client_test APPEND PROPERTY ENVIRONMENT "VALKEY_SERVER=redis-server")
|
||||
endif()
|
||||
if(TLS_LIBRARY)
|
||||
set_property(TEST client_test APPEND PROPERTY ENVIRONMENT "TEST_TLS=1")
|
||||
endif()
|
||||
if(ENABLE_RDMA)
|
||||
set_property(TEST client_test APPEND PROPERTY ENVIRONMENT "TEST_RDMA=1")
|
||||
endif()
|
||||
if(LIBEVENT_LIBRARY)
|
||||
set_property(TEST client_test APPEND PROPERTY ENVIRONMENT "TEST_CLUSTER=1")
|
||||
endif()
|
||||
|
||||
# Unit tests
|
||||
add_executable(ut_parse_cmd ut_parse_cmd.c test_utils.c)
|
||||
|
||||
Vendored
+48
@@ -1884,6 +1884,53 @@ void null_cb(valkeyAsyncContext *ac, void *r, void *privdata) {
|
||||
state->checkpoint++;
|
||||
}
|
||||
|
||||
/* Test the command parsing, required for pub/sub in the async API. */
|
||||
void test_async_command_parsing(struct config config) {
|
||||
test("Async command parsing: ");
|
||||
valkeyOptions options = get_server_tcp_options(config);
|
||||
valkeyAsyncContext *ac = valkeyAsyncConnectWithOptions(&options);
|
||||
assert(ac);
|
||||
|
||||
/* Null ptr. */
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, NULL, 45));
|
||||
/* Empty command. */
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "", 0));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, " $", 2));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*0\r\n", 4));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$-1\r\n", 9));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$-1\r\nUNSUBSCRIBE\r\n", 22));
|
||||
/* Protocol error: erroneous bulkstring length and data. */
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$100000", 11));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$100000\r", 12));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$100000\r\n", 13));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$10HELP\r\n\r\n", 15));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$100000\r\nTO-SHORT\r\n", 23));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$1\r\nTO-LONG\r\n", 17));
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$123456789\r\n", 11));
|
||||
|
||||
/* Faulty length given to function. */
|
||||
for (int i = 0; i < 19; i++) {
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*2\r\n$9\r\nSUBSCRIBE\r\n$7\r\nCHANNEL\r\n", i));
|
||||
}
|
||||
for (int i = 0; i < 21; i++) {
|
||||
assert(VALKEY_ERR == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$11\r\nUNSUBSCRIBE\r\n", i));
|
||||
}
|
||||
|
||||
/* Complete command. */
|
||||
assert(VALKEY_OK == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*2\r\n$9\r\nSUBSCRIBE\r\n$7\r\nCHANNEL\r\n", 32));
|
||||
assert(VALKEY_OK == valkeyAsyncFormattedCommand(ac, NULL, NULL, "*1\r\n$11\r\nUNSUBSCRIBE\r\n", 22));
|
||||
|
||||
// Heap allocate command without NULL terminator.
|
||||
const char ping[] = "*1\r\n$4\r\nPING\r\n";
|
||||
size_t len = sizeof(ping) - 1;
|
||||
char *buf = vk_malloc_safe(len);
|
||||
memcpy(buf, ping, len);
|
||||
assert(VALKEY_OK == valkeyAsyncFormattedCommand(ac, NULL, NULL, buf, len));
|
||||
free(buf);
|
||||
|
||||
valkeyAsyncFree(ac);
|
||||
}
|
||||
|
||||
static void test_pubsub_handling(struct config config) {
|
||||
test("Subscribe, handle published message and unsubscribe: ");
|
||||
/* Setup event dispatcher with a testcase timeout */
|
||||
@@ -2817,6 +2864,7 @@ int main(int argc, char **argv) {
|
||||
get_server_version(c, &major, NULL);
|
||||
disconnect(c, 0);
|
||||
|
||||
test_async_command_parsing(cfg);
|
||||
test_pubsub_handling(cfg);
|
||||
test_pubsub_multiple_channels(cfg);
|
||||
test_monitor(cfg);
|
||||
|
||||
Vendored
+26
-2
@@ -61,18 +61,36 @@ void eventCallback(const valkeyClusterContext *cc, int event, void *privdata) {
|
||||
printf("Event: %s\n", e);
|
||||
}
|
||||
|
||||
void connectCallback(const valkeyContext *c, int status) {
|
||||
const char *s = "";
|
||||
if (status != VALKEY_OK)
|
||||
s = "failed to ";
|
||||
printf("Event: %sconnect to %s:%d\n", s, c->tcp.host, c->tcp.port);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int show_events = 0;
|
||||
int use_cluster_nodes = 0;
|
||||
int send_to_all = 0;
|
||||
int show_connection_events = 0;
|
||||
int select_db = 0;
|
||||
|
||||
int argindex;
|
||||
for (argindex = 1; argindex < argc && argv[argindex][0] == '-';
|
||||
argindex++) {
|
||||
if (strcmp(argv[argindex], "--events") == 0) {
|
||||
show_events = 1;
|
||||
} else if (strcmp(argv[argindex], "--connection-events") == 0) {
|
||||
show_connection_events = 1;
|
||||
} else if (strcmp(argv[argindex], "--use-cluster-nodes") == 0) {
|
||||
use_cluster_nodes = 1;
|
||||
} else if (strcmp(argv[argindex], "--select-db") == 0) {
|
||||
if (++argindex < argc) /* Need an additional argument */
|
||||
select_db = atoi(argv[argindex]);
|
||||
if (select_db == 0) {
|
||||
fprintf(stderr, "Missing or faulty argument for --select-db\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Unknown argument: '%s'\n", argv[argindex]);
|
||||
exit(1);
|
||||
@@ -80,8 +98,8 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
if (argindex >= argc) {
|
||||
fprintf(stderr, "Usage: clusterclient [--events] [--use-cluster-nodes] "
|
||||
"HOST:PORT\n");
|
||||
fprintf(stderr, "Usage: clusterclient [--events] [--connection-events] "
|
||||
"[--use-cluster-nodes] [--select-db NUM] HOST:PORT\n");
|
||||
exit(1);
|
||||
}
|
||||
const char *initnode = argv[argindex];
|
||||
@@ -97,6 +115,12 @@ int main(int argc, char **argv) {
|
||||
if (show_events) {
|
||||
options.event_callback = eventCallback;
|
||||
}
|
||||
if (show_connection_events) {
|
||||
options.connect_callback = connectCallback;
|
||||
}
|
||||
if (select_db > 0) {
|
||||
options.select_db = select_db;
|
||||
}
|
||||
|
||||
valkeyClusterContext *cc = valkeyClusterConnectWithOptions(&options);
|
||||
if (cc == NULL || cc->err) {
|
||||
|
||||
+13
-1
@@ -242,6 +242,7 @@ void disconnectCallback(const valkeyAsyncContext *ac, int status) {
|
||||
int main(int argc, char **argv) {
|
||||
int use_cluster_nodes = 0;
|
||||
int show_connection_events = 0;
|
||||
int select_db = 0;
|
||||
|
||||
int optind;
|
||||
for (optind = 1; optind < argc && argv[optind][0] == '-'; optind++) {
|
||||
@@ -253,6 +254,13 @@ int main(int argc, char **argv) {
|
||||
show_connection_events = 1;
|
||||
} else if (strcmp(argv[optind], "--blocking-initial-update") == 0) {
|
||||
blocking_initial_update = 1;
|
||||
} else if (strcmp(argv[optind], "--select-db") == 0) {
|
||||
if (++optind < argc) /* Need an additional argument */
|
||||
select_db = atoi(argv[optind]);
|
||||
if (select_db == 0) {
|
||||
fprintf(stderr, "Missing or faulty argument for --select-db\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Unknown argument: '%s'\n", argv[optind]);
|
||||
}
|
||||
@@ -260,7 +268,8 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (optind >= argc) {
|
||||
fprintf(stderr,
|
||||
"Usage: clusterclient_async [--use-cluster-nodes] HOST:PORT\n");
|
||||
"Usage: clusterclient_async [--events] [--connection-events] "
|
||||
"[--use-cluster-nodes] [--select-db NUM] HOST:PORT\n");
|
||||
exit(1);
|
||||
}
|
||||
const char *initnode = argv[optind];
|
||||
@@ -283,6 +292,9 @@ int main(int argc, char **argv) {
|
||||
options.async_connect_callback = connectCallback;
|
||||
options.async_disconnect_callback = disconnectCallback;
|
||||
}
|
||||
if (select_db > 0) {
|
||||
options.select_db = select_db;
|
||||
}
|
||||
valkeyClusterOptionsUseLibevent(&options, base);
|
||||
|
||||
valkeyClusterAsyncContext *acc = valkeyClusterAsyncConnectWithOptions(&options);
|
||||
|
||||
+5
-1
@@ -16,11 +16,15 @@ syncpid1=$!;
|
||||
# Start simulated valkey node
|
||||
timeout 5s ./simulated-valkey.pl -p 7401 -d --sigcont $syncpid1 <<'EOF' &
|
||||
EXPECT CONNECT
|
||||
EXPECT ["SELECT", "2"]
|
||||
SEND +OK
|
||||
EXPECT ["CLUSTER", "SLOTS"]
|
||||
SEND [[0, 16383, ["127.0.0.1", 7401, "nodeid1"]]]
|
||||
EXPECT CLOSE
|
||||
|
||||
EXPECT CONNECT
|
||||
EXPECT ["SELECT", "2"]
|
||||
SEND +OK
|
||||
EXPECT ["SET", "foo", "initial"]
|
||||
SEND +OK
|
||||
|
||||
@@ -35,7 +39,7 @@ server1=$!
|
||||
wait $syncpid1;
|
||||
|
||||
# Run client
|
||||
timeout 4s "$clientprog" --blocking-initial-update --connection-events 127.0.0.1:7401 > "$testname.out" <<'EOF'
|
||||
timeout 4s "$clientprog" --blocking-initial-update --connection-events --select-db 2 127.0.0.1:7401 > "$testname.out" <<'EOF'
|
||||
SET foo initial
|
||||
|
||||
# Send a command that is expected to be redirected just before
|
||||
|
||||
+5
-2
@@ -13,6 +13,8 @@ syncpid1=$!;
|
||||
timeout 5s ./simulated-valkey.pl -p 7401 -d --sigcont $syncpid1 <<'EOF' &
|
||||
# The initial slotmap is not covering all slots.
|
||||
EXPECT CONNECT
|
||||
EXPECT ["SELECT", "5"]
|
||||
SEND +OK
|
||||
EXPECT ["CLUSTER", "SLOTS"]
|
||||
SEND [[0, 1, ["127.0.0.1", 7401, "nodeid7401"]]]
|
||||
|
||||
@@ -36,7 +38,7 @@ server1=$!
|
||||
wait $syncpid1;
|
||||
|
||||
# Run client
|
||||
timeout 3s "$clientprog" --events 127.0.0.1:7401 > "$testname.out" <<'EOF'
|
||||
timeout 3s "$clientprog" --events --connection-events --select-db 5 127.0.0.1:7401 > "$testname.out" <<'EOF'
|
||||
GET foo1
|
||||
GET foo2
|
||||
EOF
|
||||
@@ -56,7 +58,8 @@ if [ $clientexit -ne 0 ]; then
|
||||
fi
|
||||
|
||||
# Check the output from clusterclient
|
||||
expected="Event: slotmap-updated
|
||||
expected="Event: connect to 127.0.0.1:7401
|
||||
Event: slotmap-updated
|
||||
Event: ready
|
||||
Event: slotmap-updated
|
||||
error: slot not served by any node
|
||||
|
||||
Vendored
+2
-2
@@ -29,10 +29,10 @@
|
||||
}
|
||||
|
||||
#define CHECK_REPLY_OK(_ctx, _reply) \
|
||||
{ CHECK_REPLY_STATUS(_ctx, _reply, "OK") }
|
||||
{CHECK_REPLY_STATUS(_ctx, _reply, "OK")}
|
||||
|
||||
#define CHECK_REPLY_QUEUED(_ctx, _reply) \
|
||||
{ CHECK_REPLY_STATUS(_ctx, _reply, "QUEUED") }
|
||||
{CHECK_REPLY_STATUS(_ctx, _reply, "QUEUED")}
|
||||
|
||||
#define CHECK_REPLY_INT(_ctx, _reply, _value) \
|
||||
{ \
|
||||
|
||||
Vendored
+72
@@ -47,6 +47,48 @@ void test_valkey_parse_error_nonresp(void) {
|
||||
command_destroy(c);
|
||||
}
|
||||
|
||||
/* Create a 65 bytes command string while limit is 64 bytes */
|
||||
void test_valkey_parse_too_long_cmd(void) {
|
||||
char str[66];
|
||||
memset(str, 'A', 65);
|
||||
str[65] = '\0';
|
||||
struct cmd *c = command_get();
|
||||
int len = valkeyFormatCommand(&c->cmd, str);
|
||||
ASSERT_MSG(len >= 0, "Format command error");
|
||||
c->clen = len;
|
||||
valkey_parse_cmd(c);
|
||||
ASSERT_MSG(c->result == CMD_PARSE_ERROR, "Unexpected parse success");
|
||||
ASSERT_MSG(!strncmp(c->errstr, "Unknown command AAAA", 20), c->errstr);
|
||||
command_destroy(c);
|
||||
}
|
||||
|
||||
/* Parse a subcommand longer than the 64 char limit */
|
||||
void test_valkey_parse_too_long_subcommand(void) {
|
||||
char subcmd[66];
|
||||
memset(subcmd, 'A', 65);
|
||||
subcmd[65] = '\0';
|
||||
|
||||
struct cmd *c = command_get();
|
||||
int len = valkeyFormatCommand(&c->cmd, "XGROUP %s", subcmd);
|
||||
ASSERT_MSG(len >= 0, "Format command error");
|
||||
c->clen = len;
|
||||
valkey_parse_cmd(c);
|
||||
ASSERT_MSG(c->result == CMD_PARSE_ERROR, "Unexpected parse success");
|
||||
ASSERT_MSG(!strncmp(c->errstr, "Unknown command XGROUP AAAAAAA", 30), c->errstr);
|
||||
command_destroy(c);
|
||||
}
|
||||
|
||||
void test_valkey_parse_unknown_cmd(void) {
|
||||
struct cmd *c = command_get();
|
||||
int len = valkeyFormatCommand(&c->cmd, "OIOIOI");
|
||||
ASSERT_MSG(len >= 0, "Format command error");
|
||||
c->clen = len;
|
||||
valkey_parse_cmd(c);
|
||||
ASSERT_MSG(c->result == CMD_PARSE_ERROR, "Unexpected parse success");
|
||||
ASSERT_MSG(!strcmp(c->errstr, "Unknown command OIOIOI"), c->errstr);
|
||||
command_destroy(c);
|
||||
}
|
||||
|
||||
void test_valkey_parse_cmd_get(void) {
|
||||
struct cmd *c = command_get();
|
||||
int len = valkeyFormatCommand(&c->cmd, "GET foo");
|
||||
@@ -102,6 +144,17 @@ void test_valkey_parse_cmd_xgroup_no_subcommand(void) {
|
||||
command_destroy(c);
|
||||
}
|
||||
|
||||
void test_valkey_parse_cmd_xgroup_unknown_subcommand(void) {
|
||||
struct cmd *c = command_get();
|
||||
int len = valkeyFormatCommand(&c->cmd, "XGROUP OIOIOI");
|
||||
ASSERT_MSG(len >= 0, "Format command error");
|
||||
c->clen = len;
|
||||
valkey_parse_cmd(c);
|
||||
ASSERT_MSG(c->result == CMD_PARSE_ERROR, "Unexpected parse success");
|
||||
ASSERT_MSG(!strcmp(c->errstr, "Unknown command XGROUP OIOIOI"), c->errstr);
|
||||
command_destroy(c);
|
||||
}
|
||||
|
||||
void test_valkey_parse_cmd_xgroup_destroy_no_key(void) {
|
||||
struct cmd *c = command_get();
|
||||
int len = valkeyFormatCommand(&c->cmd, "xgroup destroy");
|
||||
@@ -186,13 +239,31 @@ void test_valkey_parse_cmd_georadius_ro_ok(void) {
|
||||
command_destroy(c);
|
||||
}
|
||||
|
||||
void test_valkey_parse_cmd_sadd_ok(void) {
|
||||
char key[201];
|
||||
memset(key, 'A', 200);
|
||||
key[200] = '\0';
|
||||
|
||||
struct cmd *c = command_get();
|
||||
int len = valkeyFormatCommand(&c->cmd, "SADD %s value", key);
|
||||
ASSERT_MSG(len >= 0, "Format command error");
|
||||
c->clen = len;
|
||||
valkey_parse_cmd(c);
|
||||
ASSERT_KEY(c, key);
|
||||
command_destroy(c);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_valkey_parse_error_nonresp();
|
||||
test_valkey_parse_too_long_cmd();
|
||||
test_valkey_parse_too_long_subcommand();
|
||||
test_valkey_parse_unknown_cmd();
|
||||
test_valkey_parse_cmd_get();
|
||||
test_valkey_parse_cmd_mset();
|
||||
test_valkey_parse_cmd_eval_1();
|
||||
test_valkey_parse_cmd_eval_0();
|
||||
test_valkey_parse_cmd_xgroup_no_subcommand();
|
||||
test_valkey_parse_cmd_xgroup_unknown_subcommand();
|
||||
test_valkey_parse_cmd_xgroup_destroy_no_key();
|
||||
test_valkey_parse_cmd_xgroup_destroy_ok();
|
||||
test_valkey_parse_cmd_xreadgroup_ok();
|
||||
@@ -200,5 +271,6 @@ int main(void) {
|
||||
test_valkey_parse_cmd_restore_ok();
|
||||
test_valkey_parse_cmd_restore_asking_ok();
|
||||
test_valkey_parse_cmd_georadius_ro_ok();
|
||||
test_valkey_parse_cmd_sadd_ok();
|
||||
return 0;
|
||||
}
|
||||
|
||||
+47
@@ -37,6 +37,15 @@ valkeyReply *create_cluster_nodes_reply(const char *str) {
|
||||
return create_reply(buf, len);
|
||||
}
|
||||
|
||||
/* Helper to create a valkeyReply that contains a verbatim string. */
|
||||
valkeyReply *create_cluster_nodes_reply_resp3(const char *str) {
|
||||
char buf[1024];
|
||||
const char *encoding = "txt:";
|
||||
int len = sprintf(buf, "=%zu\r\n%s%s\r\n", strlen(encoding) + strlen(str), encoding, str);
|
||||
|
||||
return create_reply(buf, len);
|
||||
}
|
||||
|
||||
/* Helper to create a cluster slots response.
|
||||
* Parses the string using a rudimentary JSON like format which accepts:
|
||||
* - arrays example: [elem1, elem2]
|
||||
@@ -526,6 +535,43 @@ void test_parse_cluster_nodes_with_legacy_format(void) {
|
||||
valkeyClusterFree(cc);
|
||||
}
|
||||
|
||||
/* Parse a cluster nodes reply when RESP3 is used,
|
||||
* i.e. the reply is a verbatim string. */
|
||||
void test_parse_cluster_nodes_with_resp3(void) {
|
||||
valkeyClusterOptions options = {0};
|
||||
valkeyClusterContext *cc = createClusterContext(&options);
|
||||
valkeyContext *c = valkeyContextInit();
|
||||
valkeyClusterNode *node;
|
||||
dictIterator di;
|
||||
|
||||
valkeyReply *reply = create_cluster_nodes_reply_resp3(
|
||||
"67ed2db8d677e59ec4a4cefb06858cf2a1a89fa1 127.0.0.1:30002@31002,hostname2 master - 0 1426238316232 2 connected 5461-10922\n"
|
||||
"292f8b365bb7edb5e285caf0b7e6ddc7265d2f4f 127.0.0.1:30003@31003,hostname3 master - 0 1426238318243 3 connected 10923-16383\n"
|
||||
"e7d1eecce10fd6bb5eb35b9f99a514335d9ba9ca 127.0.0.1:30001@31001,hostname1 myself,master - 0 0 1 connected 0-5460\n");
|
||||
dict *nodes = parse_cluster_nodes(cc, c, reply);
|
||||
freeReplyObject(reply);
|
||||
|
||||
assert(nodes);
|
||||
assert(dictSize(nodes) == 3); /* 3 primaries */
|
||||
dictInitIterator(&di, nodes);
|
||||
/* Verify node 1 */
|
||||
node = dictGetVal(dictNext(&di));
|
||||
assert(strcmp(node->name, "e7d1eecce10fd6bb5eb35b9f99a514335d9ba9ca") == 0);
|
||||
assert(strcmp(node->addr, "127.0.0.1:30001") == 0);
|
||||
/* Verify node 2 */
|
||||
node = dictGetVal(dictNext(&di));
|
||||
assert(strcmp(node->name, "67ed2db8d677e59ec4a4cefb06858cf2a1a89fa1") == 0);
|
||||
assert(strcmp(node->addr, "127.0.0.1:30002") == 0);
|
||||
/* Verify node 3 */
|
||||
node = dictGetVal(dictNext(&di));
|
||||
assert(strcmp(node->name, "292f8b365bb7edb5e285caf0b7e6ddc7265d2f4f") == 0);
|
||||
assert(strcmp(node->addr, "127.0.0.1:30003") == 0);
|
||||
|
||||
dictRelease(nodes);
|
||||
valkeyFree(c);
|
||||
valkeyClusterFree(cc);
|
||||
}
|
||||
|
||||
/* Parse a cluster slots reply from a basic deployment. */
|
||||
void test_parse_cluster_slots(bool parse_replicas) {
|
||||
valkeyClusterOptions options = {0};
|
||||
@@ -810,6 +856,7 @@ int main(void) {
|
||||
test_parse_cluster_nodes_with_multiple_replicas();
|
||||
test_parse_cluster_nodes_with_parse_error();
|
||||
test_parse_cluster_nodes_with_legacy_format();
|
||||
test_parse_cluster_nodes_with_resp3();
|
||||
|
||||
test_parse_cluster_slots(false /* replicas not parsed */);
|
||||
test_parse_cluster_slots(true /* replicas parsed */);
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
STD=
|
||||
WARN= -Wall
|
||||
WARN= -Wall -Werror
|
||||
OPT= -Os
|
||||
|
||||
R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS)
|
||||
|
||||
Vendored
+5
-3
@@ -839,6 +839,9 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
|
||||
* when ctrl+d is typed.
|
||||
*
|
||||
* The function returns the length of the current buffer. */
|
||||
/* Max length for CSI escape sequence parameter buffer */
|
||||
#define SEQ_BUFFER_MAX_LENGTH 8
|
||||
|
||||
static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
|
||||
{
|
||||
struct linenoiseState l;
|
||||
@@ -964,14 +967,13 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
if (seq[1] >= '0' && seq[1] <= '9') {
|
||||
/* Extended escape, read additional bytes.
|
||||
* Examples: ESC [1;5C ESC [3~ */
|
||||
const int seqBufferMaxLength = 8;
|
||||
char seqBuffer[seqBufferMaxLength];
|
||||
char seqBuffer[SEQ_BUFFER_MAX_LENGTH];
|
||||
int i = 0;
|
||||
seqBuffer[i++] = seq[1];
|
||||
|
||||
/* If first param is digit or ';', read more until we see a final in @~ */
|
||||
char additionalChar;
|
||||
while (i < seqBufferMaxLength-1 && read(l.ifd, &additionalChar, 1) != -1) {
|
||||
while (i < SEQ_BUFFER_MAX_LENGTH-1 && read(l.ifd, &additionalChar, 1) != -1) {
|
||||
seqBuffer[i++] = additionalChar;
|
||||
if (additionalChar >= '@' && additionalChar <= '~') { /* CSI final byte */
|
||||
seqBuffer[i] = '\0';
|
||||
|
||||
Vendored
+22
-4
@@ -18,10 +18,28 @@ MYCFLAGS=
|
||||
MYLDFLAGS=
|
||||
MYLIBS=
|
||||
|
||||
# Pretty-printing setup
|
||||
CC_COLOR="\033[32m"
|
||||
LNK_COLOR="\033[36;1m"
|
||||
SCOPE_COLOR="\033[38:5:250m"
|
||||
SRC_COLOR="\033[33m"
|
||||
BIN_COLOR="\033[35m"
|
||||
RESET="\033[0m"
|
||||
|
||||
ifndef V
|
||||
QUIET_CC = @printf ' %b %b(LUA Lib)%b %b\n' $(CC_COLOR)CC$(RESET) $(SCOPE_COLOR) $(RESET) $(SRC_COLOR)$<$(RESET) 1>&2;
|
||||
QUIET_LINK = @printf ' %b %b\n' $(LNK_COLOR)LINK$(RESET) $(BIN_COLOR)$@$(RESET) 1>&2;
|
||||
QUIET_AR = @printf ' %b %b\n' $(CC_COLOR)ARCHIVE$(RESET) $(BIN_COLOR)$@$(RESET) 1>&2;
|
||||
endif
|
||||
|
||||
# == END OF USER SETTINGS. NO NEED TO CHANGE ANYTHING BELOW THIS LINE =========
|
||||
|
||||
PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris
|
||||
|
||||
# Explicit pattern rule for building object files from C sources
|
||||
%.o: %.c
|
||||
$(QUIET_CC)$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
LUA_A= liblua.a
|
||||
CORE_O= lapi.o lcode.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o \
|
||||
lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o \
|
||||
@@ -49,14 +67,14 @@ o: $(ALL_O)
|
||||
a: $(ALL_A)
|
||||
|
||||
$(LUA_A): $(CORE_O) $(LIB_O)
|
||||
$(AR) $@ $(CORE_O) $(LIB_O) # DLL needs all object files
|
||||
$(RANLIB) $@
|
||||
$(QUIET_AR)$(AR) $@ $(CORE_O) $(LIB_O) # DLL needs all object files
|
||||
$(QUIET_AR)$(RANLIB) $@
|
||||
|
||||
$(LUA_T): $(LUA_O) $(LUA_A)
|
||||
$(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) $(LIBS)
|
||||
$(QUIET_LINK)$(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) $(LIBS)
|
||||
|
||||
$(LUAC_T): $(LUAC_O) $(LUA_A)
|
||||
$(CC) -o $@ $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS)
|
||||
$(QUIET_LINK)$(CC) -o $@ $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS)
|
||||
|
||||
clean:
|
||||
$(RM) $(ALL_T) $(ALL_O)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Valkey Design Documents
|
||||
|
||||
This folder collects designs for Valkey. These designs detail features and
|
||||
changes that require more detail than just the text in a pull request or an
|
||||
issue.
|
||||
|
||||
A markdown file describes each feature or larger topic.
|
||||
|
||||
## Workflow
|
||||
|
||||
**IMPORTANT: Before writing a design, start an issue for some early alignment.**
|
||||
This is the first step to find interested parties and collect initial
|
||||
requirements. This issue serves as the overall tracking for the feature.
|
||||
|
||||
1. After finishing initial discussion on the issue, determine the necessity of a
|
||||
design document. Small features or changes do not require designs.
|
||||
Wide-reaching changes or those requiring major alignment make good candidates
|
||||
for a design.
|
||||
2. To create a design document, create a new markdown file in this `design-docs`
|
||||
directory.
|
||||
3. The maintainers review and approve the design document once authors address
|
||||
feedback. Submitting a design document does not necessarily bind to a certain
|
||||
design.
|
||||
4. The design document serves as living documentation. As developers build the
|
||||
feature, the design document captures key design aspects. Commit changes to
|
||||
the design document alongside the code changes as developers implement the
|
||||
mid-level design details.
|
||||
|
||||
## What's useful to include?
|
||||
|
||||
**Design documents do not follow a strict format.** There is no template.
|
||||
|
||||
When writing a design, consider that many people, including those unfamiliar
|
||||
with the feature, read it. It should be self-contained and easy to understand.
|
||||
|
||||
The following sections are optional but provide a high-level overview of
|
||||
potential inclusions:
|
||||
|
||||
- **High-level overview**: A brief summary of the feature and its purpose.
|
||||
- **Key design elements**: The following are generally useful to include:
|
||||
- State machines
|
||||
- Data structures
|
||||
- Algorithms
|
||||
- Interaction with other Valkey components (replication, persistence, cluster,
|
||||
modules, etc.)
|
||||
- **Links to key issues/PRs**: Link to relevant issues/PRs for further reading.
|
||||
- **Links to relevant code**: Link to relevant code files for further reading.
|
||||
|
||||
## What not to include?
|
||||
|
||||
Overdocumentation often leads to stale design documents. A design document is
|
||||
not the best place for the following:
|
||||
|
||||
- **API Details**: API details belong in the
|
||||
[public Valkey documentation](https://valkey.io/commands/)
|
||||
- **Low-level Implementation Details**: Document difficult or complex
|
||||
implementation details in code comments, not design documents.
|
||||
- **Edge Cases**: Prefer to link to test cases or code locations that cover edge
|
||||
cases, rather than documenting them in the design document.
|
||||
- **Alternatives/Rejected Designs**: Document decision making in the issue or PR
|
||||
where the contributors made the decision.
|
||||
- **Overly Verbose Explanations**: Aim to use Mermaid or ASCII diagrams to
|
||||
explain complex concepts rather than prose.
|
||||
- **Boilerplate**: Keep every document minimal and to the point. Avoid
|
||||
unnecessary sections.
|
||||
- **Future work**: File issues for future work items to track them, rather than
|
||||
including them in the design document.
|
||||
@@ -0,0 +1,184 @@
|
||||
# Design Document: Atomic Slot Migration
|
||||
|
||||
## 1. Overview
|
||||
|
||||
Atomic Slot Migration (ASM) provides a seamless, atomic method for migrating
|
||||
hash slots between nodes in a Valkey cluster. This mechanism replaces
|
||||
`CLUSTER SETSLOT IMPORTING/MIGRATING` and `MIGRATE` for migrating slots between
|
||||
nodes.
|
||||
|
||||
## 2. Core Mechanics
|
||||
|
||||
Rather than migrating data key-by-key, ASM operates at the slot level by
|
||||
adapting existing replication and failover primitives:
|
||||
|
||||
- **Slot-Based Replication:** Physical data migration borrows from
|
||||
Primary-Replica replication mechanisms, but strictly scopes to the specific
|
||||
slots being moved.
|
||||
- **Atomic Ownership Transfer:** ASM executes the final handover of slot
|
||||
ownership using a coordinated process similar to a Manual Failover, ensuring
|
||||
an atomic transfer.
|
||||
- **Traffic Handling:** Throughout the migration process, the source node
|
||||
retains the data and continues to actively serve business requests. The system
|
||||
cleanly cuts over traffic to the target node only after completing the atomic
|
||||
transfer.
|
||||
|
||||
## 3. Implementation Details
|
||||
|
||||
### 3.1 High Level Overview
|
||||
|
||||
1. **Snapshot Transfer:** The source node transfers data to the target node. The
|
||||
source node forks a child process to iterate and serialize the slot's keys.
|
||||
|
||||
The source transfers data in "AOF" (Append Only File) format to the target
|
||||
node. This format consists of a stream of commands. Consequently, the target
|
||||
primary and target replicas replay these commands to restore the slot's
|
||||
state.
|
||||
|
||||
2. **Incremental Updates:** While transferring the initial snapshot, the source
|
||||
node serves business requests. The source node records any changes to the
|
||||
slot's keys during this time and sends them to the target node as incremental
|
||||
updates after completing the snapshot transfer.
|
||||
3. **Pause:** After sending the incremental updates, the source node pauses
|
||||
writes to the migrating slots. Consequently, the source node rejects any
|
||||
further business requests for those slots. This pause ensures the target node
|
||||
maintains the same state as the source node.
|
||||
4. **Failover:** After the source node pauses, the target node performs a
|
||||
takeover and becomes the primary node for the slot.
|
||||
5. **Clean Up:** After the target node becomes the primary node for the slot,
|
||||
the source node receives this information via cluster topology updates. The
|
||||
source node then unpauses and completes the slot migration. Failed migrations
|
||||
on the target side are cleaned up by deleting keys that are no longer owned
|
||||
by the node.
|
||||
|
||||
### 3.2 CLUSTER SYNCSLOTS
|
||||
|
||||
The source, target, and target replica use the `CLUSTER SYNCSLOTS` command to
|
||||
coordinate the handover state:
|
||||
|
||||
```
|
||||
Source Target Target Replica
|
||||
| | |
|
||||
|------------ SYNCSLOTS ESTABLISH -------------->| |
|
||||
| |----- SYNCSLOTS ESTABLISH ------>|
|
||||
|<-------------------- +OK ----------------------| |
|
||||
| | |
|
||||
|---------------- SYNCSLOTS ACK ---------------->| |
|
||||
| | |
|
||||
|~~~~~~~~~~~~~~ snapshot as AOF ~~~~~~~~~~~~~~~~>| |
|
||||
| |~~~~~~ forward snapshot ~~~~~~~~>|
|
||||
|----------- SYNCSLOTS SNAPSHOT-EOF ------------>| |
|
||||
| | |
|
||||
|<----------- SYNCSLOTS REQUEST-PAUSE -----------| |
|
||||
| | |
|
||||
|~~~~~~~~~~~~ incremental changes ~~~~~~~~~~~~~~>| |
|
||||
| |~~~~~~ forward changes ~~~~~~~~~>|
|
||||
|--------------- SYNCSLOTS PAUSED -------------->| |
|
||||
| | |
|
||||
|<---------- SYNCSLOTS REQUEST-FAILOVER ---------| |
|
||||
| | |
|
||||
|---------- SYNCSLOTS FAILOVER-GRANTED --------->| |
|
||||
| | |
|
||||
| (performs takeover & |
|
||||
| propagates topology) |
|
||||
| | |
|
||||
| |------- SYNCSLOTS FINISH ------->|
|
||||
(finds out about topology | |
|
||||
change & marks migration done) | |
|
||||
| | |
|
||||
```
|
||||
|
||||
Throughout the migration, both the source and target nodes exchange periodic
|
||||
`SYNCSLOTS ACK` messages to monitor the health and progress of the operation.
|
||||
If a node fails to receive an acknowledgment within the replication timeout,
|
||||
the migration is aborted.
|
||||
|
||||
See code comments in [cluster_migrateslots.c](../src/cluster_migrateslots.c) for
|
||||
detailed state machines.
|
||||
|
||||
### 3.3 Automatic Rollback
|
||||
|
||||
Various scenarios cause slot migration failure:
|
||||
|
||||
1. Link between source and target disconnects
|
||||
2. Source or target node crash, halt, or encounter a partition
|
||||
3. A failover occurs on the source or target node
|
||||
4. Out of memory error occurs on the target node
|
||||
5. Client output buffer on the source node grows too large
|
||||
6. An administrator executes `FLUSHDB` on the source or target node
|
||||
|
||||
In such cases, ASM automatically rolls back the migration.
|
||||
|
||||
```
|
||||
Source Target Target Replica
|
||||
| | |
|
||||
|------------ SYNCSLOTS ESTABLISH -------------->| |
|
||||
| |----- SYNCSLOTS ESTABLISH ------>|
|
||||
|<-------------------- +OK ----------------------| |
|
||||
... ... ...
|
||||
| | |
|
||||
| <FAILURE> |
|
||||
| | |
|
||||
| (performs cleanup) |
|
||||
| | ~~~~~~ UNLINK <key> ... ~~~~~~~>|
|
||||
| | |
|
||||
| | ------ SYNCSLOTS FINISH ------->|
|
||||
| | |
|
||||
```
|
||||
|
||||
#### 3.3.1 Cleanup
|
||||
|
||||
The cluster automatically cleans up failed or cancelled slot migrations. The
|
||||
primary is solely responsible for cleaning up unowned slots. Primaries demoted
|
||||
during migration do not clean up previously active slot imports. The promoted
|
||||
replica is responsible for both cleaning up the slot and sending a
|
||||
`SYNCSLOTS FINISH`.
|
||||
|
||||
### 3.4 Key Containment
|
||||
|
||||
The system rejects any keyed command executed on a node that is not the primary
|
||||
for that slot with `-MOVED` (e.g. `GET`, `SET`, `DEL`, `INCR`, etc).
|
||||
|
||||
Nodes filter unkeyed read commands, like `SCAN` and `KEYS`, to avoid exposing
|
||||
importing slot data. Each node in the target shard tracks the slot migration job
|
||||
state and hides writes to that slot from the end user until the migration
|
||||
completes.
|
||||
|
||||
#### 3.4.1 Full Sync, Partial Sync, RDB
|
||||
|
||||
To ensure that replicas resyncing during an import remain aware of it, Valkey
|
||||
serializes each in-progress slot import into an RDB section defined by a new
|
||||
opcode. The encoding includes the job name and the slot ranges being imported.
|
||||
Whenever the system loads an RDB file containing a slot import section, whether
|
||||
from disk or during a primary sync, it adds a new migration to track the import.
|
||||
If the Valkey node becomes a primary after loading the RDB, it cancels the slot
|
||||
migration.
|
||||
|
||||
Failure to load the opcode results in consistency problems, so the opcode is
|
||||
mandatory. If the opcode is not recognized, the RDB load will fail.
|
||||
|
||||
Loading this tracking state on primaries ensures that replicas partially syncing
|
||||
to a restarted primary still get their `SYNCSLOTS FINISH` message in the
|
||||
replication stream.
|
||||
|
||||
#### 3.4.2 AOF
|
||||
|
||||
Valkey propagates the `ESTABLISH` and `FINISH` commands to the AOF, ensuring
|
||||
they replay properly on AOF load. Similar to RDB, if any pending `ESTABLISH`
|
||||
commands lack a subsequent `FINISH` upon becoming primary, the system fails them
|
||||
after loading.
|
||||
|
||||
## 4. External References
|
||||
|
||||
- **API & User Commands:**
|
||||
- [CLUSTER MIGRATESLOTS](https://valkey.io/commands/cluster-migrateslots/)
|
||||
- [CLUSTER GETSLOTMIGRATIONS](https://valkey.io/commands/cluster-getslotmigrations/)
|
||||
- [CLUSTER CANCELSLOTMIGRATIONS](https://valkey.io/commands/cluster-cancelslotmigrations/)
|
||||
- [CLUSTER SYNCSLOTS](https://valkey.io/commands/cluster-syncslots/)
|
||||
- **Corner Cases:** Read the test cases in
|
||||
[cluster-migrateslots.tcl](../tests/unit/cluster/cluster-migrateslots.tcl)
|
||||
- **PR References:** For further reading, refer to the following Pull Requests:
|
||||
- [PR #1949](https://github.com/valkey-io/valkey/pull/1949)
|
||||
- [PR #2755](https://github.com/valkey-io/valkey/pull/2755)
|
||||
- [PR #2593](https://github.com/valkey-io/valkey/pull/2593)
|
||||
- [PR #2635](https://github.com/valkey-io/valkey/pull/2635)
|
||||
@@ -3,3 +3,5 @@
|
||||
*.gcov
|
||||
valkey.info
|
||||
lcov-html
|
||||
generated_*
|
||||
unit/wrapped/
|
||||
|
||||
+28
-16
@@ -10,30 +10,41 @@ message(STATUS "CFLAGS: ${CMAKE_C_FLAGS}")
|
||||
get_valkey_server_linker_option(VALKEY_SERVER_LDFLAGS)
|
||||
list(APPEND SERVER_LIBS "fpconv")
|
||||
list(APPEND SERVER_LIBS "hdr_histogram")
|
||||
list(APPEND SERVER_LIBS "ffc")
|
||||
valkey_build_and_install_bin(valkey-server "${VALKEY_SERVER_SRCS}" "${VALKEY_SERVER_LDFLAGS}" "${SERVER_LIBS}"
|
||||
"redis-server")
|
||||
add_dependencies(valkey-server generate_commands_def)
|
||||
add_dependencies(valkey-server generate_fmtargs_h)
|
||||
add_dependencies(valkey-server release_header)
|
||||
|
||||
if (BUILD_LUA)
|
||||
if (NOT BUILD_LUA STREQUAL "no")
|
||||
message(STATUS "Build Lua scripting engine module")
|
||||
if (BUILD_LUA STREQUAL "static")
|
||||
add_compile_definitions(STATIC_LUA=1)
|
||||
message(STATUS "Building LUA as a STATIC module")
|
||||
else ()
|
||||
add_compile_definitions(STATIC_LUA=0)
|
||||
message(STATUS "Building LUA as a DYNAMIC module")
|
||||
endif ()
|
||||
add_subdirectory(modules/lua)
|
||||
add_dependencies(valkey-server valkeylua)
|
||||
if (BUILD_LUA STREQUAL "static")
|
||||
target_link_libraries(valkey-server $<LINK_LIBRARY:WHOLE_ARCHIVE,valkeylua>)
|
||||
else ()
|
||||
add_dependencies(valkey-server valkeylua)
|
||||
endif ()
|
||||
target_compile_definitions(valkey-server PRIVATE LUA_ENABLED)
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_compile_definitions(valkey-server PRIVATE LUA_LIB=libvalkeylua.so)
|
||||
target_link_options(valkey-server PRIVATE -Wl,--disable-new-dtags)
|
||||
else ()
|
||||
target_compile_definitions(valkey-server PRIVATE LUA_LIB=libvalkeylua.dylib)
|
||||
endif ()
|
||||
set(VALKEY_INSTALL_RPATH "")
|
||||
set_target_properties(valkey-server PROPERTIES
|
||||
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR};${CMAKE_LIBRARY_OUTPUT_DIRECTORY}"
|
||||
INSTALL_RPATH_USE_LINK_PATH TRUE
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
)
|
||||
endif()
|
||||
set_target_properties(
|
||||
valkey-server
|
||||
PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR};${CMAKE_LIBRARY_OUTPUT_DIRECTORY}"
|
||||
INSTALL_RPATH_USE_LINK_PATH TRUE
|
||||
BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
endif ()
|
||||
unset(BUILD_LUA CACHE)
|
||||
|
||||
if (VALKEY_RELEASE_BUILD)
|
||||
@@ -48,9 +59,8 @@ if (DEBUG_FORCE_DEFRAG)
|
||||
endif ()
|
||||
|
||||
if (BUILD_SANITIZER)
|
||||
# 'BUILD_SANITIZER' is defined in ValkeySetup module (based on user input)
|
||||
# If defined, the variables 'VALKEY_SANITAIZER_CFLAGS' and 'VALKEY_SANITAIZER_LDFLAGS'
|
||||
# are set with the link & compile flags required
|
||||
# 'BUILD_SANITIZER' is defined in ValkeySetup module (based on user input) If defined, the variables
|
||||
# 'VALKEY_SANITAIZER_CFLAGS' and 'VALKEY_SANITAIZER_LDFLAGS' are set with the link & compile flags required
|
||||
message(STATUS "Adding sanitizer flags for target valkey-server")
|
||||
target_compile_options(valkey-server PRIVATE ${VALKEY_SANITAIZER_CFLAGS})
|
||||
target_link_options(valkey-server PRIVATE ${VALKEY_SANITAIZER_LDFLAGS})
|
||||
@@ -60,6 +70,7 @@ unset(BUILD_SANITIZER CACHE)
|
||||
# Target: valkey-cli
|
||||
list(APPEND CLI_LIBS "fpconv")
|
||||
list(APPEND CLI_LIBS "linenoise")
|
||||
list(APPEND CLI_LIBS "ffc")
|
||||
valkey_build_and_install_bin(valkey-cli "${VALKEY_CLI_SRCS}" "${VALKEY_SERVER_LDFLAGS}" "${CLI_LIBS}" "redis-cli")
|
||||
add_dependencies(valkey-cli generate_commands_def)
|
||||
add_dependencies(valkey-cli generate_fmtargs_h)
|
||||
@@ -67,6 +78,7 @@ add_dependencies(valkey-cli generate_fmtargs_h)
|
||||
# Target: valkey-benchmark
|
||||
list(APPEND BENCH_LIBS "fpconv")
|
||||
list(APPEND BENCH_LIBS "hdr_histogram")
|
||||
list(APPEND BENCH_LIBS "ffc")
|
||||
valkey_build_and_install_bin(valkey-benchmark "${VALKEY_BENCHMARK_SRCS}" "${VALKEY_SERVER_LDFLAGS}" "${BENCH_LIBS}"
|
||||
"redis-benchmark")
|
||||
add_dependencies(valkey-benchmark generate_commands_def)
|
||||
@@ -109,16 +121,16 @@ if (BUILD_EXAMPLE_MODULES)
|
||||
add_subdirectory(modules)
|
||||
endif ()
|
||||
|
||||
if (BUILD_UNIT_TESTS)
|
||||
if (BUILD_UNIT_GTESTS)
|
||||
add_subdirectory(unit)
|
||||
endif ()
|
||||
|
||||
# Friendly hint like the Makefile one
|
||||
file(RELATIVE_PATH _CMAKE_DIR_RELATIVE_PATH "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}")
|
||||
add_custom_target(hint ALL
|
||||
add_custom_target(
|
||||
hint ALL
|
||||
DEPENDS valkey-server valkey-cli valkey-benchmark
|
||||
COMMAND ${CMAKE_COMMAND} -E echo ""
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Hint: It is a good idea to run tests with your CMake-built binaries \\;\\)"
|
||||
COMMAND ${CMAKE_COMMAND} -E echo " ./${_CMAKE_DIR_RELATIVE_PATH}/runtest"
|
||||
COMMAND ${CMAKE_COMMAND} -E echo ""
|
||||
)
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "")
|
||||
|
||||
+60
-49
@@ -171,6 +171,7 @@ ifeq ($(uname_S),SunOS)
|
||||
else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
# Darwin
|
||||
FINAL_LDFLAGS+= -Wl,-export_dynamic
|
||||
FINAL_LIBS+= -ldl
|
||||
# Homebrew's OpenSSL is not linked to /usr/local to avoid
|
||||
# conflicts with the system's LibreSSL installation so it
|
||||
@@ -209,18 +210,22 @@ ifeq ($(uname_S),NetBSD)
|
||||
else
|
||||
ifeq ($(uname_S),FreeBSD)
|
||||
# FreeBSD
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),DragonFly)
|
||||
# DragonFly
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),OpenBSD)
|
||||
# OpenBSD
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),NetBSD)
|
||||
# NetBSD
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),Haiku)
|
||||
@@ -251,25 +256,40 @@ ifdef OPENSSL_PREFIX
|
||||
endif
|
||||
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/libvalkey/include -I../deps/linenoise -I../deps/hdr_histogram -I../deps/fpconv
|
||||
FINAL_CFLAGS+= -I../deps/libvalkey/include -I../deps/linenoise -I../deps/hdr_histogram -I../deps/fpconv -I../deps/fast_float
|
||||
|
||||
# Lua scripting engine module
|
||||
LUA_MODULE_NAME:=modules/lua/libvalkeylua.so
|
||||
ifeq ($(BUILD_LUA),no)
|
||||
LUA_MODULE_NAME=
|
||||
LUA_MODULE=
|
||||
LUA_MODULE_INSTALL=
|
||||
else
|
||||
FINAL_CFLAGS+=-DSTATIC_LUA=0
|
||||
else ifeq ($(BUILD_LUA),module)
|
||||
LUA_MODULE_NAME=modules/lua/libvalkeylua.so
|
||||
LUA_MODULE=$(LUA_MODULE_NAME)
|
||||
LUA_MODULE_INSTALL=install-lua-module
|
||||
|
||||
current_dir = $(shell pwd)
|
||||
FINAL_CFLAGS+=-DLUA_ENABLED -DLUA_LIB=libvalkeylua.so
|
||||
ifeq ($(uname_S),Darwin)
|
||||
FINAL_LDFLAGS+= -Wl,-rpath,$(PREFIX)/lib
|
||||
FINAL_LDFLAGS+= -Wl,-rpath,$(current_dir)/modules/lua
|
||||
FINAL_CFLAGS+=-DLUA_ENABLED -DLUA_LIB=libvalkeylua.so -DSTATIC_LUA=0
|
||||
ifeq ($(uname_S),Darwin)
|
||||
FINAL_LDFLAGS+= -Wl,-rpath,$(PREFIX)/lib
|
||||
FINAL_LDFLAGS+= -Wl,-rpath,$(current_dir)/modules/lua
|
||||
else
|
||||
FINAL_LDFLAGS+= -Wl,-rpath,$(PREFIX)/lib:$(current_dir)/modules/lua -Wl,--disable-new-dtags
|
||||
endif
|
||||
else
|
||||
FINAL_LDFLAGS+= -Wl,-rpath,$(PREFIX)/lib:$(current_dir)/modules/lua -Wl,--disable-new-dtags
|
||||
endif
|
||||
# The default: building Lua as a static module.
|
||||
LUA_MODULE_NAME=modules/lua/libvalkeylua.a
|
||||
LUA_MODULE=$(LUA_MODULE_NAME)
|
||||
current_dir = $(shell pwd)
|
||||
FINAL_CFLAGS+=-DLUA_ENABLED -DSTATIC_LUA=1
|
||||
ifeq ($(uname_S),Darwin)
|
||||
LUA_LDFLAGS=-Wl,-export_dynamic -Wl,-force_load,$(current_dir)/modules/lua/libvalkeylua.a ../deps/lua/src/liblua.a
|
||||
else ifeq ($(uname_S),FreeBSD)
|
||||
LUA_LDFLAGS=-Wl,--export-dynamic -Wl,--whole-archive $(current_dir)/modules/lua/libvalkeylua.a -Wl,--no-whole-archive ../deps/lua/src/liblua.a
|
||||
else
|
||||
LUA_LDFLAGS=-Wl,--whole-archive $(current_dir)/modules/lua/libvalkeylua.a -Wl,--no-whole-archive ../deps/lua/src/liblua.a
|
||||
endif
|
||||
endif
|
||||
|
||||
# Determine systemd support and/or build preference (defaulting to auto-detection)
|
||||
@@ -373,7 +393,7 @@ RDMA_MODULE_NAME:=valkey-rdma$(PROG_SUFFIX).so
|
||||
RDMA_MODULE_CFLAGS:=$(FINAL_CFLAGS)
|
||||
ifeq ($(BUILD_RDMA),module)
|
||||
FINAL_CFLAGS+=-DUSE_RDMA=$(BUILD_MODULE)
|
||||
RDMA_CLIENT_LIBS = ../deps/libvalkey/lib/libvalkey_rdma.a $(RDMA_LIBS)
|
||||
RDMA_CLIENT_LIBS = ../deps/libvalkey/lib/libvalkey_rdma.a
|
||||
RDMA_MODULE=$(RDMA_MODULE_NAME)
|
||||
RDMA_MODULE_CFLAGS+=-DUSE_RDMA=$(BUILD_MODULE) -DBUILD_RDMA_MODULE=$(BUILD_MODULE) $(RDMA_LIBS)
|
||||
endif
|
||||
@@ -471,6 +491,7 @@ ENGINE_SERVER_OBJ = \
|
||||
config.o \
|
||||
connection.o \
|
||||
crc16.o \
|
||||
crc16_slottable.o \
|
||||
crc64.o \
|
||||
crccombine.o \
|
||||
crcspeed.o \
|
||||
@@ -555,11 +576,13 @@ ENGINE_SERVER_OBJ = \
|
||||
util.o \
|
||||
valkey-check-aof.o \
|
||||
valkey-check-rdb.o \
|
||||
valkey_strtod.o \
|
||||
vector.o \
|
||||
vset.o \
|
||||
ziplist.o \
|
||||
zipmap.o \
|
||||
zmalloc.o
|
||||
zmalloc.o \
|
||||
queues.o
|
||||
ENGINE_SERVER_OBJ+=$(ENGINE_TRACE_OBJ)
|
||||
ENGINE_CLI_NAME=$(ENGINE_NAME)-cli$(PROG_SUFFIX)
|
||||
ENGINE_CLI_OBJ = \
|
||||
@@ -583,6 +606,7 @@ ENGINE_CLI_OBJ = \
|
||||
strl.o \
|
||||
util.o \
|
||||
valkey-cli.o \
|
||||
valkey_strtod.o \
|
||||
zmalloc.o
|
||||
ENGINE_BENCHMARK_NAME=$(ENGINE_NAME)-benchmark$(PROG_SUFFIX)
|
||||
ENGINE_BENCHMARK_OBJ = \
|
||||
@@ -591,6 +615,7 @@ ENGINE_BENCHMARK_OBJ = \
|
||||
anet.o \
|
||||
cli_common.o \
|
||||
crc16.o \
|
||||
crc16_slottable.o \
|
||||
crc64.o \
|
||||
crccombine.o \
|
||||
crcspeed.o \
|
||||
@@ -607,26 +632,14 @@ ENGINE_BENCHMARK_OBJ = \
|
||||
strl.o \
|
||||
util.o \
|
||||
valkey-benchmark.o \
|
||||
valkey_strtod.o \
|
||||
zmalloc.o
|
||||
ENGINE_CHECK_RDB_NAME=$(ENGINE_NAME)-check-rdb$(PROG_SUFFIX)
|
||||
ENGINE_CHECK_AOF_NAME=$(ENGINE_NAME)-check-aof$(PROG_SUFFIX)
|
||||
ENGINE_LIB_NAME=lib$(ENGINE_NAME).a
|
||||
ENGINE_TEST_FILES:=$(wildcard unit/*.c)
|
||||
ENGINE_TEST_OBJ:=$(sort $(patsubst unit/%.c,unit/%.o,$(ENGINE_TEST_FILES)))
|
||||
ENGINE_UNIT_TESTS:=$(ENGINE_NAME)-unit-tests$(PROG_SUFFIX)
|
||||
ENGINE_UNIT_GTESTS:=$(ENGINE_NAME)-unit-gtests$(PROG_SUFFIX)
|
||||
ALL_SOURCES=$(sort $(patsubst %.o,%.c,$(ENGINE_SERVER_OBJ) $(ENGINE_CLI_OBJ) $(ENGINE_BENCHMARK_OBJ)))
|
||||
|
||||
USE_FAST_FLOAT?=no
|
||||
ifeq ($(USE_FAST_FLOAT),yes)
|
||||
# valkey_strtod.h uses this flag to switch valkey_strtod function to fast_float_strtod,
|
||||
# therefore let's pass it to compiler for preprocessing.
|
||||
FINAL_CFLAGS += -D USE_FAST_FLOAT
|
||||
# next, let's build and add actual library containing fast_float_strtod function for linking.
|
||||
DEPENDENCY_TARGETS += fast_float_c_interface
|
||||
FAST_FLOAT_STRTOD_OBJECT := ../deps/fast_float_c_interface/fast_float_strtod.o
|
||||
FINAL_LIBS += $(FAST_FLOAT_STRTOD_OBJECT)
|
||||
endif
|
||||
|
||||
USE_LIBBACKTRACE?=no
|
||||
ifeq ($(USE_LIBBACKTRACE),yes)
|
||||
FINAL_CFLAGS += -DUSE_LIBBACKTRACE
|
||||
@@ -637,7 +650,9 @@ ifeq ($(USE_LIBBACKTRACE),yes)
|
||||
FINAL_LIBS += -lbacktrace
|
||||
endif
|
||||
|
||||
ALL_BUILD_PREREQUISITES=$(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(TLS_MODULE) $(RDMA_MODULE) $(LUA_MODULE)
|
||||
DEPENDENCY_TARGETS+= gtest-parallel
|
||||
|
||||
ALL_BUILD_PREREQUISITES=$(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(TLS_MODULE) $(RDMA_MODULE)
|
||||
|
||||
all: $(ALL_BUILD_PREREQUISITES)
|
||||
@echo ""
|
||||
@@ -653,7 +668,7 @@ endif
|
||||
|
||||
.PHONY: all
|
||||
|
||||
all-with-unit-tests: all $(ENGINE_UNIT_TESTS)
|
||||
all-with-unit-tests: all $(ENGINE_UNIT_GTESTS)
|
||||
.PHONY: all
|
||||
|
||||
persist-settings: distclean
|
||||
@@ -666,14 +681,15 @@ persist-settings: distclean
|
||||
echo BUILD_RDMA=$(BUILD_RDMA) >> .make-settings
|
||||
echo USE_SYSTEMD=$(USE_SYSTEMD) >> .make-settings
|
||||
echo BUILD_LUA=$(BUILD_LUA) >> .make-settings
|
||||
echo USE_FAST_FLOAT=$(USE_FAST_FLOAT) >> .make-settings
|
||||
echo USE_LIBBACKTRACE=$(USE_LIBBACKTRACE) >> .make-settings
|
||||
echo LIBBACKTRACE_PREFIX=$(LIBBACKTRACE_PREFIX) >> .make-settings
|
||||
echo CFLAGS=$(CFLAGS) >> .make-settings
|
||||
echo LDFLAGS=$(LDFLAGS) >> .make-settings
|
||||
echo SERVER_CFLAGS=$(SERVER_CFLAGS) >> .make-settings
|
||||
echo SERVER_LDFLAGS=$(SERVER_LDFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_CFLAGS=$(FINAL_CFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_LDFLAGS=$(FINAL_LDFLAGS) >> .make-settings
|
||||
echo ENGINE_SERVER_OBJ=$(ENGINE_SERVER_OBJ) >> .make-settings
|
||||
-(cd ../deps && $(MAKE) $(DEPENDENCY_TARGETS))
|
||||
|
||||
.PHONY: persist-settings
|
||||
@@ -692,17 +708,13 @@ ifneq ($(strip $(PREV_FINAL_LDFLAGS)), $(strip $(FINAL_LDFLAGS)))
|
||||
endif
|
||||
|
||||
# valkey-server
|
||||
$(SERVER_NAME): $(ENGINE_SERVER_OBJ)
|
||||
$(SERVER_LD) -o $@ $^ ../deps/libvalkey/lib/libvalkey.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS)
|
||||
$(SERVER_NAME): $(ENGINE_SERVER_OBJ) $(LUA_MODULE)
|
||||
$(SERVER_LD) -o $@ $(ENGINE_SERVER_OBJ) ../deps/libvalkey/lib/libvalkey.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS) $(LUA_LDFLAGS)
|
||||
|
||||
# Valkey static library, used to compile against for unit testing
|
||||
$(ENGINE_LIB_NAME): $(ENGINE_SERVER_OBJ)
|
||||
$(SERVER_AR) rcs $@ $^
|
||||
|
||||
# valkey-unit-tests
|
||||
$(ENGINE_UNIT_TESTS): $(ENGINE_TEST_OBJ) $(ENGINE_LIB_NAME)
|
||||
$(SERVER_LD) -o $@ $^ ../deps/libvalkey/lib/libvalkey.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS)
|
||||
|
||||
# valkey-sentinel
|
||||
$(ENGINE_SENTINEL_NAME): $(SERVER_NAME)
|
||||
$(ENGINE_INSTALL) $(SERVER_NAME) $(ENGINE_SENTINEL_NAME)
|
||||
@@ -717,15 +729,15 @@ $(ENGINE_CHECK_AOF_NAME): $(SERVER_NAME)
|
||||
|
||||
# valkey-tls.so
|
||||
$(TLS_MODULE_NAME): $(SERVER_NAME)
|
||||
$(QUIET_CC)$(CC) -o $@ tls.c -shared -fPIC $(TLS_MODULE_CFLAGS) $(TLS_CLIENT_LIBS)
|
||||
$(QUIET_CC)$(CC) $(LDFLAGS) -o $@ tls.c -shared -fPIC $(TLS_MODULE_CFLAGS) $(TLS_CLIENT_LIBS)
|
||||
|
||||
# valkey-rdma.so
|
||||
$(RDMA_MODULE_NAME): $(SERVER_NAME)
|
||||
$(QUIET_CC)$(CC) -o $@ rdma.c -shared -fPIC $(RDMA_MODULE_CFLAGS)
|
||||
$(QUIET_CC)$(CC) $(LDFLAGS) -o $@ rdma.c -shared -fPIC $(RDMA_MODULE_CFLAGS)
|
||||
|
||||
# engine_lua.so
|
||||
$(LUA_MODULE_NAME): $(SERVER_NAME)
|
||||
cd modules/lua && $(MAKE) OPTIMIZATION="$(OPTIMIZATION)"
|
||||
$(LUA_MODULE_NAME): .make-prerequisites
|
||||
$(MAKE) -C modules/lua OPTIMIZATION="$(OPTIMIZATION)" BUILD_LUA="$(BUILD_LUA)"
|
||||
|
||||
# valkey-cli
|
||||
$(ENGINE_CLI_NAME): $(ENGINE_CLI_OBJ)
|
||||
@@ -747,9 +759,6 @@ DEP = $(ENGINE_SERVER_OBJ:%.o=%.d) $(ENGINE_CLI_OBJ:%.o=%.d) $(ENGINE_BENCHMARK_
|
||||
trace/%.o: trace/%.c .make-prerequisites
|
||||
$(SERVER_CC) -Itrace -MMD -o $@ -c $<
|
||||
|
||||
unit/%.o: unit/%.c .make-prerequisites
|
||||
$(SERVER_CC) -MMD -o $@ -c $<
|
||||
|
||||
# The following files are checked in and don't normally need to be rebuilt. They
|
||||
# are built only if python is available and their prereqs are modified.
|
||||
ifneq (,$(PYTHON))
|
||||
@@ -760,17 +769,12 @@ fmtargs.h: ../utils/generate-fmtargs.py
|
||||
$(QUITE_GEN)sed '/Everything below this line/,$$d' $@ > $@.tmp
|
||||
$(QUITE_GEN)$(PYTHON) ../utils/generate-fmtargs.py >> $@.tmp
|
||||
$(QUITE_GEN)mv $@.tmp $@
|
||||
|
||||
unit/test_files.h: unit/*.c ../utils/generate-unit-test-header.py
|
||||
$(QUIET_GEN)$(PYTHON) ../utils/generate-unit-test-header.py
|
||||
|
||||
unit/test_main.o: unit/test_files.h
|
||||
endif
|
||||
|
||||
commands.c: $(COMMANDS_DEF_FILENAME).def
|
||||
|
||||
clean:
|
||||
rm -rf $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(ENGINE_UNIT_TESTS) $(ENGINE_LIB_NAME) unit/*.o unit/*.d trace/*.o trace/*.d *.o *.gcda *.gcno *.gcov valkey.info lcov-html Makefile.dep *.so
|
||||
rm -rf $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(ENGINE_UNIT_GTESTS) $(ENGINE_LIB_NAME) trace/*.o trace/*.d *.o *.gcda *.gcno *.gcov valkey.info lcov-html Makefile.dep *.so
|
||||
rm -f $(DEP)
|
||||
-(cd modules/lua && $(MAKE) clean)
|
||||
|
||||
@@ -780,15 +784,21 @@ distclean: clean
|
||||
-(cd ../deps && $(MAKE) distclean)
|
||||
-(cd modules && $(MAKE) clean)
|
||||
-(cd ../tests/modules && $(MAKE) clean)
|
||||
-(cd unit && $(MAKE) clean)
|
||||
-(rm -f .make-*)
|
||||
-(find unit -type d -name __pycache__ -exec rm -rf {} +)
|
||||
|
||||
.PHONY: distclean
|
||||
|
||||
test: $(ALL_BUILD_PREREQUISITES)
|
||||
@(cd ..; VALKEY_PROG_SUFFIX="$(PROG_SUFFIX)" ./runtest)
|
||||
|
||||
test-unit: $(ENGINE_UNIT_TESTS)
|
||||
./$(ENGINE_UNIT_TESTS)
|
||||
test-unit:
|
||||
@(cd unit && $(MAKE) test-unit)
|
||||
|
||||
# valkey-unit-gtests
|
||||
$(ENGINE_UNIT_GTESTS): $(ENGINE_LIB_NAME)
|
||||
@(cd unit && $(MAKE) $(ENGINE_UNIT_GTESTS))
|
||||
|
||||
test-modules: $(SERVER_NAME)
|
||||
@(cd ..; ./runtest-moduleapi)
|
||||
@@ -805,6 +815,7 @@ lcov:
|
||||
@lcov --version
|
||||
$(MAKE) gcov
|
||||
@(set -e; cd ..; ./runtest)
|
||||
$(MAKE) test-unit COVERAGE=1 COVERAGE_CFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
@geninfo -o valkey.info .
|
||||
@genhtml --legend -o lcov-html valkey.info
|
||||
|
||||
|
||||
@@ -1005,6 +1005,131 @@ int startAppendOnly(void) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Try to restart AOF after replica full sync by adopting `server.rdb_filename`
|
||||
* as the new BASE file (RDB preamble mode), avoiding a redundant AOFRW.
|
||||
* Returns C_OK on success; on C_ERR caller should fallback to
|
||||
* restartAOFAfterSYNC(). */
|
||||
int restartAOFWithSyncRdb(void) {
|
||||
serverAssert(server.aof_state == AOF_OFF);
|
||||
|
||||
int ret = C_ERR;
|
||||
int newfd = -1, rdbfile_renamed = 0;
|
||||
sds new_base_filename = NULL;
|
||||
sds new_base_filepath = NULL;
|
||||
sds new_incr_filename = NULL;
|
||||
sds new_incr_filepath = NULL;
|
||||
aofManifest *temp_am = NULL;
|
||||
|
||||
if (dirCreateIfMissing(server.aof_dirname) == -1) {
|
||||
serverLog(LL_WARNING, "Can't open or create append-only dir %s: %s", server.aof_dirname, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
serverAssert(server.aof_manifest != NULL);
|
||||
temp_am = aofManifestDup(server.aof_manifest);
|
||||
|
||||
new_base_filename = getNewBaseFileNameAndMarkPreAsHistory(temp_am, server.aof_use_rdb_preamble);
|
||||
serverAssert(new_base_filename != NULL);
|
||||
new_base_filepath = makePath(server.aof_dirname, new_base_filename);
|
||||
|
||||
if (rename(server.rdb_filename, new_base_filepath) == -1) {
|
||||
serverLog(LL_WARNING, "Error trying to rename the RDB file %s into %s: %s", server.rdb_filename,
|
||||
new_base_filepath, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
rdbfile_renamed = 1;
|
||||
|
||||
/* Mark existing incr AOF files as history BEFORE creating the new one,
|
||||
* so the new incr entry is not inadvertently moved to history. */
|
||||
markRewrittenIncrAofAsHistory(temp_am);
|
||||
|
||||
new_incr_filename = getNewIncrAofName(temp_am);
|
||||
new_incr_filepath = makePath(server.aof_dirname, new_incr_filename);
|
||||
newfd = open(new_incr_filepath, O_WRONLY | O_TRUNC | O_CREAT, 0666);
|
||||
if (newfd == -1) {
|
||||
serverLog(LL_WARNING, "Can't open the append-only file %s: %s", new_incr_filename, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (persistAofManifest(temp_am) == C_ERR) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
aofManifestFreeAndUpdate(temp_am);
|
||||
temp_am = NULL;
|
||||
|
||||
aofDelHistoryFiles();
|
||||
|
||||
sdsfree(new_base_filepath);
|
||||
new_base_filepath = NULL;
|
||||
sdsfree(new_incr_filepath);
|
||||
new_incr_filepath = NULL;
|
||||
|
||||
/* Drain pending fsync jobs from the previous AOF before publishing the new
|
||||
* fsynced offset to avoid races on fsynced_reploff_pending. */
|
||||
bioDrainWorker(BIO_AOF_FSYNC);
|
||||
atomic_store_explicit(&server.fsynced_reploff_pending, server.primary_repl_offset, memory_order_relaxed);
|
||||
server.fsynced_reploff =
|
||||
atomic_load_explicit(&server.fsynced_reploff_pending, memory_order_relaxed);
|
||||
|
||||
int aof_bio_fsync_status = atomic_load_explicit(&server.aof_bio_fsync_status, memory_order_relaxed);
|
||||
if (aof_bio_fsync_status == C_ERR) {
|
||||
serverLog(LL_WARNING, "AOF reopen, just ignore the AOF fsync error in bio job");
|
||||
atomic_store_explicit(&server.aof_bio_fsync_status, C_OK, memory_order_relaxed);
|
||||
}
|
||||
|
||||
if (server.aof_last_write_status == C_ERR) {
|
||||
serverLog(LL_WARNING, "AOF reopen, just ignore the last error.");
|
||||
server.aof_last_write_status = C_OK;
|
||||
}
|
||||
|
||||
server.aof_lastbgrewrite_status = C_OK;
|
||||
server.stat_aofrw_consecutive_failures = 0;
|
||||
|
||||
server.aof_last_fsync = server.mstime;
|
||||
server.aof_last_incr_size = 0;
|
||||
server.aof_last_incr_fsync_offset = 0;
|
||||
server.aof_rewrite_base_size = getAppendOnlyFileSize(new_base_filename, NULL);
|
||||
server.aof_current_size = server.aof_rewrite_base_size;
|
||||
|
||||
server.aof_fd = newfd;
|
||||
server.aof_state = AOF_ON;
|
||||
|
||||
serverLog(LL_NOTICE, "Reused RDB file from primary sync as AOF base file: %s", new_base_filename);
|
||||
ret = C_OK;
|
||||
return ret;
|
||||
|
||||
cleanup:
|
||||
if (rdbfile_renamed) {
|
||||
if (rename(new_base_filepath, server.rdb_filename) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Failed to rename AOF base back to RDB file %s: %s. "
|
||||
"Orphan file may remain at %s",
|
||||
server.rdb_filename, strerror(errno), new_base_filepath);
|
||||
} else {
|
||||
rdbfile_renamed = 0;
|
||||
}
|
||||
}
|
||||
if (server.rdb_del_sync_files && allPersistenceDisabled()) {
|
||||
serverLog(LL_NOTICE, "Removing the RDB file obtained from "
|
||||
"the primary. This replica has persistence "
|
||||
"disabled");
|
||||
if (rdbfile_renamed) {
|
||||
bg_unlink(new_base_filepath);
|
||||
} else {
|
||||
bg_unlink(server.rdb_filename);
|
||||
}
|
||||
}
|
||||
if (newfd != -1) close(newfd);
|
||||
if (new_incr_filepath) {
|
||||
bg_unlink(new_incr_filepath);
|
||||
sdsfree(new_incr_filepath);
|
||||
}
|
||||
if (temp_am) aofManifestFree(temp_am);
|
||||
if (new_base_filepath) sdsfree(new_base_filepath);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* 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
|
||||
@@ -1376,7 +1501,8 @@ struct client *createAOFClient(void) {
|
||||
* background processing there is a chance that the
|
||||
* command execution order will be violated.
|
||||
*/
|
||||
c->raw_flag = 0;
|
||||
c->raw_flag1 = 0;
|
||||
c->raw_flag2 = 0;
|
||||
c->flag.deny_blocking = 1;
|
||||
c->flag.fake = 1;
|
||||
|
||||
|
||||
@@ -144,6 +144,12 @@ typedef union bio_job {
|
||||
|
||||
void *bioProcessBackgroundJobs(void *arg);
|
||||
|
||||
/* Allocate a bio_job. Marked noinline so that it appears as a distinct frame in valgrind
|
||||
* stack traces, allowing a targeted Valgrind suppression for BIO job leaks */
|
||||
__attribute__((noinline)) static bio_job *allocBioJob(size_t extra) {
|
||||
return zmalloc(sizeof(bio_job) + extra);
|
||||
}
|
||||
|
||||
/* Make sure we have enough stack to perform all the things we do in the
|
||||
* main thread. */
|
||||
#define VALKEY_THREAD_STACK_SIZE (1024 * 1024 * 4)
|
||||
@@ -188,7 +194,7 @@ void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) {
|
||||
va_list valist;
|
||||
/* Allocate memory for the job structure and all required
|
||||
* arguments */
|
||||
bio_job *job = zmalloc(sizeof(*job) + sizeof(void *) * (arg_count));
|
||||
bio_job *job = allocBioJob(sizeof(void *) * (arg_count));
|
||||
job->free_args.free_fn = free_fn;
|
||||
|
||||
va_start(valist, arg_count);
|
||||
@@ -200,7 +206,7 @@ void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) {
|
||||
}
|
||||
|
||||
void bioCreateCloseJob(int fd, int need_fsync, int need_reclaim_cache) {
|
||||
bio_job *job = zmalloc(sizeof(*job));
|
||||
bio_job *job = allocBioJob(0);
|
||||
job->fd_args.fd = fd;
|
||||
job->fd_args.need_fsync = need_fsync;
|
||||
job->fd_args.need_reclaim_cache = need_reclaim_cache;
|
||||
@@ -209,7 +215,7 @@ void bioCreateCloseJob(int fd, int need_fsync, int need_reclaim_cache) {
|
||||
}
|
||||
|
||||
void bioCreateCloseAofJob(int fd, long long offset, int need_reclaim_cache) {
|
||||
bio_job *job = zmalloc(sizeof(*job));
|
||||
bio_job *job = allocBioJob(0);
|
||||
job->fd_args.fd = fd;
|
||||
job->fd_args.offset = offset;
|
||||
job->fd_args.need_fsync = 1;
|
||||
@@ -219,7 +225,7 @@ void bioCreateCloseAofJob(int fd, long long offset, int need_reclaim_cache) {
|
||||
}
|
||||
|
||||
void bioCreateFsyncJob(int fd, long long offset, int need_reclaim_cache) {
|
||||
bio_job *job = zmalloc(sizeof(*job));
|
||||
bio_job *job = allocBioJob(0);
|
||||
job->fd_args.fd = fd;
|
||||
job->fd_args.offset = offset;
|
||||
job->fd_args.need_reclaim_cache = need_reclaim_cache;
|
||||
@@ -228,14 +234,14 @@ void bioCreateFsyncJob(int fd, long long offset, int need_reclaim_cache) {
|
||||
}
|
||||
|
||||
void bioCreateSaveRDBToDiskJob(connection *conn, int is_dual_channel) {
|
||||
bio_job *job = zmalloc(sizeof(*job));
|
||||
bio_job *job = allocBioJob(0);
|
||||
job->save_to_disk_args.conn = conn;
|
||||
job->save_to_disk_args.is_dual_channel = is_dual_channel;
|
||||
bioSubmitJob(BIO_RDB_SAVE, job);
|
||||
}
|
||||
|
||||
void bioCreateTlsReloadJob(void) {
|
||||
bio_job *job = zmalloc(sizeof(*job));
|
||||
bio_job *job = allocBioJob(0);
|
||||
bioSubmitJob(BIO_TLS_RELOAD, job);
|
||||
}
|
||||
|
||||
@@ -260,9 +266,7 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
bio_worker_num = bioWorkerNum(bwd);
|
||||
|
||||
while (1) {
|
||||
/* Keep the job in the queue until it's fully processed so cancellation
|
||||
* won't leave an untracked in-flight allocation. */
|
||||
bio_job *job = mutexQueuePeek(bwd->bio_jobs, true);
|
||||
bio_job *job = mutexQueuePop(bwd->bio_jobs, true);
|
||||
|
||||
/* Process the job accordingly to its type. */
|
||||
int job_type = job->header.type;
|
||||
@@ -313,8 +317,6 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
} else {
|
||||
serverPanic("Wrong job type in bioProcessBackgroundJobs().");
|
||||
}
|
||||
void *removed_job = mutexQueuePop(bwd->bio_jobs, false);
|
||||
serverAssert(removed_job == job);
|
||||
zfree(job);
|
||||
atomic_fetch_sub(&bio_jobs_counter[job_type], 1);
|
||||
}
|
||||
|
||||
+286
-2
@@ -35,6 +35,44 @@
|
||||
#define REPLY_FLAG_RESP3 (1 << 2)
|
||||
#define REPLY_FLAG_EXACT_TYPE (1 << 3)
|
||||
|
||||
/* ============================================================
|
||||
* Two-level callback architecture
|
||||
* ============================================================
|
||||
*
|
||||
* There are two distinct sets of callbacks used in this file:
|
||||
*
|
||||
* Level 1 – ReplyParserCallbacks (resp_parser.h)
|
||||
* The low-level, recursive-descent parser in resp_parser.c calls these
|
||||
* during a single pass over a raw RESP buffer. Collection callbacks
|
||||
* (array, set, map, attribute) receive a `ReplyParser *` so they can
|
||||
* recurse into child elements by calling parseReply() themselves.
|
||||
* Scalar callbacks receive the raw wire bytes (proto/proto_len) in
|
||||
* addition to the parsed value. Two separate Level-1 callback tables
|
||||
* are defined below:
|
||||
*
|
||||
* CallReplyParserCallbacks – each callback receives the target
|
||||
* CallReply* directly as ctx; used by callReplyParse() to build a
|
||||
* CallReply node tree from a captured RESP buffer. Passing
|
||||
* CallReply* directly makes this path naturally reentrant: a
|
||||
* module that calls VM_Call from inside a VM_CallArgv typed
|
||||
* callback triggers a nested callReplyParse() that operates on an
|
||||
* entirely independent CallReply tree.
|
||||
*
|
||||
* ReplyHandlersParserCallbacks – wires the replyHandlers* adapter
|
||||
* functions; used by invokeReplyHandlers() to dispatch a live
|
||||
* reply to the ValkeyModuleReplyHandlers provided by the module.
|
||||
*
|
||||
* Level 2 – ValkeyModuleReplyHandlers (valkeymodule.h)
|
||||
* The public module API. These user-supplied callbacks receive only
|
||||
* clean, parsed values (no raw RESP bytes, no ReplyParser pointer).
|
||||
* Collection callbacks come in matched Start/End pairs so the module
|
||||
* does not need to drive the parser itself. The replyHandlers*
|
||||
* adapters bridge Level 1 → Level 2: they accept Level-1 arguments,
|
||||
* strip the parser internals, and call the corresponding
|
||||
* ValkeyModuleReplyHandlers callback with just the value arguments.
|
||||
* ============================================================ */
|
||||
|
||||
|
||||
/* --------------------------------------------------------
|
||||
* An opaque struct used to parse a RESP protocol reply and
|
||||
* represent it. Used when parsing replies such as in RM_Call
|
||||
@@ -270,7 +308,13 @@ CallReply *callReplyCreatePromise(void *private_data) {
|
||||
return res;
|
||||
}
|
||||
|
||||
static const ReplyParserCallbacks DefaultParserCallbacks = {
|
||||
/* Callback table used by callReplyParse() to build a CallReply tree from a
|
||||
* captured RESP buffer. Each callback receives the target CallReply* directly
|
||||
* as its opaque ctx argument. Because every callReplyParse() invocation is
|
||||
* stack-local and carries no shared state, this path is naturally reentrant:
|
||||
* a module that calls VM_Call from inside a VM_CallArgv typed callback will
|
||||
* trigger a nested callReplyParse() with an independent CallReply tree. */
|
||||
static const ReplyParserCallbacks CallReplyParserCallbacks = {
|
||||
.null_callback = callReplyNull,
|
||||
.bulk_string_callback = callReplyBulkString,
|
||||
.null_bulk_string_callback = callReplyNullBulkString,
|
||||
@@ -296,7 +340,7 @@ static void callReplyParse(CallReply *rep) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReplyParser parser = {.curr_location = rep->proto, .callbacks = DefaultParserCallbacks};
|
||||
ReplyParser parser = {.curr_location = rep->proto, .callbacks = CallReplyParserCallbacks};
|
||||
|
||||
parseReply(&parser, rep);
|
||||
rep->flags |= REPLY_FLAG_PARSED;
|
||||
@@ -582,3 +626,243 @@ void enableParseExactReplyTypeFlag(CallReply *rep) {
|
||||
serverAssert(!(rep->flags & REPLY_FLAG_PARSED));
|
||||
rep->flags |= REPLY_FLAG_EXACT_TYPE;
|
||||
}
|
||||
|
||||
/* Internal pairing of a const callback table with its associated context.
|
||||
* Created on the stack by callers so the public ValkeyModuleReplyHandlers
|
||||
* struct remains immutable (no context field). The recursive-descent parser
|
||||
* receives a pointer to one of these as its opaque `ctx` argument. */
|
||||
typedef struct {
|
||||
const ValkeyModuleReplyHandlers *handlers;
|
||||
void *context;
|
||||
} RespHandlersCtx;
|
||||
|
||||
static void replyHandlersNull(void *ctx, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->null) return;
|
||||
w->handlers->null(w->context);
|
||||
}
|
||||
|
||||
static void replyHandlersBulkString(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->bulkString) return;
|
||||
w->handlers->bulkString(w->context, str, len);
|
||||
}
|
||||
|
||||
static void replyHandlersNullBulkString(void *ctx, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->nullBulkString) return;
|
||||
w->handlers->nullBulkString(w->context);
|
||||
}
|
||||
|
||||
static void replyHandlersNullArray(void *ctx, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->nullArray) return;
|
||||
w->handlers->nullArray(w->context);
|
||||
}
|
||||
|
||||
static void replyHandlersError(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->error) return;
|
||||
w->handlers->error(w->context, str, len);
|
||||
}
|
||||
|
||||
static void replyHandlersSimpleStr(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->simpleString) return;
|
||||
w->handlers->simpleString(w->context, str, len);
|
||||
}
|
||||
|
||||
static void replyHandlersLong(void *ctx, long long val, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->integer) return;
|
||||
w->handlers->integer(w->context, val);
|
||||
}
|
||||
|
||||
static void replyHandlersArray(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
|
||||
UNUSED(proto);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (w->handlers->arrayStart) w->handlers->arrayStart(w->context, len);
|
||||
/* Always consume all child elements to keep the parser in sync,
|
||||
* regardless of whether the start/end callbacks are set. */
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
parseReply(parser, ctx);
|
||||
}
|
||||
if (w->handlers->arrayEnd) w->handlers->arrayEnd(w->context);
|
||||
}
|
||||
|
||||
static void replyHandlersSet(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
|
||||
UNUSED(proto);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (w->handlers->setStart) w->handlers->setStart(w->context, len);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
parseReply(parser, ctx);
|
||||
}
|
||||
if (w->handlers->setEnd) w->handlers->setEnd(w->context);
|
||||
}
|
||||
|
||||
static void replyHandlersMap(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
|
||||
UNUSED(proto);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (w->handlers->mapStart) w->handlers->mapStart(w->context, len);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
parseReply(parser, ctx);
|
||||
parseReply(parser, ctx);
|
||||
}
|
||||
if (w->handlers->mapEnd) w->handlers->mapEnd(w->context);
|
||||
}
|
||||
|
||||
static void replyHandlersDouble(void *ctx, double val, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->doubleVal) return;
|
||||
w->handlers->doubleVal(w->context, val);
|
||||
}
|
||||
|
||||
static void replyHandlersBool(void *ctx, int val, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->boolVal) return;
|
||||
w->handlers->boolVal(w->context, val);
|
||||
}
|
||||
|
||||
static void replyHandlersBigNumber(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->bigNumber) return;
|
||||
w->handlers->bigNumber(w->context, str, len);
|
||||
}
|
||||
|
||||
static void replyHandlersVerbatimString(void *ctx,
|
||||
const char *format,
|
||||
const char *str,
|
||||
size_t len,
|
||||
const char *proto,
|
||||
size_t proto_len) {
|
||||
UNUSED(proto);
|
||||
UNUSED(proto_len);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->verbatimString) return;
|
||||
w->handlers->verbatimString(w->context, str, len, format);
|
||||
}
|
||||
|
||||
static void replyHandlersAttribute(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
|
||||
UNUSED(proto);
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (w->handlers->attributeStart) w->handlers->attributeStart(w->context, len);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
parseReply(parser, ctx);
|
||||
parseReply(parser, ctx);
|
||||
}
|
||||
if (w->handlers->attributeEnd) w->handlers->attributeEnd(w->context);
|
||||
/* Always parse the element that follows the attribute section. */
|
||||
parseReply(parser, ctx);
|
||||
}
|
||||
|
||||
static void replyHandlersParseError(void *ctx) {
|
||||
RespHandlersCtx *w = (RespHandlersCtx *)ctx;
|
||||
if (!w->handlers->replyParsingError) return;
|
||||
w->handlers->replyParsingError(w->context);
|
||||
}
|
||||
|
||||
static const ReplyParserCallbacks ReplyHandlersParserCallbacks = {
|
||||
.null_callback = replyHandlersNull,
|
||||
.bulk_string_callback = replyHandlersBulkString,
|
||||
.null_bulk_string_callback = replyHandlersNullBulkString,
|
||||
.null_array_callback = replyHandlersNullArray,
|
||||
.error_callback = replyHandlersError,
|
||||
.simple_str_callback = replyHandlersSimpleStr,
|
||||
.long_callback = replyHandlersLong,
|
||||
.array_callback = replyHandlersArray,
|
||||
.set_callback = replyHandlersSet,
|
||||
.map_callback = replyHandlersMap,
|
||||
.double_callback = replyHandlersDouble,
|
||||
.bool_callback = replyHandlersBool,
|
||||
.big_number_callback = replyHandlersBigNumber,
|
||||
.verbatim_string_callback = replyHandlersVerbatimString,
|
||||
.attribute_callback = replyHandlersAttribute,
|
||||
.error = replyHandlersParseError,
|
||||
};
|
||||
|
||||
/* Parse the RESP reply accumulated in client `c`'s output buffer and deliver
|
||||
* it to the handler callbacks in `handlers`.
|
||||
*
|
||||
* If `handlers->onRespAvailable` is set it is called first with the raw
|
||||
* RESP bytes. When it returns 0, per-type callbacks are skipped; when it
|
||||
* returns 1 (or is NULL), the reply is walked recursively and each value is
|
||||
* dispatched to the matching typed callback.
|
||||
*
|
||||
* The client's output buffer is consumed by this call (bufpos reset, reply
|
||||
* list drained). */
|
||||
void invokeReplyHandlers(ValkeyModuleCtx *ctx, client *c, const ValkeyModuleReplyHandlers *handlers, void *reply_ctx) {
|
||||
char *buf = NULL;
|
||||
size_t buf_len = 0;
|
||||
int free_buffer = 0;
|
||||
|
||||
serverAssert(!c->flag.blocked);
|
||||
|
||||
if (listLength(c->reply) == 0 && (size_t)c->bufpos < c->buf_usable_size) {
|
||||
/* This is a fast path for the common case of a reply inside the
|
||||
* client static buffer. Don't create an SDS string but just use
|
||||
* the client buffer directly. */
|
||||
c->buf[c->bufpos] = '\0';
|
||||
buf = c->buf;
|
||||
buf_len = c->bufpos;
|
||||
c->bufpos = 0;
|
||||
} else {
|
||||
listIter iter;
|
||||
listRewind(c->reply, &iter);
|
||||
listNode *node;
|
||||
size_t lensum = c->bufpos;
|
||||
while ((node = listNext(&iter))) {
|
||||
clientReplyBlock *o = listNodeValue(node);
|
||||
lensum += o->used;
|
||||
}
|
||||
buf = zmalloc_usable(lensum + 1, NULL);
|
||||
char *ptr = buf;
|
||||
memcpy(ptr, c->buf, c->bufpos);
|
||||
ptr += c->bufpos;
|
||||
c->bufpos = 0;
|
||||
while (listLength(c->reply)) {
|
||||
clientReplyBlock *o = listNodeValue(listFirst(c->reply));
|
||||
memcpy(ptr, o->buf, o->used);
|
||||
ptr += o->used;
|
||||
listDelNode(c->reply, listFirst(c->reply));
|
||||
}
|
||||
ptr[0] = '\0';
|
||||
buf_len = lensum;
|
||||
free_buffer = 1;
|
||||
}
|
||||
|
||||
int continue_parsing = 1;
|
||||
|
||||
if (handlers->onRespAvailable) {
|
||||
continue_parsing = handlers->onRespAvailable(reply_ctx, ctx, buf, buf_len);
|
||||
}
|
||||
|
||||
if (continue_parsing) {
|
||||
RespHandlersCtx dispatch_ctx = {.handlers = handlers, .context = reply_ctx};
|
||||
ReplyParser parser = {.curr_location = buf, .callbacks = ReplyHandlersParserCallbacks};
|
||||
parseReply(&parser, &dispatch_ctx);
|
||||
}
|
||||
|
||||
if (free_buffer) {
|
||||
zfree(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#define SRC_CALL_REPLY_H_
|
||||
|
||||
#include "resp_parser.h"
|
||||
#include "valkeymodule.h"
|
||||
|
||||
typedef struct CallReply CallReply;
|
||||
typedef void (*ValkeyModuleOnUnblocked)(void *ctx, CallReply *reply, void *private_data);
|
||||
@@ -58,4 +59,6 @@ void freeCallReply(CallReply *rep);
|
||||
CallReply *callReplyCreatePromise(void *private_data);
|
||||
void enableParseExactReplyTypeFlag(CallReply *rep);
|
||||
|
||||
void invokeReplyHandlers(ValkeyModuleCtx *ctx, client *c, const ValkeyModuleReplyHandlers *handlers, void *reply_ctx);
|
||||
|
||||
#endif /* SRC_CALL_REPLY_H_ */
|
||||
|
||||
+210
-1
@@ -41,6 +41,7 @@
|
||||
#include "cluster.h"
|
||||
#include "cluster_slot_stats.h"
|
||||
#include "module.h"
|
||||
#include "crc16_slottable.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -268,7 +269,7 @@ void restoreCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
obj = rdbLoadObject(type, &payload, objectGetVal(key), c->db->id, NULL);
|
||||
obj = rdbLoadObject(type, &payload, objectGetVal(key), c->db->id, NULL, RDBFLAGS_NONE, 0);
|
||||
if (obj == NULL) {
|
||||
addReplyError(c, "Bad data format");
|
||||
return;
|
||||
@@ -1433,6 +1434,11 @@ void addNodeToNodeReply(client *c, clusterNode *node) {
|
||||
hostname[0] != '\0') {
|
||||
length++;
|
||||
}
|
||||
|
||||
if (sdslen(node->availability_zone) != 0) {
|
||||
length++;
|
||||
}
|
||||
|
||||
addReplyMapLen(c, length);
|
||||
|
||||
if (server.cluster_preferred_endpoint_type != CLUSTER_ENDPOINT_TYPE_IP) {
|
||||
@@ -1446,6 +1452,13 @@ void addNodeToNodeReply(client *c, clusterNode *node) {
|
||||
addReplyBulkCString(c, hostname);
|
||||
length--;
|
||||
}
|
||||
|
||||
if (sdslen(node->availability_zone) != 0) {
|
||||
addReplyBulkCString(c, "availability-zone");
|
||||
addReplyBulkCString(c, node->availability_zone);
|
||||
length--;
|
||||
}
|
||||
|
||||
serverAssert(length == 0);
|
||||
}
|
||||
|
||||
@@ -1630,3 +1643,199 @@ void clusterCommandFlushslot(client *c) {
|
||||
delKeysInSlot(slot, lazy, false, true);
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* CLUSTERSCAN Command
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Compute and cache encoded fingerprint for CLUSTERSCAN cursor validation.
|
||||
*
|
||||
* Fingerprint uniquely identifies the current memory layout by hashing
|
||||
* relevant configuration bits. Currently this includes only the hash seed,
|
||||
* but additional factors (e.g., hash table type) can be mixed in later.
|
||||
*
|
||||
* Fingerprint is encoded using consecutive ASCII values starting from '0'
|
||||
* (48 to 111), each represenenting 6 bits. This encoding avoids '-', '{', '}'
|
||||
* which are part of the cursor.
|
||||
*
|
||||
* Returns a non zero 32-bit fingerprint. 0 is reserved for cross-node cursor */
|
||||
static const char *clusterscanFingerprint(void) {
|
||||
static char cached_fp[7];
|
||||
if (cached_fp[0]) return cached_fp;
|
||||
|
||||
uint64_t *seed = (uint64_t *)hashtableGetHashFunctionSeed();
|
||||
uint64_t hash = wangHash64(seed[0] ^ seed[1]);
|
||||
|
||||
/* Truncating to 32 bit instead of 64 bit */
|
||||
uint32_t fp = (uint32_t)hash;
|
||||
|
||||
/* Ensure fingerprint is never 0, zero is reserved for cross node
|
||||
* cursor where fingerprint validation would be skipped, scenarios
|
||||
* include initial and end a given slots scan */
|
||||
if (fp == 0) fp = 1;
|
||||
|
||||
/* Convert 32-bit fingerprint to 6 char string using base64 like encoding. */
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
cached_fp[i] = '0' + (fp & 0x3F);
|
||||
fp >>= 6;
|
||||
}
|
||||
cached_fp[6] = '\0';
|
||||
|
||||
return cached_fp;
|
||||
}
|
||||
|
||||
/* Parse the cursor for CLUSTERSCAN Command.
|
||||
* The format is <fingerprint>-<hashtag>-<cursor>.
|
||||
*
|
||||
* Fingerprint identifies the node's memory layout. On mismatch, the scan
|
||||
* restarts from cursor 0 rather than returning an error.
|
||||
*
|
||||
* Hashtag is used to route to the correct node.
|
||||
*
|
||||
* Cursor is the actual local scan cursor.
|
||||
*/
|
||||
static int parseClusterScanCursor(robj *o, int *slot, unsigned long long *cursor) {
|
||||
char *p = objectGetVal(o);
|
||||
char *end = p + sdslen(p);
|
||||
char *token;
|
||||
|
||||
/* Handle fingerprint */
|
||||
token = strchr(p, '-');
|
||||
if (!token) return C_ERR;
|
||||
|
||||
size_t fp_len = token - p;
|
||||
char *fp_start = p;
|
||||
p = token + 1;
|
||||
|
||||
/* Handle hashtag */
|
||||
token = strchr(p, '-');
|
||||
if (!token) return C_ERR;
|
||||
|
||||
int hash_slot = keyHashSlot(p, token - p);
|
||||
*slot = hash_slot;
|
||||
p = token + 1;
|
||||
|
||||
/* Handle the local cursor */
|
||||
if (!string2ull(p, end - p, cursor)) return C_ERR;
|
||||
|
||||
/* Fingerprint is 0 when beginning to scan a slot so ignore the cursor value
|
||||
* in that case. If fingerprint doesn't match the current node's fingerprint,
|
||||
* restart the scan from cursor 0. From encoding we know fingerprint length is 6.*/
|
||||
const char *fp = clusterscanFingerprint();
|
||||
if (fp_len != 6 || memcmp(fp_start, fp, 6) != 0) {
|
||||
*cursor = 0;
|
||||
}
|
||||
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* CLUSTERSCAN command - topology-aware scan across cluster slots.
|
||||
* Cursor format: <fingerprint>-<hashtag>-<local_cursor>
|
||||
* Supports SLOT, MATCH, COUNT, and TYPE options. */
|
||||
void clusterscanCommand(client *c) {
|
||||
if (!server.cluster_enabled) {
|
||||
addReplyError(c, "This instance has cluster support disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
int slot;
|
||||
unsigned long long cursor;
|
||||
int input_slot = -1;
|
||||
int match_slot = -1;
|
||||
int skip_scan = 0;
|
||||
|
||||
/* Parse all arguments together so that values of MATCH/COUNT/SLOT/TYPE are
|
||||
* not mistaken for the option names.*/
|
||||
for (int i = 2; i < c->argc; i++) {
|
||||
int remaining = c->argc - i;
|
||||
char *opt = objectGetVal(c->argv[i]);
|
||||
|
||||
if (!strcasecmp(opt, "slot") && remaining >= 2) {
|
||||
if (input_slot != -1) {
|
||||
addReplyError(c, "SLOT option can only be specified once");
|
||||
return;
|
||||
}
|
||||
if ((input_slot = getSlotOrReply(c, c->argv[i + 1])) == -1) return;
|
||||
i++;
|
||||
} else if (!strcasecmp(opt, "match") && remaining >= 2) {
|
||||
sds pat = objectGetVal(c->argv[i + 1]);
|
||||
int patlen = sdslen(pat);
|
||||
match_slot = (patlen == 1 && pat[0] == '*') ? -1 : patternHashSlot(pat, patlen);
|
||||
i++;
|
||||
} else if ((!strcasecmp(opt, "count") || !strcasecmp(opt, "type")) && remaining >= 2) {
|
||||
i++; /* Let scanGenericCommand parse this */
|
||||
} else {
|
||||
addReplyErrorObject(c, shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* SLOT and single slot MATCH target different slots hence conclude the scan */
|
||||
skip_scan = input_slot != -1 && match_slot != -1 && input_slot != match_slot;
|
||||
|
||||
/* Handle cursor "0" case. If slot information is provided we return
|
||||
* the updated cursor to scan input slot, else scan from slot 0. */
|
||||
if (strcmp(objectGetVal(c->argv[1]), "0") == 0) {
|
||||
if (input_slot != -1) {
|
||||
slot = input_slot;
|
||||
} else if (match_slot != -1) {
|
||||
slot = match_slot; /* If match maps to a particular slot, start scan from there */
|
||||
} else {
|
||||
slot = 0;
|
||||
}
|
||||
|
||||
addReplyArrayLen(c, 2);
|
||||
if (skip_scan) {
|
||||
addReplyBulkCString(c, "0");
|
||||
} else {
|
||||
sds new_cursor = sdscatfmt(sdsempty(), "0-{%s}-0", crc16_slot_table[slot]);
|
||||
addReplyBulkSds(c, new_cursor);
|
||||
}
|
||||
addReplyArrayLen(c, 0);
|
||||
return;
|
||||
} else {
|
||||
if (parseClusterScanCursor(c->argv[1], &slot, &cursor) == C_ERR) {
|
||||
addReplyError(c, "Invalid cursor");
|
||||
return;
|
||||
}
|
||||
|
||||
if (input_slot != -1 && slot != input_slot) {
|
||||
addReplyError(c, "Cursor slot mismatch with SLOT argument");
|
||||
return;
|
||||
}
|
||||
|
||||
if (match_slot != -1 && slot != match_slot) {
|
||||
/* Advance cursor to the slot matched by MATCH if required but do not go back. */
|
||||
addReplyArrayLen(c, 2);
|
||||
if (!skip_scan && match_slot > slot) {
|
||||
sds new_cursor = sdscatfmt(sdsempty(), "0-{%s}-0", crc16_slot_table[match_slot]);
|
||||
addReplyBulkSds(c, new_cursor);
|
||||
} else {
|
||||
addReplyBulkCString(c, "0");
|
||||
}
|
||||
addReplyArrayLen(c, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scan the slot using scanGenericCommand */
|
||||
sds cursor_prefix = sdscatfmt(sdsempty(), "%s-{%s}-", clusterscanFingerprint(), crc16_slot_table[slot]);
|
||||
sds finished_cursor_prefix = NULL;
|
||||
|
||||
/* If SLOT argument was provided or implied by MATCH, don't advance to next slot then return 0 cursor.
|
||||
* Else, advance to next slot for full cluster scan */
|
||||
if (input_slot != -1 || match_slot != -1) {
|
||||
finished_cursor_prefix = sdsnew("");
|
||||
} else {
|
||||
int next_slot = slot + 1;
|
||||
if (next_slot >= CLUSTER_SLOTS) {
|
||||
finished_cursor_prefix = sdsnew("");
|
||||
} else {
|
||||
finished_cursor_prefix = sdscatfmt(sdsempty(), "0-{%s}-", crc16_slot_table[next_slot]);
|
||||
}
|
||||
}
|
||||
|
||||
scanGenericCommand(c, NULL, cursor, slot, cursor_prefix, finished_cursor_prefix);
|
||||
sdsfree(cursor_prefix);
|
||||
sdsfree(finished_cursor_prefix);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ void clusterUpdateMyselfClientIpV6(void);
|
||||
void clusterUpdateMyselfHostname(void);
|
||||
void clusterUpdateMyselfAnnouncedPorts(void);
|
||||
void clusterUpdateMyselfHumanNodename(void);
|
||||
void clusterUpdateMyselfAvailabilityZone(void);
|
||||
|
||||
void clusterPropagatePublish(robj *channel, robj *message, int sharded);
|
||||
void clusterBroadcastPong(int target);
|
||||
|
||||
+207
-27
@@ -101,7 +101,6 @@ const char *clusterGetMessageTypeString(int type);
|
||||
void removeChannelsInSlot(unsigned int slot);
|
||||
unsigned int countChannelsInSlot(unsigned int hashslot);
|
||||
void clusterAddNodeToShard(const char *shard_id, clusterNode *node);
|
||||
list *clusterLookupNodeListByShardId(const char *shard_id);
|
||||
void clusterRemoveNodeFromShard(clusterNode *node);
|
||||
int auxShardIdSetter(clusterNode *n, void *value, size_t length);
|
||||
sds auxShardIdGetter(clusterNode *n, sds s);
|
||||
@@ -109,6 +108,9 @@ int auxShardIdPresent(clusterNode *n);
|
||||
int auxHumanNodenameSetter(clusterNode *n, void *value, size_t length);
|
||||
sds auxHumanNodenameGetter(clusterNode *n, sds s);
|
||||
int auxHumanNodenamePresent(clusterNode *n);
|
||||
int auxAvailabilityZoneSetter(clusterNode *n, void *value, size_t length);
|
||||
sds auxAvailabilityZoneGetter(clusterNode *n, sds s);
|
||||
int auxAvailabilityZonePresent(clusterNode *n);
|
||||
int auxAnnounceClientIpV4Setter(clusterNode *n, void *value, size_t length);
|
||||
sds auxAnnounceClientIpV4Getter(clusterNode *n, sds s);
|
||||
int auxAnnounceClientIpV4Present(clusterNode *n);
|
||||
@@ -136,6 +138,7 @@ sds clusterEncodeOpenSlotsAuxField(int rdbflags);
|
||||
int clusterDecodeOpenSlotsAuxField(int rdbflags, sds s);
|
||||
static int nodeExceedsHandshakeTimeout(clusterNode *node, mstime_t now);
|
||||
void clusterCommandFlushslot(client *c);
|
||||
int clusterAllReplicasThinkPrimaryIsFail(void);
|
||||
|
||||
static inline clusterMsg *toClusterMsg(void *buf) {
|
||||
clusterMsgHeader *hdr = (clusterMsgHeader *)buf;
|
||||
@@ -155,6 +158,16 @@ int clusterNodeIsVotingPrimary(clusterNode *n) {
|
||||
return (n->flags & CLUSTER_NODE_PRIMARY) && n->numslots;
|
||||
}
|
||||
|
||||
/* Returns if myself is the best ranked replica in an automatic failover process.
|
||||
* To avoid newly added empty replica from affecting the ranking, we will skip it. */
|
||||
static inline int myselfIsBestRankedReplica(void) {
|
||||
return (server.cluster->mf_end == 0 &&
|
||||
getNodeReplicationOffset(myself) != 0 &&
|
||||
server.cluster->failover_auth_rank == 0 &&
|
||||
server.cluster->failover_failed_primary_rank == 0 &&
|
||||
clusterAllReplicasThinkPrimaryIsFail());
|
||||
}
|
||||
|
||||
int getNodeDefaultClientPort(clusterNode *n) {
|
||||
return server.tls_cluster ? n->tls_port : n->tcp_port;
|
||||
}
|
||||
@@ -421,6 +434,7 @@ typedef enum {
|
||||
af_announce_client_ipv6,
|
||||
af_announce_client_tcp_port,
|
||||
af_announce_client_tls_port,
|
||||
af_availability_zone,
|
||||
af_count, /* must be the last field */
|
||||
} auxFieldIndex;
|
||||
|
||||
@@ -437,6 +451,7 @@ auxFieldHandler auxFieldHandlers[] = {
|
||||
{"client-ipv6", auxAnnounceClientIpV6Setter, auxAnnounceClientIpV6Getter, auxAnnounceClientIpV6Present},
|
||||
{"client-tcp-port", auxAnnounceClientTcpPortSetter, auxAnnounceClientTcpPortGetter, auxAnnounceClientTcpPortPresent},
|
||||
{"client-tls-port", auxAnnounceClientTlsPortSetter, auxAnnounceClientTlsPortGetter, auxAnnounceClientTlsPortPresent},
|
||||
{"availability-zone", auxAvailabilityZoneSetter, auxAvailabilityZoneGetter, auxAvailabilityZonePresent},
|
||||
};
|
||||
|
||||
int auxShardIdSetter(clusterNode *n, void *value, size_t length) {
|
||||
@@ -486,6 +501,22 @@ int auxHumanNodenamePresent(clusterNode *n) {
|
||||
return sdslen(n->human_nodename);
|
||||
}
|
||||
|
||||
int auxAvailabilityZoneSetter(clusterNode *n, void *value, size_t length) {
|
||||
if (sdslen(n->availability_zone) == length && !strncmp(value, n->availability_zone, length)) {
|
||||
return C_OK;
|
||||
}
|
||||
n->availability_zone = sdscpylen(n->availability_zone, value, length);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
sds auxAvailabilityZoneGetter(clusterNode *n, sds s) {
|
||||
return sdscat(s, n->availability_zone);
|
||||
}
|
||||
|
||||
int auxAvailabilityZonePresent(clusterNode *n) {
|
||||
return sdslen(n->availability_zone);
|
||||
}
|
||||
|
||||
int auxAnnounceClientIpV4Setter(clusterNode *n, void *value, size_t length) {
|
||||
if (sdslen(n->announce_client_ipv4) == length && !strncmp(value, n->announce_client_ipv4, length)) {
|
||||
/* Unchanged value */
|
||||
@@ -1303,6 +1334,10 @@ static void updateAnnouncedHumanNodename(clusterNode *node, char *value) {
|
||||
updateSdsExtensionField(&node->human_nodename, value);
|
||||
}
|
||||
|
||||
static void updateAvailabilityZone(clusterNode *node, char *value) {
|
||||
updateSdsExtensionField(&node->availability_zone, value);
|
||||
}
|
||||
|
||||
static void updateAnnouncedClientIpV4(clusterNode *node, char *value) {
|
||||
updateSdsExtensionField(&node->announce_client_ipv4, value);
|
||||
}
|
||||
@@ -1388,6 +1423,11 @@ void clusterUpdateMyselfHumanNodename(void) {
|
||||
updateAnnouncedHumanNodename(myself, server.cluster_announce_human_nodename);
|
||||
}
|
||||
|
||||
void clusterUpdateMyselfAvailabilityZone(void) {
|
||||
if (!myself) return;
|
||||
updateAvailabilityZone(myself, server.availability_zone);
|
||||
}
|
||||
|
||||
void clusterUpdateMyselfClientIpV4(void) {
|
||||
if (!myself) return;
|
||||
updateAnnouncedClientIpV4(myself, server.cluster_announce_client_ipv4);
|
||||
@@ -1495,6 +1535,7 @@ void clusterInit(void) {
|
||||
clusterUpdateMyselfClientIpV6();
|
||||
clusterUpdateMyselfHostname();
|
||||
clusterUpdateMyselfHumanNodename();
|
||||
clusterUpdateMyselfAvailabilityZone();
|
||||
resetClusterStats();
|
||||
}
|
||||
|
||||
@@ -1613,18 +1654,13 @@ void clusterHandleServerShutdown(bool auto_failover) {
|
||||
* 4) Only for hard reset: a new Node ID is generated.
|
||||
* 5) Only for hard reset: currentEpoch and configEpoch are set to 0.
|
||||
* 6) The new configuration is saved and the cluster state updated.
|
||||
* 7) If the node was a replica, the whole data set is flushed away. */
|
||||
* 7) If the node was a replica, the whole data set is flushed away.
|
||||
* 8) If it is a hard reset or the node was a replica: a new Shard ID is generated. */
|
||||
void clusterReset(int hard) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int j, was_replica = 0;
|
||||
|
||||
/* Turn into primary. */
|
||||
if (nodeIsReplica(myself)) {
|
||||
was_replica = 1;
|
||||
clusterSetNodeAsPrimary(myself);
|
||||
flushAllDataAndResetRDB(server.lazyfree_lazy_user_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS);
|
||||
}
|
||||
int j;
|
||||
bool new_shard = false;
|
||||
|
||||
/* Close slots, reset manual failover state. */
|
||||
clusterCloseAllSlots();
|
||||
@@ -1664,22 +1700,34 @@ void clusterReset(int hard) {
|
||||
|
||||
/* To change the Node ID we need to remove the old name from the
|
||||
* nodes table, change the ID, and re-add back with new name. */
|
||||
new_shard = true;
|
||||
oldname = sdsnewlen(myself->name, CLUSTER_NAMELEN);
|
||||
dictDelete(server.cluster->nodes, oldname);
|
||||
sdsfree(oldname);
|
||||
getRandomHexChars(myself->name, CLUSTER_NAMELEN);
|
||||
getRandomHexChars(myself->shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNode(myself);
|
||||
serverLog(LL_NOTICE, "Node hard reset, now I'm %.40s", myself->name);
|
||||
} else {
|
||||
/* If we were a replica, this means our shard_id is the shard_id of
|
||||
* the primary node, and since now we become a new empty primary, we
|
||||
* need to have our own shard_id. */
|
||||
if (was_replica) getRandomHexChars(myself->shard_id, CLUSTER_NAMELEN);
|
||||
new_shard = true;
|
||||
}
|
||||
|
||||
/* Re-populate shards */
|
||||
clusterAddNodeToShard(myself->shard_id, myself);
|
||||
if (new_shard) {
|
||||
clusterRemoveNodeFromShard(myself);
|
||||
getRandomHexChars(myself->shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(myself->shard_id, myself);
|
||||
serverLog(LL_NOTICE, "Moving myself to a new shard %.40s.", myself->shard_id);
|
||||
}
|
||||
|
||||
/* Turn into primary. clusterSetNodeAsPrimary prints the shard ID to the
|
||||
* server logs, so calling it here we can print the new correct shard ID. */
|
||||
if (nodeIsReplica(myself)) {
|
||||
clusterSetNodeAsPrimary(myself);
|
||||
flushAllDataAndResetRDB(server.lazyfree_lazy_user_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS);
|
||||
}
|
||||
|
||||
/* Make sure to persist the new config and update the state. */
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_FSYNC_CONFIG);
|
||||
@@ -1917,6 +1965,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
|
||||
node->announce_client_ipv6 = sdsempty();
|
||||
node->hostname = sdsempty();
|
||||
node->human_nodename = sdsempty();
|
||||
node->availability_zone = sdsempty();
|
||||
node->tcp_port = 0;
|
||||
node->cport = 0;
|
||||
node->tls_port = 0;
|
||||
@@ -2155,6 +2204,7 @@ void freeClusterNode(clusterNode *n) {
|
||||
/* Free these members after links are freed, as freeClusterLink may access them. */
|
||||
sdsfree(n->hostname);
|
||||
sdsfree(n->human_nodename);
|
||||
sdsfree(n->availability_zone);
|
||||
sdsfree(n->announce_client_ipv4);
|
||||
sdsfree(n->announce_client_ipv6);
|
||||
raxFree(n->fail_reports);
|
||||
@@ -2496,6 +2546,10 @@ void markNodeAsFailing(clusterNode *node) {
|
||||
|
||||
/* Immediately check if the failing node is our primary node. */
|
||||
if (nodeIsReplica(myself) && myself->replicaof == node) {
|
||||
/* Mark my primary is FAIL so that we can bring out flags during gossip,
|
||||
* so that other nodes know that my primary node has failed, so that other
|
||||
* nodes know that my offset will no longer be updated. */
|
||||
myself->flags |= CLUSTER_NODE_MY_PRIMARY_FAIL;
|
||||
/* We can start an automatic failover as soon as possible, setting a flag
|
||||
* here so that we don't need to waiting for the cron to kick in. */
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER);
|
||||
@@ -2564,6 +2618,7 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
|
||||
serverLog(LL_NOTICE, "Clear FAIL state for node %.40s (%s): %s is reachable again.", node->name,
|
||||
humanNodename(node), nodeIsReplica(node) ? "replica" : "primary without slots");
|
||||
node->flags &= ~CLUSTER_NODE_FAIL;
|
||||
if (nodeIsReplica(myself) && myself->replicaof == node) node->flags &= ~CLUSTER_NODE_MY_PRIMARY_FAIL;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
|
||||
@@ -2578,6 +2633,7 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
|
||||
"Clear FAIL state for node %.40s (%s): is reachable again and nobody is serving its slots after some time.",
|
||||
node->name, humanNodename(node));
|
||||
node->flags &= ~CLUSTER_NODE_FAIL;
|
||||
if (nodeIsReplica(myself) && myself->replicaof == node) node->flags &= ~CLUSTER_NODE_MY_PRIMARY_FAIL;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
}
|
||||
@@ -3381,6 +3437,9 @@ static uint32_t writePingExtensions(clusterMsg *hdr, int gossipcount) {
|
||||
writePortPingExtIfNonzero(&totlen, &cursor, CLUSTERMSG_EXT_TYPE_CLIENT_PORT, myself->announce_client_tcp_port);
|
||||
extensions +=
|
||||
writePortPingExtIfNonzero(&totlen, &cursor, CLUSTERMSG_EXT_TYPE_CLIENT_TLS_PORT, myself->announce_client_tls_port);
|
||||
extensions +=
|
||||
writeSdsPingExtIfNonempty(&totlen, &cursor, CLUSTERMSG_EXT_TYPE_AVAILABILITY_ZONE, myself->availability_zone);
|
||||
|
||||
|
||||
/* Gossip forgotten nodes */
|
||||
if (dictSize(server.cluster->nodes_black_list) > 0) {
|
||||
@@ -3430,6 +3489,7 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
|
||||
clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender, CLUSTER_NAMELEN);
|
||||
char *ext_hostname = NULL;
|
||||
char *ext_humannodename = NULL;
|
||||
char *ext_availability_zone = NULL;
|
||||
char *ext_clientipv4 = NULL;
|
||||
char *ext_clientipv6 = NULL;
|
||||
int ext_clientport = 0;
|
||||
@@ -3481,6 +3541,10 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
|
||||
} else if (type == CLUSTERMSG_EXT_TYPE_SHARDID) {
|
||||
clusterMsgPingExtShardId *shardid_ext = (clusterMsgPingExtShardId *)&(ext->ext[0].shard_id);
|
||||
ext_shardid = shardid_ext->shard_id;
|
||||
} else if (type == CLUSTERMSG_EXT_TYPE_AVAILABILITY_ZONE) {
|
||||
clusterMsgPingExtAvailabilityZone *availability_zone_ext =
|
||||
(clusterMsgPingExtAvailabilityZone *)&(ext->ext[0].availability_zone);
|
||||
ext_availability_zone = availability_zone_ext->availability_zone;
|
||||
} else {
|
||||
/* Unknown type, we will ignore it but log what happened. */
|
||||
serverLog(LL_WARNING, "Received unknown extension type %d", type);
|
||||
@@ -3499,6 +3563,7 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
|
||||
updateAnnouncedClientIpV6(sender, ext_clientipv6);
|
||||
updateAnnouncedClientPort(sender, ext_clientport);
|
||||
updateAnnouncedClientTlsPort(sender, ext_clienttlsport);
|
||||
updateAvailabilityZone(sender, ext_availability_zone);
|
||||
/* If the node did not send us a shard-id extension, it means the sender
|
||||
* does not support it (old version), node->shard_id is randomly generated.
|
||||
* A cluster-wide consensus for the node's shard_id is not necessary.
|
||||
@@ -3629,17 +3694,38 @@ int clusterIsValidPacket(clusterLink *link) {
|
||||
explen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
|
||||
explen += (sizeof(clusterMsgDataGossip) * count);
|
||||
|
||||
/* Make sure that the number of gossip messages fit in the remaining
|
||||
* space in the message. */
|
||||
if (totlen < explen) {
|
||||
serverLog(LL_WARNING,
|
||||
"Received invalid %s packet with gossip count %d that exceeds "
|
||||
"total packet length (%lld)",
|
||||
clusterGetMessageTypeString(type), count, (unsigned long long)totlen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If there is extension data, which doesn't have a fixed length,
|
||||
* loop through them and validate the length of it now. */
|
||||
if (msg->mflags[0] & CLUSTERMSG_FLAG0_EXT_DATA) {
|
||||
clusterMsgPingExt *ext = getInitialPingExt(msg, count);
|
||||
while (extensions--) {
|
||||
/* Make sure there is at least enough memory for the extension information so
|
||||
* we can parse it. */
|
||||
if ((totlen - explen) < sizeof(clusterMsgPingExt)) {
|
||||
serverLog(LL_WARNING,
|
||||
"Received invalid %s packet with extension data that exceeds "
|
||||
"total packet length (%lld)",
|
||||
clusterGetMessageTypeString(type), (unsigned long long)totlen);
|
||||
return 0;
|
||||
}
|
||||
uint32_t extlen = getPingExtLength(ext);
|
||||
if (extlen % 8 != 0) {
|
||||
serverLog(LL_WARNING, "Received a %s packet without proper padding (%d bytes)",
|
||||
clusterGetMessageTypeString(type), (int)extlen);
|
||||
return 0;
|
||||
}
|
||||
/* Similar check to earlier, but we want to make sure the extension length is valid
|
||||
* this time. */
|
||||
if ((totlen - explen) < extlen) {
|
||||
serverLog(LL_WARNING,
|
||||
"Received invalid %s packet with extension data that exceeds "
|
||||
@@ -3794,6 +3880,13 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
} else {
|
||||
sender->flags &= ~CLUSTER_NODE_MULTI_MEET_SUPPORTED;
|
||||
}
|
||||
|
||||
/* Check if the sender has marked its primary node as FAIL. */
|
||||
if (flags & CLUSTER_NODE_MY_PRIMARY_FAIL) {
|
||||
sender->flags |= CLUSTER_NODE_MY_PRIMARY_FAIL;
|
||||
} else {
|
||||
sender->flags &= ~CLUSTER_NODE_MY_PRIMARY_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the last time we saw any data from this node. We
|
||||
@@ -4028,6 +4121,9 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
* what are the instances really competing. */
|
||||
if (sender) {
|
||||
int nofailover = flags & CLUSTER_NODE_NOFAILOVER;
|
||||
if (nofailover != clusterNodeIsNoFailover(sender)) {
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
sender->flags &= ~CLUSTER_NODE_NOFAILOVER;
|
||||
sender->flags |= nofailover;
|
||||
}
|
||||
@@ -4077,13 +4173,14 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
/* Primary turned into a replica! Reconfigure the node. */
|
||||
if (sender_claimed_primary && areInSameShard(sender_claimed_primary, sender)) {
|
||||
/* `sender` was a primary and was in the same shard as its new primary */
|
||||
if (nodeEpoch(sender_claimed_primary) > sender_claimed_config_epoch) {
|
||||
if (nodeEpoch(sender) > sender_claimed_config_epoch ||
|
||||
nodeEpoch(sender_claimed_primary) > sender_claimed_config_epoch) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Ignore stale message from %.40s (%s) in shard %.40s;"
|
||||
" gossip config epoch: %llu, current config epoch: %llu",
|
||||
sender->name, humanNodename(sender), sender->shard_id,
|
||||
(unsigned long long)sender_claimed_config_epoch,
|
||||
(unsigned long long)nodeEpoch(sender_claimed_primary));
|
||||
(unsigned long long)max(nodeEpoch(sender), nodeEpoch(sender_claimed_primary)));
|
||||
/* This packet is stale so we avoid processing it anymore. Otherwise
|
||||
* this may cause a primary-replica chain issue. */
|
||||
return 1;
|
||||
@@ -4240,6 +4337,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
for (size_t w = 0; w < CLUSTER_SLOT_WORDS && !found_new_owner; w++) {
|
||||
uint64_t word;
|
||||
memcpy(&word, msg->myslots + SLOT_WORD_OFFSET(w), sizeof(word));
|
||||
memrev64ifbe(&word);
|
||||
while (word) {
|
||||
const int slot = clusterExtractSlotFromWord(&word, w);
|
||||
|
||||
@@ -4381,6 +4479,7 @@ void clusterWriteHandler(connection *conn) {
|
||||
|
||||
nwritten = connWrite(conn, (char *)msg + msg_offset, msg_len - msg_offset);
|
||||
if (nwritten <= 0) {
|
||||
if (nwritten == -1 && connGetState(conn) == CONN_STATE_CONNECTED) return; /* equivalent to EAGAIN */
|
||||
serverLog(LL_DEBUG, "I/O error writing to node link: %s",
|
||||
(nwritten == -1) ? connGetLastError(conn) : "short write");
|
||||
handleLinkIOError(link);
|
||||
@@ -4737,8 +4836,9 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
*
|
||||
* Since we have non-voting replicas that lower the probability of an entry
|
||||
* to feature our node, we set the number of entries per packet as
|
||||
* 10% of the total nodes we have. */
|
||||
wanted = floor(dictSize(server.cluster->nodes) / 10);
|
||||
* a configurable percentage (default 10%) of the total nodes we have,
|
||||
* bounded by the actual number of known nodes. */
|
||||
wanted = (dictSize(server.cluster->nodes) * server.cluster_message_gossip_perc / 100);
|
||||
if (wanted < 3) wanted = 3;
|
||||
if (wanted > freshnodes) wanted = freshnodes;
|
||||
|
||||
@@ -4769,11 +4869,21 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
link->node->meet_sent = mstime();
|
||||
}
|
||||
|
||||
/* Populate the gossip fields */
|
||||
int maxiterations = wanted * 3;
|
||||
while (freshnodes > 0 && gossipcount < wanted && maxiterations--) {
|
||||
dictEntry *de = dictGetRandomKey(server.cluster->nodes);
|
||||
clusterNode *this = dictGetVal(de);
|
||||
/* Populate the gossip fields.
|
||||
* Use dictGetSomeKeys() to sample candidates in a single batch instead
|
||||
* of calling dictGetRandomKey() in a retry loop. We over-allocate to
|
||||
* have enough candidates after filtering out ineligible nodes.
|
||||
* dictGetSomeKeys() picks a random starting point each call, so over
|
||||
* many ping rounds all nodes get even coverage without needing an
|
||||
* explicit shuffle. */
|
||||
int candidates_wanted = wanted + 2; /* +2 for myself and link->node */
|
||||
if (candidates_wanted > (int)dictSize(server.cluster->nodes))
|
||||
candidates_wanted = dictSize(server.cluster->nodes);
|
||||
dictEntry **candidates = zmalloc(sizeof(dictEntry *) * candidates_wanted);
|
||||
unsigned int ncandidates = dictGetSomeKeys(server.cluster->nodes, candidates, candidates_wanted);
|
||||
|
||||
for (unsigned int i = 0; i < ncandidates && gossipcount < wanted; i++) {
|
||||
clusterNode *this = dictGetVal(candidates[i]);
|
||||
|
||||
/* Don't include this node: the whole packet header is about us
|
||||
* already, so we just gossip about other nodes.
|
||||
@@ -4791,7 +4901,6 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
*/
|
||||
if (this->flags & (CLUSTER_NODE_HANDSHAKE | CLUSTER_NODE_NOADDR) ||
|
||||
(this->link == NULL && this->numslots == 0)) {
|
||||
freshnodes--; /* Technically not correct, but saves CPU. */
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4801,9 +4910,9 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
/* Add it */
|
||||
clusterSetGossipEntry(hdr, gossipcount, this);
|
||||
this->last_in_ping_gossip = cluster_pings_sent;
|
||||
freshnodes--;
|
||||
gossipcount++;
|
||||
}
|
||||
zfree(candidates);
|
||||
|
||||
/* If there are PFAIL nodes, add them at the end. */
|
||||
if (pfail_wanted) {
|
||||
@@ -5122,6 +5231,16 @@ void clusterRequestFailoverAuth(void) {
|
||||
* in the header to communicate the nodes receiving the message that
|
||||
* they should authorized the failover even if the primary is working. */
|
||||
if (server.cluster->mf_end) msgblock->data[0].msg.mflags[0] |= CLUSTERMSG_FLAG0_FORCEACK;
|
||||
|
||||
/* If this is an automatic failover and if myself is the best ranked replica,
|
||||
* set the CLUSTERMSG_FLAG0_FORCEACK bit in the header as well.
|
||||
*
|
||||
* In this case, we hope that other primary nodes will not refuse to vote because
|
||||
* they did not receive the FAIL message in time. */
|
||||
if (server.cluster->mf_end == 0 && myselfIsBestRankedReplica()) {
|
||||
msgblock->data[0].msg.mflags[0] |= CLUSTERMSG_FLAG0_FORCEACK;
|
||||
}
|
||||
|
||||
clusterBroadcastMessage(msgblock);
|
||||
clusterMsgSendBlockDecrRefCount(msgblock);
|
||||
}
|
||||
@@ -5211,6 +5330,7 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
|
||||
for (size_t w = 0; w < CLUSTER_SLOT_WORDS; w++) {
|
||||
uint64_t word;
|
||||
memcpy(&word, claimed_slots + SLOT_WORD_OFFSET(w), sizeof(word));
|
||||
memrev64ifbe(&word);
|
||||
while (word) {
|
||||
slot = clusterExtractSlotFromWord(&word, w);
|
||||
|
||||
@@ -5326,6 +5446,29 @@ int clusterGetFailedPrimaryRank(void) {
|
||||
return rank;
|
||||
}
|
||||
|
||||
|
||||
/* Returns 1 if all replicas under my primary think the primary is in FAIL state.
|
||||
*
|
||||
* This is useful in automatic failover. For example, from my perspective,
|
||||
* if all other replicas, including myself, both mark the primary node as FAIL,
|
||||
* which means that myself and other replicas have exchanged new gossip information
|
||||
* after the primary node went down, and we know the latest replication offset of
|
||||
* the replicas. If a replica finds that its ranking is optimal in all cases, then
|
||||
* the replica can initiate an election immediately in automatic failover without
|
||||
* waiting for the delay. */
|
||||
int clusterAllReplicasThinkPrimaryIsFail(void) {
|
||||
serverAssert(nodeIsReplica(myself));
|
||||
serverAssert(myself->replicaof);
|
||||
|
||||
clusterNode *primary = myself->replicaof;
|
||||
for (int i = 0; i < primary->num_replicas; i++) {
|
||||
if (!nodePrimaryIsFail(primary->replicas[i])) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* This function is called by clusterHandleReplicaFailover() in order to
|
||||
* let the replica log why it is not able to failover. Sometimes there are
|
||||
* not the conditions, but since the failover function is called again and
|
||||
@@ -5539,10 +5682,22 @@ void clusterHandleReplicaFailover(void) {
|
||||
server.cluster->failover_auth_time = now;
|
||||
server.cluster->failover_auth_rank = 0;
|
||||
server.cluster->failover_failed_primary_rank = 0;
|
||||
/* Reset auth_age since it is outdated now and we can bypass the auth_timeout
|
||||
}
|
||||
|
||||
if (server.cluster->mf_end == 0 && myselfIsBestRankedReplica()) {
|
||||
/* If we find that myself is the best ranked replica, we can initiate the
|
||||
* failover immediately. */
|
||||
server.cluster->failover_auth_time = now;
|
||||
serverLog(LL_NOTICE, "This is the best ranked replica and can initiate the election immediately.");
|
||||
}
|
||||
|
||||
if (server.cluster->failover_auth_time == now) {
|
||||
/* If we happen to initiate a failover (automatic or manual) immediately.
|
||||
* Reset auth_age since it is outdated now and we can bypass the auth_timeout
|
||||
* check in the next state and start the election ASAP. */
|
||||
auth_age = 0;
|
||||
}
|
||||
|
||||
serverLog(LL_NOTICE,
|
||||
"Start of election delayed for %lld milliseconds "
|
||||
"(rank #%d, primary rank #%d, offset %lld).",
|
||||
@@ -5566,8 +5721,13 @@ void clusterHandleReplicaFailover(void) {
|
||||
* It is also possible that we received the message that telling a
|
||||
* shard is up. Update the delay if our failed_primary_rank changed.
|
||||
*
|
||||
* It is also possible that we received more message and then we figure
|
||||
* out myself is the best ranked replica, in this case, we can initiate
|
||||
* the election immediately.
|
||||
*
|
||||
* Not performed if this is a manual failover. */
|
||||
if (server.cluster->failover_auth_sent == 0 && server.cluster->mf_end == 0) {
|
||||
if (server.cluster->failover_auth_sent == 0 && server.cluster->mf_end == 0 &&
|
||||
server.cluster->failover_auth_time != now) {
|
||||
int newrank = clusterGetReplicaRank();
|
||||
if (newrank != server.cluster->failover_auth_rank) {
|
||||
long long added_delay = (newrank - server.cluster->failover_auth_rank) * (delay * 2);
|
||||
@@ -5585,6 +5745,13 @@ void clusterHandleReplicaFailover(void) {
|
||||
serverLog(LL_NOTICE, "Failed primary rank updated to #%d, added %lld milliseconds of delay.",
|
||||
new_failed_primary_rank, added_delay);
|
||||
}
|
||||
|
||||
if (myselfIsBestRankedReplica()) {
|
||||
/* If we find that myself is the best ranked replica, we can initiate the
|
||||
* failover immediately. */
|
||||
server.cluster->failover_auth_time = now;
|
||||
serverLog(LL_NOTICE, "Myself become the best ranked replica, initiate the election immediately.");
|
||||
}
|
||||
}
|
||||
|
||||
/* Return ASAP if we can't still start the election. */
|
||||
@@ -6186,7 +6353,13 @@ void clusterBeforeSleep(void) {
|
||||
/* Save the config, possibly using fsync. */
|
||||
if (flags & CLUSTER_TODO_SAVE_CONFIG) {
|
||||
int fsync = flags & CLUSTER_TODO_FSYNC_CONFIG;
|
||||
clusterSaveConfigOrLog(fsync);
|
||||
if (server.cluster_configfile_save_behavior == CLUSTER_CONFIGFILE_SAVE_BEHAVIOR_SYNC) {
|
||||
/* Sync mode: exit the process if saving fails. */
|
||||
clusterSaveConfigOrDie(fsync);
|
||||
} else if (server.cluster_configfile_save_behavior == CLUSTER_CONFIGFILE_SAVE_BEHAVIOR_BEST_EFFORT) {
|
||||
/* Best-effort mode: log (don't exit) if saving fails and wait for the next retry. */
|
||||
clusterSaveConfigOrLog(fsync);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & CLUSTER_TODO_BROADCAST_ALL) {
|
||||
@@ -7071,6 +7244,12 @@ void addNodeDetailsToShardReply(client *c, clusterNode *node) {
|
||||
addReplyBulkCString(c, health_msg);
|
||||
reply_count++;
|
||||
|
||||
if (sdslen(node->availability_zone) != 0) {
|
||||
addReplyBulkCString(c, "availability-zone");
|
||||
addReplyBulkCBuffer(c, node->availability_zone, sdslen(node->availability_zone));
|
||||
reply_count++;
|
||||
}
|
||||
|
||||
setDeferredMapLen(c, node_replylen, reply_count);
|
||||
}
|
||||
|
||||
@@ -7882,6 +8061,7 @@ int clusterCommandSpecial(client *c) {
|
||||
char new_shard_id[CLUSTER_NAMELEN];
|
||||
getRandomHexChars(new_shard_id, CLUSTER_NAMELEN);
|
||||
updateShardId(myself, new_shard_id);
|
||||
serverLog(LL_NOTICE, "Moving myself to a new shard %.40s.", myself->shard_id);
|
||||
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_BROADCAST_ALL);
|
||||
addReply(c, shared.ok);
|
||||
|
||||
@@ -65,6 +65,11 @@ typedef struct clusterLink {
|
||||
#define CLUSTER_NODE_MULTI_MEET_SUPPORTED CLUSTER_NODE_LIGHT_HDR_MODULE_SUPPORTED /* This node handles multi meet packet. \
|
||||
Light hdr for module and multi meet were both introduced in 8.1, \
|
||||
so we could reduce the same flag value. */
|
||||
#define CLUSTER_NODE_MY_PRIMARY_FAIL (1 << 13) /* myself is a replica and my primary is FAIL in my view. \
|
||||
* myself will gossip this flag to other replica in the \
|
||||
* shard so that the replicas can make a better ranking \
|
||||
* decisions to help with the failover. */
|
||||
|
||||
#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"
|
||||
@@ -80,6 +85,7 @@ typedef struct clusterLink {
|
||||
#define nodeSupportsExtensions(n) ((n)->flags & CLUSTER_NODE_EXTENSIONS_SUPPORTED)
|
||||
#define nodeSupportsMultiMeet(n) ((n)->flags & CLUSTER_NODE_MULTI_MEET_SUPPORTED)
|
||||
#define nodeInNormalState(n) (!((n)->flags & (CLUSTER_NODE_HANDSHAKE | CLUSTER_NODE_MEET | CLUSTER_NODE_PFAIL | CLUSTER_NODE_FAIL)))
|
||||
#define nodePrimaryIsFail(n) ((n)->flags & CLUSTER_NODE_MY_PRIMARY_FAIL)
|
||||
|
||||
/* Cluster messages header */
|
||||
|
||||
@@ -166,6 +172,7 @@ typedef enum {
|
||||
CLUSTERMSG_EXT_TYPE_CLIENT_IPV6,
|
||||
CLUSTERMSG_EXT_TYPE_CLIENT_PORT,
|
||||
CLUSTERMSG_EXT_TYPE_CLIENT_TLS_PORT,
|
||||
CLUSTERMSG_EXT_TYPE_AVAILABILITY_ZONE,
|
||||
} clusterMsgPingtypes;
|
||||
|
||||
/* Helper function for making sure extensions are eight byte aligned. */
|
||||
@@ -179,6 +186,10 @@ typedef struct {
|
||||
char human_nodename[1]; /* The announced nodename, ends with \0. */
|
||||
} clusterMsgPingExtHumanNodename;
|
||||
|
||||
typedef struct {
|
||||
char availability_zone[1]; /* The availability zone, ends with \0. */
|
||||
} clusterMsgPingExtAvailabilityZone;
|
||||
|
||||
typedef struct {
|
||||
char name[CLUSTER_NAMELEN]; /* Node name. */
|
||||
uint64_t ttl; /* Remaining time to blacklist the node, in seconds. */
|
||||
@@ -219,6 +230,7 @@ typedef struct {
|
||||
clusterMsgPingExtClientIpV6 announce_client_ipv6;
|
||||
clusterMsgPingExtClientPort announce_client_port;
|
||||
clusterMsgPingExtClientTlsPort announce_client_tls_port;
|
||||
clusterMsgPingExtAvailabilityZone availability_zone;
|
||||
} ext[]; /* Actual extension information, formatted so that the data is 8
|
||||
* byte aligned, regardless of its content. */
|
||||
} clusterMsgPingExt;
|
||||
@@ -392,6 +404,7 @@ struct _clusterNode {
|
||||
sds announce_client_ipv6; /* IPv6 for clients only. */
|
||||
sds hostname; /* The known hostname for this node */
|
||||
sds human_nodename; /* The known human readable nodename for this node */
|
||||
sds availability_zone; /* The known availability zone for this node */
|
||||
int tcp_port; /* Latest known clients TCP port. */
|
||||
int tls_port; /* Latest known clients TLS port */
|
||||
int cport; /* Latest known cluster port of this node. */
|
||||
|
||||
@@ -1410,9 +1410,9 @@ sds generateSyncSlotsEstablishCommand(slotMigrationJob *job) {
|
||||
listRewind(job->slot_ranges, &li);
|
||||
while ((ln = listNext(&li))) {
|
||||
slotRange *range = (slotRange *)ln->value;
|
||||
sdscatfmt(result, "$%i\r\n%i\r\n$%i\r\n%i\r\n",
|
||||
digits10(range->start_slot), range->start_slot,
|
||||
digits10(range->end_slot), range->end_slot);
|
||||
result = sdscatfmt(result, "$%i\r\n%i\r\n$%i\r\n%i\r\n",
|
||||
digits10(range->start_slot), range->start_slot,
|
||||
digits10(range->end_slot), range->end_slot);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1448,8 +1448,8 @@ int slotExportTryDoPause(slotMigrationJob *job) {
|
||||
return C_ERR;
|
||||
}
|
||||
serverLog(LL_NOTICE,
|
||||
"Pausing writes to allow slot migration %s to finalize failover.",
|
||||
job->description);
|
||||
"Pausing writes (remaining_repl_size is %lld) to allow slot migration %s to finalize failover.",
|
||||
job->client->reply_bytes, job->description);
|
||||
job->mf_end = mstime() + server.cluster_mf_timeout * CLUSTER_MF_PAUSE_MULT;
|
||||
pauseActions(PAUSE_DURING_SLOT_MIGRATION, job->mf_end,
|
||||
PAUSE_ACTIONS_CLIENT_WRITE_SET);
|
||||
@@ -2145,6 +2145,7 @@ void resetSlotMigrationJob(slotMigrationJob *job) {
|
||||
/* Only one of client or conn should be set. */
|
||||
serverAssert(!job->client || !job->conn);
|
||||
if (job->client) {
|
||||
job->client->slot_migration_job = NULL;
|
||||
freeClientAsync(job->client);
|
||||
job->client = NULL;
|
||||
} else if (job->conn) {
|
||||
|
||||
+5
-3
@@ -43,9 +43,11 @@ static commandlogEntry *commandlogCreateEntry(client *c, robj **argv, int argc,
|
||||
ce->argv[j] =
|
||||
createObject(OBJ_STRING, sdscatprintf(sdsempty(), "... (%d more arguments)", argc - ceargc + 1));
|
||||
} else {
|
||||
/* Trim too long strings as well... */
|
||||
if (argv[j]->type == OBJ_STRING && sdsEncodedObject(argv[j]) &&
|
||||
sdslen(objectGetVal(argv[j])) > COMMANDLOG_ENTRY_MAX_STRING) {
|
||||
if (clientCommandArgShouldBeRedacted(c, j)) {
|
||||
ce->argv[j] = shared.redacted;
|
||||
/* Trim too long strings as well... */
|
||||
} else if (argv[j]->type == OBJ_STRING && sdsEncodedObject(argv[j]) &&
|
||||
sdslen(objectGetVal(argv[j])) > COMMANDLOG_ENTRY_MAX_STRING) {
|
||||
sds s = sdsnewlen(objectGetVal(argv[j]), COMMANDLOG_ENTRY_MAX_STRING);
|
||||
|
||||
s = sdscatprintf(s, "... (%lu more bytes)",
|
||||
|
||||
+115
-29
@@ -1007,7 +1007,7 @@ struct COMMAND_ARG CLUSTER_SETSLOT_Args[] = {
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* CLUSTER SHARDS history */
|
||||
commandHistory CLUSTER_SHARDS_History[] = {
|
||||
{"9.1.0","Added shard id field to CLUSTER SHARDS response"},
|
||||
{"9.1.0","Added shard level `id` field and node level `availability-zone` field."},
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -1104,6 +1104,7 @@ struct COMMAND_ARG CLUSTER_SLOT_STATS_Args[] = {
|
||||
commandHistory CLUSTER_SLOTS_History[] = {
|
||||
{"4.0.0","Added node IDs."},
|
||||
{"7.0.0","Added additional networking metadata field."},
|
||||
{"9.1.0","Added `availability-zone` field inside the networking metadata."},
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -1147,11 +1148,11 @@ struct COMMAND_STRUCT CLUSTER_Subcommands[] = {
|
||||
{MAKE_CMD("delslots","Sets hash slots as unbound for a node.","O(N) where N is the total number of hash slot arguments","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_DELSLOTS_History,0,CLUSTER_DELSLOTS_Tips,0,clusterCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_DELSLOTS_Keyspecs,0,NULL,1),.args=CLUSTER_DELSLOTS_Args},
|
||||
{MAKE_CMD("delslotsrange","Sets hash slot ranges as unbound for a node.","O(N) where N is the total number of the slots between the start slot and end slot arguments.","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_DELSLOTSRANGE_History,0,CLUSTER_DELSLOTSRANGE_Tips,0,clusterCommand,-4,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_DELSLOTSRANGE_Keyspecs,0,NULL,1),.args=CLUSTER_DELSLOTSRANGE_Args},
|
||||
{MAKE_CMD("failover","Forces a replica to perform a manual failover of its primary.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_FAILOVER_History,0,CLUSTER_FAILOVER_Tips,0,clusterCommand,-2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_FAILOVER_Keyspecs,0,NULL,1),.args=CLUSTER_FAILOVER_Args},
|
||||
{MAKE_CMD("flushslot","Remove all keys from the target slot.","O(N) where N is the number of keys in the target slot","9.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_FLUSHSLOT_History,0,CLUSTER_FLUSHSLOT_Tips,0,clusterCommand,-3,CMD_WRITE|CMD_NO_ASYNC_LOADING|CMD_ADMIN,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,CLUSTER_FLUSHSLOT_Keyspecs,0,NULL,2),.args=CLUSTER_FLUSHSLOT_Args},
|
||||
{MAKE_CMD("flushslot","Removes all keys from the target slot.","O(N) where N is the number of keys in the target slot","9.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_FLUSHSLOT_History,0,CLUSTER_FLUSHSLOT_Tips,0,clusterCommand,-3,CMD_WRITE|CMD_NO_ASYNC_LOADING|CMD_ADMIN,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,CLUSTER_FLUSHSLOT_Keyspecs,0,NULL,2),.args=CLUSTER_FLUSHSLOT_Args},
|
||||
{MAKE_CMD("flushslots","Deletes all slots information from a node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_FLUSHSLOTS_History,0,CLUSTER_FLUSHSLOTS_Tips,0,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_FLUSHSLOTS_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("forget","Removes a node from the nodes table.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_FORGET_History,0,CLUSTER_FORGET_Tips,0,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_FORGET_Keyspecs,0,NULL,1),.args=CLUSTER_FORGET_Args},
|
||||
{MAKE_CMD("getkeysinslot","Returns the key names in a hash slot.","O(N) where N is the number of requested keys","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_GETKEYSINSLOT_History,0,CLUSTER_GETKEYSINSLOT_Tips,1,clusterCommand,4,CMD_STALE,ACL_CATEGORY_SLOW,NULL,CLUSTER_GETKEYSINSLOT_Keyspecs,0,NULL,2),.args=CLUSTER_GETKEYSINSLOT_Args},
|
||||
{MAKE_CMD("getslotmigrations","Get the status of ongoing and recently finished slot import and export operations.","O(N), where N is the number of active slot import and export jobs.","9.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_GETSLOTMIGRATIONS_History,0,CLUSTER_GETSLOTMIGRATIONS_Tips,0,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_GETSLOTMIGRATIONS_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("getslotmigrations","Gets the status of ongoing and recently finished slot import and export operations.","O(N), where N is the number of active slot import and export jobs.","9.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_GETSLOTMIGRATIONS_History,0,CLUSTER_GETSLOTMIGRATIONS_Tips,0,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_GETSLOTMIGRATIONS_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_HELP_History,0,CLUSTER_HELP_Tips,0,clusterCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,CLUSTER_HELP_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("info","Returns information about the state of a node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_INFO_History,0,CLUSTER_INFO_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,CLUSTER_INFO_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("keyslot","Returns the hash slot for a key.","O(N) where N is the number of bytes in the key","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_KEYSLOT_History,0,CLUSTER_KEYSLOT_Tips,0,clusterKeySlotCommand,3,CMD_STALE,ACL_CATEGORY_SLOW,NULL,CLUSTER_KEYSLOT_Keyspecs,0,NULL,1),.args=CLUSTER_KEYSLOT_Args},
|
||||
@@ -1162,7 +1163,7 @@ struct COMMAND_STRUCT CLUSTER_Subcommands[] = {
|
||||
{MAKE_CMD("myshardid","Returns the shard ID of a node.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_MYSHARDID_History,0,CLUSTER_MYSHARDID_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,CLUSTER_MYSHARDID_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("nodes","Returns the cluster configuration for a node.","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_NODES_History,0,CLUSTER_NODES_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,CLUSTER_NODES_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("replicas","Lists the replica nodes of a primary node.","O(N) where N is the number of replicas.","5.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_REPLICAS_History,0,CLUSTER_REPLICAS_Tips,1,clusterCommand,3,CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_REPLICAS_Keyspecs,0,NULL,1),.args=CLUSTER_REPLICAS_Args},
|
||||
{MAKE_CMD("replicate","Configure a node as replica of a primary node or detach a replica from its primary.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_REPLICATE_History,1,CLUSTER_REPLICATE_Tips,0,clusterCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_REPLICATE_Keyspecs,0,NULL,1),.args=CLUSTER_REPLICATE_Args},
|
||||
{MAKE_CMD("replicate","Configures a node as replica of a primary node or detaches a replica from its primary.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_REPLICATE_History,1,CLUSTER_REPLICATE_Tips,0,clusterCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_REPLICATE_Keyspecs,0,NULL,1),.args=CLUSTER_REPLICATE_Args},
|
||||
{MAKE_CMD("reset","Resets a node.","O(N) where N is the number of known nodes. The command may execute a FLUSHALL as a side effect.","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_RESET_History,0,CLUSTER_RESET_Tips,0,clusterCommand,-2,CMD_ADMIN|CMD_STALE|CMD_NOSCRIPT,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_RESET_Keyspecs,0,NULL,1),.args=CLUSTER_RESET_Args},
|
||||
{MAKE_CMD("saveconfig","Forces a node to save the cluster configuration to disk.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SAVECONFIG_History,0,CLUSTER_SAVECONFIG_Tips,0,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_SAVECONFIG_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("set-config-epoch","Sets the configuration epoch for a new node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SET_CONFIG_EPOCH_History,0,CLUSTER_SET_CONFIG_EPOCH_Tips,0,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_SET_CONFIG_EPOCH_Keyspecs,0,NULL,1),.args=CLUSTER_SET_CONFIG_EPOCH_Args},
|
||||
@@ -1170,7 +1171,7 @@ struct COMMAND_STRUCT CLUSTER_Subcommands[] = {
|
||||
{MAKE_CMD("shards","Returns the mapping of cluster slots to shards.","O(N) where N is the total number of cluster nodes","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SHARDS_History,1,CLUSTER_SHARDS_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,CLUSTER_SHARDS_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("slaves","Lists the replica nodes of a primary node.","O(N) where N is the number of replicas.","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER REPLICAS`","5.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLAVES_History,0,CLUSTER_SLAVES_Tips,1,clusterCommand,3,CMD_ADMIN|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_SLAVES_Keyspecs,0,NULL,1),.args=CLUSTER_SLAVES_Args},
|
||||
{MAKE_CMD("slot-stats","Return an array of slot usage statistics for slots assigned to the current node.","O(N) where N is the total number of slots based on arguments. O(N*log(N)) with ORDERBY subcommand.","8.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLOT_STATS_History,0,CLUSTER_SLOT_STATS_Tips,2,clusterSlotStatsCommand,-4,CMD_STALE|CMD_LOADING,ACL_CATEGORY_SLOW,NULL,CLUSTER_SLOT_STATS_Keyspecs,0,NULL,1),.args=CLUSTER_SLOT_STATS_Args},
|
||||
{MAKE_CMD("slots","Returns the mapping of cluster slots to nodes.","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLOTS_History,2,CLUSTER_SLOTS_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,CLUSTER_SLOTS_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("slots","Returns the mapping of cluster slots to nodes.","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLOTS_History,3,CLUSTER_SLOTS_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,CLUSTER_SLOTS_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("syncslots","A container for internal slot migration commands.","Depends on subcommand.","9.0.0",CMD_DOC_SYSCMD,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SYNCSLOTS_History,0,CLUSTER_SYNCSLOTS_Tips,0,clusterCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE|CMD_MAY_REPLICATE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLUSTER_SYNCSLOTS_Keyspecs,0,NULL,0)},
|
||||
{0}
|
||||
};
|
||||
@@ -1192,6 +1193,38 @@ struct COMMAND_STRUCT CLUSTER_Subcommands[] = {
|
||||
#define CLUSTER_Keyspecs NULL
|
||||
#endif
|
||||
|
||||
/********** CLUSTERSCAN ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* CLUSTERSCAN history */
|
||||
#define CLUSTERSCAN_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* CLUSTERSCAN tips */
|
||||
const char *CLUSTERSCAN_Tips[] = {
|
||||
"nondeterministic_output",
|
||||
"request_policy:special",
|
||||
"response_policy:special",
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* CLUSTERSCAN key specs */
|
||||
keySpec CLUSTERSCAN_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_NOT_KEY,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* CLUSTERSCAN argument table */
|
||||
struct COMMAND_ARG CLUSTERSCAN_Args[] = {
|
||||
{MAKE_ARG("cursor",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("match-pattern",ARG_TYPE_PATTERN,-1,"MATCH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
{MAKE_ARG("type",ARG_TYPE_STRING,-1,"TYPE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
{MAKE_ARG("slot",ARG_TYPE_INTEGER,-1,"SLOT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
};
|
||||
|
||||
/********** READONLY ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
@@ -1870,7 +1903,7 @@ struct COMMAND_STRUCT CLIENT_Subcommands[] = {
|
||||
{MAKE_CMD("getredir","Returns the client ID to which the connection's tracking notifications are redirected.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETREDIR_History,0,CLIENT_GETREDIR_Tips,0,clientGetredirCommand,2,CMD_ALLOW_BUSY|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_SLOW,NULL,CLIENT_GETREDIR_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_HELP_History,0,CLIENT_HELP_Tips,0,clientHelpCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_SLOW,NULL,CLIENT_HELP_Keyspecs,0,NULL,0)},
|
||||
{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,clientIDCommand,2,CMD_ALLOW_BUSY|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_SLOW,NULL,CLIENT_ID_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("import-source","Mark this client as an import source when server is in import mode.","O(1)","8.1.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_IMPORT_SOURCE_History,0,CLIENT_IMPORT_SOURCE_Tips,0,clientImportSourceCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_SLOW,NULL,CLIENT_IMPORT_SOURCE_Keyspecs,0,NULL,1),.args=CLIENT_IMPORT_SOURCE_Args},
|
||||
{MAKE_CMD("import-source","Marks this client as an import source when the server is in import mode.","O(1)","8.1.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_IMPORT_SOURCE_History,0,CLIENT_IMPORT_SOURCE_Tips,0,clientImportSourceCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_SLOW,NULL,CLIENT_IMPORT_SOURCE_Keyspecs,0,NULL,1),.args=CLIENT_IMPORT_SOURCE_Args},
|
||||
{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,clientInfoCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_SLOW,NULL,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,9,CLIENT_KILL_Tips,0,clientKillCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_ADMIN|ACL_CATEGORY_CONNECTION|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,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,9,CLIENT_LIST_Tips,1,clientListCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_ADMIN|ACL_CATEGORY_CONNECTION|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,CLIENT_LIST_Keyspecs,0,NULL,27),.args=CLIENT_LIST_Args},
|
||||
@@ -7471,7 +7504,7 @@ struct COMMAND_ARG COMMANDLOG_RESET_Args[] = {
|
||||
/* COMMANDLOG command table */
|
||||
struct COMMAND_STRUCT COMMANDLOG_Subcommands[] = {
|
||||
{MAKE_CMD("get","Returns the specified command log's entries.","O(N) where N is the number of entries returned","8.1.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_GET_History,0,COMMANDLOG_GET_Tips,2,commandlogCommand,4,CMD_ADMIN|CMD_LOADING|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,COMMANDLOG_GET_Keyspecs,0,NULL,2),.args=COMMANDLOG_GET_Args},
|
||||
{MAKE_CMD("help","Show helpful text about the different subcommands","O(1)","8.1.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_HELP_History,0,COMMANDLOG_HELP_Tips,0,commandlogCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,COMMANDLOG_HELP_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("help","Shows helpful text about the different subcommands.","O(1)","8.1.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_HELP_History,0,COMMANDLOG_HELP_Tips,0,commandlogCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,COMMANDLOG_HELP_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("len","Returns the number of entries in the specified type of command log.","O(1)","8.1.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_LEN_History,0,COMMANDLOG_LEN_Tips,3,commandlogCommand,3,CMD_ADMIN|CMD_LOADING|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,COMMANDLOG_LEN_Keyspecs,0,NULL,1),.args=COMMANDLOG_LEN_Args},
|
||||
{MAKE_CMD("reset","Clears all entries from the specified type of command log.","O(N) where N is the number of entries in the commandlog","8.1.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMANDLOG_RESET_History,0,COMMANDLOG_RESET_Tips,2,commandlogCommand,3,CMD_ADMIN|CMD_LOADING|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,COMMANDLOG_RESET_Keyspecs,0,NULL,1),.args=COMMANDLOG_RESET_Args},
|
||||
{0}
|
||||
@@ -8661,7 +8694,7 @@ const char *SLOWLOG_RESET_Tips[] = {
|
||||
/* SLOWLOG command table */
|
||||
struct COMMAND_STRUCT SLOWLOG_Subcommands[] = {
|
||||
{MAKE_CMD("get","Returns the slow log's entries.","O(N) where N is the number of entries returned","2.2.12",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_GET_History,1,SLOWLOG_GET_Tips,2,slowlogCommand,-2,CMD_ADMIN|CMD_LOADING|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,SLOWLOG_GET_Keyspecs,0,NULL,1),.args=SLOWLOG_GET_Args},
|
||||
{MAKE_CMD("help","Show helpful text about the different subcommands","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_HELP_History,0,SLOWLOG_HELP_Tips,0,slowlogCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,SLOWLOG_HELP_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("help","Shows helpful text about the different subcommands.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_HELP_History,0,SLOWLOG_HELP_Tips,0,slowlogCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SLOW,NULL,SLOWLOG_HELP_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("len","Returns the number of entries in the slow log.","O(1)","2.2.12",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_LEN_History,0,SLOWLOG_LEN_Tips,3,slowlogCommand,2,CMD_ADMIN|CMD_LOADING|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,SLOWLOG_LEN_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("reset","Clears all entries from the slow log.","O(N) where N is the number of entries in the slowlog","2.2.12",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_RESET_History,0,SLOWLOG_RESET_Tips,2,slowlogCommand,2,CMD_ADMIN|CMD_LOADING|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,SLOWLOG_RESET_Keyspecs,0,NULL,0)},
|
||||
{0}
|
||||
@@ -11435,6 +11468,57 @@ struct COMMAND_ARG MSET_Args[] = {
|
||||
{MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=MSET_data_Subargs},
|
||||
};
|
||||
|
||||
/********** MSETEX ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* MSETEX history */
|
||||
#define MSETEX_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* MSETEX tips */
|
||||
const char *MSETEX_Tips[] = {
|
||||
"request_policy:multi_shard",
|
||||
"response_policy:all_succeeded",
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* MSETEX key specs */
|
||||
keySpec MSETEX_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,2}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* MSETEX data argument table */
|
||||
struct COMMAND_ARG MSETEX_data_Subargs[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* MSETEX condition argument table */
|
||||
struct COMMAND_ARG MSETEX_condition_Subargs[] = {
|
||||
{MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* MSETEX expiration argument table */
|
||||
struct COMMAND_ARG MSETEX_expiration_Subargs[] = {
|
||||
{MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"PX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,"EXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,"PXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("keepttl",ARG_TYPE_PURE_TOKEN,-1,"KEEPTTL",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* MSETEX argument table */
|
||||
struct COMMAND_ARG MSETEX_Args[] = {
|
||||
{MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=MSETEX_data_Subargs},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=MSETEX_condition_Subargs},
|
||||
{MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=MSETEX_expiration_Subargs},
|
||||
};
|
||||
|
||||
/********** MSETNX ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
@@ -11773,6 +11857,7 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
/* cluster */
|
||||
{MAKE_CMD("asking","Signals that a cluster client is following an -ASK redirect.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,ASKING_History,0,ASKING_Tips,0,askingCommand,1,CMD_FAST,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_FAST,NULL,ASKING_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("cluster","A container for Cluster commands.","Depends on subcommand.","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_History,0,CLUSTER_Tips,0,NULL,-2,0,ACL_CATEGORY_SLOW,NULL,CLUSTER_Keyspecs,0,NULL,0),.subcommands=CLUSTER_Subcommands},
|
||||
{MAKE_CMD("clusterscan","Iterates over the keys in the cluster.","O(N) where N is the number of elements returned.","9.1.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTERSCAN_History,0,CLUSTERSCAN_Tips,3,clusterscanCommand,-2,CMD_READONLY|CMD_TOUCHES_ARBITRARY_KEYS,ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_READ|ACL_CATEGORY_SLOW,NULL,CLUSTERSCAN_Keyspecs,1,NULL,5),.args=CLUSTERSCAN_Args},
|
||||
{MAKE_CMD("readonly","Enables read-only queries for a connection to a Valkey replica node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,READONLY_History,0,READONLY_Tips,0,readonlyCommand,1,CMD_FAST|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_FAST,NULL,READONLY_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("readwrite","Enables read-write queries for a connection to a Valkey replica node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,READWRITE_History,0,READWRITE_Tips,0,readwriteCommand,1,CMD_FAST|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION|ACL_CATEGORY_FAST,NULL,READWRITE_Keyspecs,0,NULL,0)},
|
||||
/* connection */
|
||||
@@ -11828,13 +11913,13 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
/* hash */
|
||||
{MAKE_CMD("hdel","Deletes one or more fields and their values from a hash. Deletes the hash if no fields remain.","O(N) where N is the number of fields to be removed.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HDEL_History,1,HDEL_Tips,0,hdelCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HDEL_Keyspecs,1,NULL,2),.args=HDEL_Args},
|
||||
{MAKE_CMD("hexists","Determines whether a field exists in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXISTS_History,0,HEXISTS_Tips,0,hexistsCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,HEXISTS_Keyspecs,1,NULL,2),.args=HEXISTS_Args},
|
||||
{MAKE_CMD("hexpire","Set expiry time on hash fields.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRE_History,0,HEXPIRE_Tips,0,hexpireCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HEXPIRE_Keyspecs,1,NULL,4),.args=HEXPIRE_Args},
|
||||
{MAKE_CMD("hexpireat","Set expiry time on hash fields.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIREAT_History,0,HEXPIREAT_Tips,0,hexpireatCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HEXPIREAT_Keyspecs,1,NULL,4),.args=HEXPIREAT_Args},
|
||||
{MAKE_CMD("hexpiretime","Returns Unix timestamps in seconds since the epoch at which the given key's field(s) will expire","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRETIME_History,0,HEXPIRETIME_Tips,0,hexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,HEXPIRETIME_Keyspecs,1,NULL,2),.args=HEXPIRETIME_Args},
|
||||
{MAKE_CMD("hexpire","Sets expiry time on hash fields.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRE_History,0,HEXPIRE_Tips,0,hexpireCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HEXPIRE_Keyspecs,1,NULL,4),.args=HEXPIRE_Args},
|
||||
{MAKE_CMD("hexpireat","Sets expiry time on hash fields.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIREAT_History,0,HEXPIREAT_Tips,0,hexpireatCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HEXPIREAT_Keyspecs,1,NULL,4),.args=HEXPIREAT_Args},
|
||||
{MAKE_CMD("hexpiretime","Returns Unix timestamps in seconds since the epoch at which the given key's field(s) will expire.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRETIME_History,0,HEXPIRETIME_Tips,0,hexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,HEXPIRETIME_Keyspecs,1,NULL,2),.args=HEXPIRETIME_Args},
|
||||
{MAKE_CMD("hget","Returns the value of a field in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGET_History,0,HGET_Tips,0,hgetCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,HGET_Keyspecs,1,NULL,2),.args=HGET_Args},
|
||||
{MAKE_CMD("hgetall","Returns all fields and 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,HGETALL_History,0,HGETALL_Tips,1,hgetallCommand,2,CMD_READONLY,ACL_CATEGORY_HASH|ACL_CATEGORY_READ|ACL_CATEGORY_SLOW,NULL,HGETALL_Keyspecs,1,NULL,1),.args=HGETALL_Args},
|
||||
{MAKE_CMD("hgetdel","Returns the values of one or more fields and deletes them from a hash.","O(N) where N is the number of fields to be retrieved and deleted.","9.1.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGETDEL_History,0,HGETDEL_Tips,0,hgetdelCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HGETDEL_Keyspecs,1,NULL,2),.args=HGETDEL_Args},
|
||||
{MAKE_CMD("hgetex","Get the value of one or more fields of a given hash key, and optionally set their expiration time or time-to-live (TTL).","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGETEX_History,0,HGETEX_Tips,0,hgetexCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HGETEX_Keyspecs,1,NULL,3),.args=HGETEX_Args},
|
||||
{MAKE_CMD("hgetex","Gets the value of one or more fields of a given hash key, and optionally sets their expiration time or time-to-live (TTL).","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGETEX_History,0,HGETEX_Tips,0,hgetexCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HGETEX_Keyspecs,1,NULL,3),.args=HGETEX_Args},
|
||||
{MAKE_CMD("hincrby","Increments the integer value of a field in a hash by a number. Uses 0 as initial value if the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HINCRBY_History,0,HINCRBY_Tips,0,hincrbyCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HINCRBY_Keyspecs,1,NULL,3),.args=HINCRBY_Args},
|
||||
{MAKE_CMD("hincrbyfloat","Increments the floating point value of a field by a number. Uses 0 as initial value if the field doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HINCRBYFLOAT_History,0,HINCRBYFLOAT_Tips,0,hincrbyfloatCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HINCRBYFLOAT_Keyspecs,1,NULL,3),.args=HINCRBYFLOAT_Args},
|
||||
{MAKE_CMD("hkeys","Returns all fields 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,HKEYS_History,0,HKEYS_Tips,1,hkeysCommand,2,CMD_READONLY,ACL_CATEGORY_HASH|ACL_CATEGORY_READ|ACL_CATEGORY_SLOW,NULL,HKEYS_Keyspecs,1,NULL,1),.args=HKEYS_Args},
|
||||
@@ -11842,14 +11927,14 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
{MAKE_CMD("hmget","Returns the values of all fields in a hash.","O(N) where N is the number of fields being requested.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HMGET_History,0,HMGET_Tips,0,hmgetCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,HMGET_Keyspecs,1,NULL,2),.args=HMGET_Args},
|
||||
{MAKE_CMD("hmset","Sets the values of multiple fields.","O(N) where N is the number of fields being set.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HMSET_History,0,HMSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HMSET_Keyspecs,1,NULL,2),.args=HMSET_Args},
|
||||
{MAKE_CMD("hpersist","Remove the existing expiration on a hash key's field(s).","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPERSIST_History,0,HPERSIST_Tips,0,hpersistCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HPERSIST_Keyspecs,1,NULL,2),.args=HPERSIST_Args},
|
||||
{MAKE_CMD("hpexpire","Set expiry time on hash object.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRE_History,0,HPEXPIRE_Tips,0,hpexpireCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HPEXPIRE_Keyspecs,1,NULL,4),.args=HPEXPIRE_Args},
|
||||
{MAKE_CMD("hpexpireat","Set expiration time on hash field.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIREAT_History,0,HPEXPIREAT_Tips,0,hpexpireatCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HPEXPIREAT_Keyspecs,1,NULL,4),.args=HPEXPIREAT_Args},
|
||||
{MAKE_CMD("hpexpiretime","Returns the Unix timestamp in milliseconds since Unix epoch at which the given key's field(s) will expire","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRETIME_History,0,HPEXPIRETIME_Tips,0,hpexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,HPEXPIRETIME_Keyspecs,1,NULL,2),.args=HPEXPIRETIME_Args},
|
||||
{MAKE_CMD("hpexpire","Sets expiry time on hash object.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRE_History,0,HPEXPIRE_Tips,0,hpexpireCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HPEXPIRE_Keyspecs,1,NULL,4),.args=HPEXPIRE_Args},
|
||||
{MAKE_CMD("hpexpireat","Sets expiration time on hash field.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIREAT_History,0,HPEXPIREAT_Tips,0,hpexpireatCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HPEXPIREAT_Keyspecs,1,NULL,4),.args=HPEXPIREAT_Args},
|
||||
{MAKE_CMD("hpexpiretime","Returns the Unix timestamp in milliseconds since Unix epoch at which the given key's field(s) will expire.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRETIME_History,0,HPEXPIRETIME_Tips,0,hpexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,HPEXPIRETIME_Keyspecs,1,NULL,2),.args=HPEXPIRETIME_Args},
|
||||
{MAKE_CMD("hpttl","Returns the remaining time to live in milliseconds of a hash key's field(s) that have an associated expiration.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,0,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,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|ACL_CATEGORY_READ|ACL_CATEGORY_SLOW,NULL,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|ACL_CATEGORY_READ|ACL_CATEGORY_SLOW,NULL,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_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HSET_Keyspecs,1,NULL,2),.args=HSET_Args},
|
||||
{MAKE_CMD("hsetex","Set the value of one or more fields of a given hash key, and optionally set their expiration time.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETEX_History,0,HSETEX_Tips,0,hsetexCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HSETEX_Keyspecs,1,NULL,5),.args=HSETEX_Args},
|
||||
{MAKE_CMD("hsetex","Sets the value of one or more fields of a given hash key, and optionally sets their expiration time.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETEX_History,0,HSETEX_Tips,0,hsetexCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,HSETEX_Keyspecs,1,NULL,5),.args=HSETEX_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_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_WRITE,NULL,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_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,HSTRLEN_Keyspecs,1,NULL,2),.args=HSTRLEN_Args},
|
||||
{MAKE_CMD("httl","Returns the remaining time to live (in seconds) of a hash key's field(s) that have an associated expiration.","O(N) where N is the number of specified fields.","9.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,0,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_HASH|ACL_CATEGORY_READ,NULL,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args},
|
||||
@@ -11865,7 +11950,7 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
{MAKE_CMD("blmpop","Pops the first element from one of multiple lists. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(N+M) where N is the number of provided keys and M is the number of elements returned.","7.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLMPOP_History,0,BLMPOP_Tips,0,blmpopCommand,-5,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,BLMPOP_Keyspecs,1,blmpopGetKeys,5),.args=BLMPOP_Args},
|
||||
{MAKE_CMD("blpop","Removes and returns the first element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(N) where N is the number of provided keys.","2.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLPOP_History,1,BLPOP_Tips,0,blpopCommand,-3,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,BLPOP_Keyspecs,1,NULL,2),.args=BLPOP_Args},
|
||||
{MAKE_CMD("brpop","Removes and returns the last element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(N) where N is the number of provided keys.","2.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BRPOP_History,1,BRPOP_Tips,0,brpopCommand,-3,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,BRPOP_Keyspecs,1,NULL,2),.args=BRPOP_Args},
|
||||
{MAKE_CMD("brpoplpush","Pops an element from a list, pushes it to another list and returns it. Block until an element is available otherwise. Deletes the list if the last element was popped.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BRPOPLPUSH_History,1,BRPOPLPUSH_Tips,0,brpoplpushCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_BLOCKING,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,BRPOPLPUSH_Keyspecs,2,NULL,3),.args=BRPOPLPUSH_Args},
|
||||
{MAKE_CMD("brpoplpush","Pops an element from a list, pushes it to another list and returns it. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BRPOPLPUSH_History,1,BRPOPLPUSH_Tips,0,brpoplpushCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_BLOCKING,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,BRPOPLPUSH_Keyspecs,2,NULL,3),.args=BRPOPLPUSH_Args},
|
||||
{MAKE_CMD("lindex","Returns an element from a list by its index.","O(N) where N is the number of elements to traverse to get to the element at index. This makes asking for the first or the last element of the list O(1).","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LINDEX_History,0,LINDEX_Tips,0,lindexCommand,3,CMD_READONLY,ACL_CATEGORY_LIST|ACL_CATEGORY_READ|ACL_CATEGORY_SLOW,NULL,LINDEX_Keyspecs,1,NULL,2),.args=LINDEX_Args},
|
||||
{MAKE_CMD("linsert","Inserts an element before or after another element in a list.","O(N) where N is the number of elements to traverse before seeing the value pivot. This means that inserting somewhere on the left end on the list (head) can be considered O(1) and inserting somewhere on the right end (tail) is O(N).","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LINSERT_History,0,LINSERT_Tips,0,linsertCommand,5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,LINSERT_Keyspecs,1,NULL,4),.args=LINSERT_Args},
|
||||
{MAKE_CMD("llen","Returns the length of a list.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LLEN_History,0,LLEN_Tips,0,llenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_LIST|ACL_CATEGORY_READ,NULL,LLEN_Keyspecs,1,NULL,1),.args=LLEN_Args},
|
||||
@@ -11878,7 +11963,7 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
{MAKE_CMD("lrange","Returns a range of elements from a list.","O(S+N) where S is the distance of start offset from HEAD for small lists, from nearest end (HEAD or TAIL) for large lists; and N is the number of elements in the specified range.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LRANGE_History,0,LRANGE_Tips,0,lrangeCommand,4,CMD_READONLY,ACL_CATEGORY_LIST|ACL_CATEGORY_READ|ACL_CATEGORY_SLOW,NULL,LRANGE_Keyspecs,1,NULL,3),.args=LRANGE_Args},
|
||||
{MAKE_CMD("lrem","Removes elements from a list. Deletes the list if the last element was removed.","O(N+M) where N is the length of the list and M is the number of elements removed.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LREM_History,0,LREM_Tips,0,lremCommand,4,CMD_WRITE,ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,LREM_Keyspecs,1,NULL,3),.args=LREM_Args},
|
||||
{MAKE_CMD("lset","Sets the value of an element in a list by its index.","O(N) where N is the length of the list. Setting either the first or the last element of the list is O(1).","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LSET_History,0,LSET_Tips,0,lsetCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,LSET_Keyspecs,1,NULL,3),.args=LSET_Args},
|
||||
{MAKE_CMD("ltrim","Removes elements from both ends a list. Deletes the list if all elements were trimmed.","O(N) where N is the number of elements to be removed by the operation.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LTRIM_History,0,LTRIM_Tips,0,ltrimCommand,4,CMD_WRITE,ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,LTRIM_Keyspecs,1,NULL,3),.args=LTRIM_Args},
|
||||
{MAKE_CMD("ltrim","Removes elements from both ends of a list. Deletes the list if all elements were trimmed.","O(N) where N is the number of elements to be removed by the operation.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LTRIM_History,0,LTRIM_Tips,0,ltrimCommand,4,CMD_WRITE,ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,LTRIM_Keyspecs,1,NULL,3),.args=LTRIM_Args},
|
||||
{MAKE_CMD("rpop","Returns and removes one or more elements from the end of a list. Deletes the list if the last element was popped.","O(N) where N is the number of elements returned","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPOP_History,1,RPOP_Tips,0,rpopCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_LIST|ACL_CATEGORY_WRITE,NULL,RPOP_Keyspecs,1,NULL,2),.args=RPOP_Args},
|
||||
{MAKE_CMD("rpoplpush","Returns the last element of a list after removing and pushing it to another list. Deletes the list if the last element was popped.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPOPLPUSH_History,0,RPOPLPUSH_Tips,0,rpoplpushCommand,3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,RPOPLPUSH_Keyspecs,2,NULL,2),.args=RPOPLPUSH_Args},
|
||||
{MAKE_CMD("rpush","Appends one or more elements to a list. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSH_History,1,RPUSH_Tips,0,rpushCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_LIST|ACL_CATEGORY_WRITE,NULL,RPUSH_Keyspecs,1,NULL,2),.args=RPUSH_Args},
|
||||
@@ -11888,7 +11973,7 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
{MAKE_CMD("publish","Posts a message to a channel.","O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBLISH_History,0,PUBLISH_Tips,0,publishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE|CMD_SENTINEL,ACL_CATEGORY_FAST|ACL_CATEGORY_PUBSUB,NULL,PUBLISH_Keyspecs,0,NULL,2),.args=PUBLISH_Args},
|
||||
{MAKE_CMD("pubsub","A container for Pub/Sub commands.","Depends on subcommand.","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_History,0,PUBSUB_Tips,0,NULL,-2,0,ACL_CATEGORY_SLOW,NULL,PUBSUB_Keyspecs,0,NULL,0),.subcommands=PUBSUB_Subcommands},
|
||||
{MAKE_CMD("punsubscribe","Stops listening to messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUNSUBSCRIBE_History,0,PUNSUBSCRIBE_Tips,0,punsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_PUBSUB|ACL_CATEGORY_SLOW,NULL,PUNSUBSCRIBE_Keyspecs,0,NULL,1),.args=PUNSUBSCRIBE_Args},
|
||||
{MAKE_CMD("spublish","Post a message to a shard channel","O(N) where N is the number of clients subscribed to the receiving shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SPUBLISH_History,0,SPUBLISH_Tips,0,spublishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE,ACL_CATEGORY_FAST|ACL_CATEGORY_PUBSUB,NULL,SPUBLISH_Keyspecs,1,NULL,2),.args=SPUBLISH_Args},
|
||||
{MAKE_CMD("spublish","Posts a message to a shard channel.","O(N) where N is the number of clients subscribed to the receiving shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SPUBLISH_History,0,SPUBLISH_Tips,0,spublishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE,ACL_CATEGORY_FAST|ACL_CATEGORY_PUBSUB,NULL,SPUBLISH_Keyspecs,1,NULL,2),.args=SPUBLISH_Args},
|
||||
{MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_PUBSUB|ACL_CATEGORY_SLOW,NULL,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args},
|
||||
{MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_PUBSUB|ACL_CATEGORY_SLOW,NULL,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args},
|
||||
{MAKE_CMD("sunsubscribe","Stops listening to messages posted to shard channels.","O(N) where N is the number of shard channels to unsubscribe.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUNSUBSCRIBE_History,0,SUNSUBSCRIBE_Tips,0,sunsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_PUBSUB|ACL_CATEGORY_SLOW,NULL,SUNSUBSCRIBE_Keyspecs,1,NULL,1),.args=SUNSUBSCRIBE_Args},
|
||||
@@ -11915,11 +12000,11 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
{MAKE_CMD("debug","A container for debugging commands.","Depends on subcommand.","1.0.0",CMD_DOC_SYSCMD,NULL,NULL,"server",COMMAND_GROUP_SERVER,DEBUG_History,0,DEBUG_Tips,0,debugCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_PROTECTED,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,DEBUG_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("failover","Starts a coordinated failover from a server to one of its replicas.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,FAILOVER_History,0,FAILOVER_Tips,0,failoverCommand,-1,CMD_ADMIN|CMD_NOSCRIPT|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,FAILOVER_Keyspecs,0,NULL,3),.args=FAILOVER_Args},
|
||||
{MAKE_CMD("flushall","Removes all keys from all databases.","O(N) where N is the total number of keys in all databases","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,FLUSHALL_History,2,FLUSHALL_Tips,2,flushallCommand,-1,CMD_WRITE|CMD_ALL_DBS,ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,FLUSHALL_Keyspecs,0,NULL,1),.args=FLUSHALL_Args},
|
||||
{MAKE_CMD("flushdb","Remove all keys from the current database.","O(N) where N is the number of keys in the selected database","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,FLUSHDB_History,2,FLUSHDB_Tips,2,flushdbCommand,-1,CMD_WRITE,ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,FLUSHDB_Keyspecs,0,NULL,1),.args=FLUSHDB_Args},
|
||||
{MAKE_CMD("flushdb","Removes all keys from the current database.","O(N) where N is the number of keys in the selected database","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,FLUSHDB_History,2,FLUSHDB_Tips,2,flushdbCommand,-1,CMD_WRITE,ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,FLUSHDB_Keyspecs,0,NULL,1),.args=FLUSHDB_Args},
|
||||
{MAKE_CMD("info","Returns information and statistics about the server.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,INFO_History,1,INFO_Tips,3,infoCommand,-1,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,INFO_Keyspecs,0,NULL,1),.args=INFO_Args},
|
||||
{MAKE_CMD("lastsave","Returns the Unix timestamp of the last successful save to disk.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LASTSAVE_History,0,LASTSAVE_Tips,1,lastsaveCommand,1,CMD_LOADING|CMD_STALE|CMD_FAST,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_FAST,NULL,LASTSAVE_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("latency","A container for latency diagnostics commands.","Depends on subcommand.","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LATENCY_History,0,LATENCY_Tips,0,NULL,-2,0,ACL_CATEGORY_SLOW,NULL,LATENCY_Keyspecs,0,NULL,0),.subcommands=LATENCY_Subcommands},
|
||||
{MAKE_CMD("lolwut","Displays computer art and the server version",NULL,"5.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LOLWUT_History,0,LOLWUT_Tips,0,lolwutCommand,-1,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_READ,NULL,LOLWUT_Keyspecs,0,NULL,1),.args=LOLWUT_Args},
|
||||
{MAKE_CMD("lolwut","Displays computer art and the server version.",NULL,"5.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LOLWUT_History,0,LOLWUT_Tips,0,lolwutCommand,-1,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_READ,NULL,LOLWUT_Keyspecs,0,NULL,1),.args=LOLWUT_Args},
|
||||
{MAKE_CMD("memory","A container for memory diagnostics commands.","Depends on subcommand.","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MEMORY_History,0,MEMORY_Tips,0,NULL,-2,0,ACL_CATEGORY_SLOW,NULL,MEMORY_Keyspecs,0,NULL,0),.subcommands=MEMORY_Subcommands},
|
||||
{MAKE_CMD("module","A container for module commands.","Depends on subcommand.","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MODULE_History,0,MODULE_Tips,0,NULL,-2,0,ACL_CATEGORY_SLOW,NULL,MODULE_Keyspecs,0,NULL,0),.subcommands=MODULE_Subcommands},
|
||||
{MAKE_CMD("monitor","Listens for all requests received by the server in real-time.",NULL,"1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MONITOR_History,0,MONITOR_Tips,0,monitorCommand,1,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS|ACL_CATEGORY_SLOW,NULL,MONITOR_Keyspecs,0,NULL,0)},
|
||||
@@ -11948,14 +12033,14 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
{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_FAST|ACL_CATEGORY_READ|ACL_CATEGORY_SET,NULL,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_FAST|ACL_CATEGORY_SET|ACL_CATEGORY_WRITE,NULL,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_FAST|ACL_CATEGORY_SET|ACL_CATEGORY_WRITE,NULL,SPOP_Keyspecs,1,NULL,2),.args=SPOP_Args},
|
||||
{MAKE_CMD("srandmember","Get one or multiple random members from a set","Without the count argument O(1), otherwise O(N) where N is the absolute value of the passed count.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SRANDMEMBER_History,1,SRANDMEMBER_Tips,1,srandmemberCommand,-2,CMD_READONLY,ACL_CATEGORY_READ|ACL_CATEGORY_SET|ACL_CATEGORY_SLOW,NULL,SRANDMEMBER_Keyspecs,1,NULL,2),.args=SRANDMEMBER_Args},
|
||||
{MAKE_CMD("srandmember","Gets one or multiple random members from a set.","Without the count argument O(1), otherwise O(N) where N is the absolute value of the passed count.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SRANDMEMBER_History,1,SRANDMEMBER_Tips,1,srandmemberCommand,-2,CMD_READONLY,ACL_CATEGORY_READ|ACL_CATEGORY_SET|ACL_CATEGORY_SLOW,NULL,SRANDMEMBER_Keyspecs,1,NULL,2),.args=SRANDMEMBER_Args},
|
||||
{MAKE_CMD("srem","Removes one or more members from a set. Deletes the set if the last member was removed.","O(N) where N is the number of members to be removed.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SREM_History,1,SREM_Tips,0,sremCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_SET|ACL_CATEGORY_WRITE,NULL,SREM_Keyspecs,1,NULL,2),.args=SREM_Args},
|
||||
{MAKE_CMD("sscan","Iterates over members of a set.","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,"set",COMMAND_GROUP_SET,SSCAN_History,0,SSCAN_Tips,1,sscanCommand,-3,CMD_READONLY,ACL_CATEGORY_READ|ACL_CATEGORY_SET|ACL_CATEGORY_SLOW,NULL,SSCAN_Keyspecs,1,NULL,4),.args=SSCAN_Args},
|
||||
{MAKE_CMD("sunion","Returns the union of multiple sets.","O(N) where N is the total number of elements in all given sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SUNION_History,0,SUNION_Tips,1,sunionCommand,-2,CMD_READONLY,ACL_CATEGORY_READ|ACL_CATEGORY_SET|ACL_CATEGORY_SLOW,NULL,SUNION_Keyspecs,1,NULL,1),.args=SUNION_Args},
|
||||
{MAKE_CMD("sunionstore","Stores the union of multiple sets in a key.","O(N) where N is the total number of elements in all given sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SUNIONSTORE_History,0,SUNIONSTORE_Tips,0,sunionstoreCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SET|ACL_CATEGORY_SLOW|ACL_CATEGORY_WRITE,NULL,SUNIONSTORE_Keyspecs,2,NULL,2),.args=SUNIONSTORE_Args},
|
||||
/* sorted_set */
|
||||
{MAKE_CMD("bzmpop","Removes and returns a member by score from one or more sorted sets. Blocks until a member is available otherwise. Deletes the sorted set if the last element was popped.","O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.","7.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,BZMPOP_History,0,BZMPOP_Tips,0,bzmpopCommand,-5,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_SLOW|ACL_CATEGORY_SORTEDSET|ACL_CATEGORY_WRITE,NULL,BZMPOP_Keyspecs,1,blmpopGetKeys,5),.args=BZMPOP_Args},
|
||||
{MAKE_CMD("bzpopmax","Removes and returns the member with the highest score from one or more sorted sets. Blocks until a member available otherwise. Deletes the sorted set if the last element was popped.","O(log(N)) with N being the number of elements in the sorted set.","5.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,BZPOPMAX_History,1,BZPOPMAX_Tips,0,bzpopmaxCommand,-3,CMD_WRITE|CMD_FAST|CMD_BLOCKING,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_FAST|ACL_CATEGORY_SORTEDSET|ACL_CATEGORY_WRITE,NULL,BZPOPMAX_Keyspecs,1,NULL,2),.args=BZPOPMAX_Args},
|
||||
{MAKE_CMD("bzpopmax","Removes and returns the member with the highest score from one or more sorted sets. Blocks until a member is available otherwise. Deletes the sorted set if the last element was popped.","O(log(N)) with N being the number of elements in the sorted set.","5.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,BZPOPMAX_History,1,BZPOPMAX_Tips,0,bzpopmaxCommand,-3,CMD_WRITE|CMD_FAST|CMD_BLOCKING,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_FAST|ACL_CATEGORY_SORTEDSET|ACL_CATEGORY_WRITE,NULL,BZPOPMAX_Keyspecs,1,NULL,2),.args=BZPOPMAX_Args},
|
||||
{MAKE_CMD("bzpopmin","Removes and returns the member with the lowest score from one or more sorted sets. Blocks until a member is available otherwise. Deletes the sorted set if the last element was popped.","O(log(N)) with N being the number of elements in the sorted set.","5.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,BZPOPMIN_History,1,BZPOPMIN_Tips,0,bzpopminCommand,-3,CMD_WRITE|CMD_FAST|CMD_BLOCKING,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_FAST|ACL_CATEGORY_SORTEDSET|ACL_CATEGORY_WRITE,NULL,BZPOPMIN_Keyspecs,1,NULL,2),.args=BZPOPMIN_Args},
|
||||
{MAKE_CMD("zadd","Adds one or more members to a sorted set, or updates their scores. Creates the key if it doesn't exist.","O(log(N)) for each item added, where N is the number of elements in the sorted set.","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZADD_History,3,ZADD_Tips,0,zaddCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_SORTEDSET|ACL_CATEGORY_WRITE,NULL,ZADD_Keyspecs,1,NULL,6),.args=ZADD_Args},
|
||||
{MAKE_CMD("zcard","Returns the number of members in a sorted set.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZCARD_History,0,ZCARD_Tips,0,zcardCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_READ|ACL_CATEGORY_SORTEDSET,NULL,ZCARD_Keyspecs,1,NULL,1),.args=ZCARD_Args},
|
||||
@@ -11992,12 +12077,12 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
/* stream */
|
||||
{MAKE_CMD("xack","Returns the number of messages that were successfully acknowledged by the consumer group member of a stream.","O(1) for each message ID processed.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XACK_History,0,XACK_Tips,0,xackCommand,-4,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STREAM|ACL_CATEGORY_WRITE,NULL,XACK_Keyspecs,1,NULL,3),.args=XACK_Args},
|
||||
{MAKE_CMD("xadd","Appends a new message to a stream. Creates the key if it doesn't exist.","O(1) when adding a new entry, O(N) when trimming where N being the number of entries evicted.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XADD_History,2,XADD_Tips,1,xaddCommand,-5,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STREAM|ACL_CATEGORY_WRITE,NULL,XADD_Keyspecs,1,NULL,5),.args=XADD_Args},
|
||||
{MAKE_CMD("xautoclaim","Changes, or acquires, ownership of messages in a consumer group, as if the messages were delivered to as consumer group member.","O(1) if COUNT is small.","6.2.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XAUTOCLAIM_History,1,XAUTOCLAIM_Tips,1,xautoclaimCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STREAM|ACL_CATEGORY_WRITE,NULL,XAUTOCLAIM_Keyspecs,1,NULL,7),.args=XAUTOCLAIM_Args},
|
||||
{MAKE_CMD("xclaim","Changes, or acquires, ownership of a message in a consumer group, as if the message was delivered a consumer group member.","O(log N) with N being the number of messages in the PEL of the consumer group.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XCLAIM_History,0,XCLAIM_Tips,1,xclaimCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STREAM|ACL_CATEGORY_WRITE,NULL,XCLAIM_Keyspecs,1,NULL,11),.args=XCLAIM_Args},
|
||||
{MAKE_CMD("xautoclaim","Changes, or acquires, ownership of messages in a consumer group, as if the messages were delivered to a consumer group member.","O(1) if COUNT is small.","6.2.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XAUTOCLAIM_History,1,XAUTOCLAIM_Tips,1,xautoclaimCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STREAM|ACL_CATEGORY_WRITE,NULL,XAUTOCLAIM_Keyspecs,1,NULL,7),.args=XAUTOCLAIM_Args},
|
||||
{MAKE_CMD("xclaim","Changes, or acquires, ownership of a message in a consumer group, as if the message was delivered to a consumer group member.","O(log N) with N being the number of messages in the PEL of the consumer group.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XCLAIM_History,0,XCLAIM_Tips,1,xclaimCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STREAM|ACL_CATEGORY_WRITE,NULL,XCLAIM_Keyspecs,1,NULL,11),.args=XCLAIM_Args},
|
||||
{MAKE_CMD("xdel","Returns the number of messages after removing them from a stream.","O(1) for each single item to delete in the stream, regardless of the stream size.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XDEL_History,0,XDEL_Tips,0,xdelCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STREAM|ACL_CATEGORY_WRITE,NULL,XDEL_Keyspecs,1,NULL,2),.args=XDEL_Args},
|
||||
{MAKE_CMD("xgroup","A container for consumer groups commands.","Depends on subcommand.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XGROUP_History,0,XGROUP_Tips,0,NULL,-2,0,ACL_CATEGORY_SLOW,NULL,XGROUP_Keyspecs,0,NULL,0),.subcommands=XGROUP_Subcommands},
|
||||
{MAKE_CMD("xinfo","A container for stream introspection commands.","Depends on subcommand.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_History,0,XINFO_Tips,0,NULL,-2,0,ACL_CATEGORY_SLOW,NULL,XINFO_Keyspecs,0,NULL,0),.subcommands=XINFO_Subcommands},
|
||||
{MAKE_CMD("xlen","Return the number of messages in a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XLEN_History,0,XLEN_Tips,0,xlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_READ|ACL_CATEGORY_STREAM,NULL,XLEN_Keyspecs,1,NULL,1),.args=XLEN_Args},
|
||||
{MAKE_CMD("xlen","Returns the number of messages in a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XLEN_History,0,XLEN_Tips,0,xlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_READ|ACL_CATEGORY_STREAM,NULL,XLEN_Keyspecs,1,NULL,1),.args=XLEN_Args},
|
||||
{MAKE_CMD("xpending","Returns the information and entries from a stream consumer group's pending entries list.","O(N) with N being the number of elements returned, so asking for a small fixed number of entries per call is O(1). O(M), where M is the total number of entries scanned when used with the IDLE filter. When the command returns just the summary and the list of consumers is small, it runs in O(1) time; otherwise, an additional O(N) time for iterating every consumer.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XPENDING_History,1,XPENDING_Tips,1,xpendingCommand,-3,CMD_READONLY,ACL_CATEGORY_READ|ACL_CATEGORY_SLOW|ACL_CATEGORY_STREAM,NULL,XPENDING_Keyspecs,1,NULL,3),.args=XPENDING_Args},
|
||||
{MAKE_CMD("xrange","Returns the messages from a stream within a range of IDs.","O(N) with N being the number of elements being returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XRANGE_History,1,XRANGE_Tips,0,xrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_READ|ACL_CATEGORY_SLOW|ACL_CATEGORY_STREAM,NULL,XRANGE_Keyspecs,1,NULL,4),.args=XRANGE_Args},
|
||||
{MAKE_CMD("xread","Returns messages from multiple streams with IDs greater than the ones requested. Blocks until a message is available otherwise.",NULL,"5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREAD_History,0,XREAD_Tips,0,xreadCommand,-4,CMD_BLOCKING|CMD_READONLY,ACL_CATEGORY_BLOCKING|ACL_CATEGORY_READ|ACL_CATEGORY_SLOW|ACL_CATEGORY_STREAM,NULL,XREAD_Keyspecs,1,xreadGetKeys,3),.args=XREAD_Args},
|
||||
@@ -12008,7 +12093,7 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
/* string */
|
||||
{MAKE_CMD("append","Appends a string to the value of a key. Creates the key if it doesn't exist.","O(1). The amortized time complexity is O(1) assuming the appended value is small and the already present value is of any size, since the dynamic string library used by the server will double the free space available on every reallocation.","2.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,APPEND_History,0,APPEND_Tips,0,appendCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,APPEND_Keyspecs,1,NULL,2),.args=APPEND_Args},
|
||||
{MAKE_CMD("decr","Decrements the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DECR_History,0,DECR_Tips,0,decrCommand,2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,DECR_Keyspecs,1,NULL,1),.args=DECR_Args},
|
||||
{MAKE_CMD("decrby","Decrements a number from the integer value of a key. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DECRBY_History,0,DECRBY_Tips,0,decrbyCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,DECRBY_Keyspecs,1,NULL,2),.args=DECRBY_Args},
|
||||
{MAKE_CMD("decrby","Decrements the integer value of a key by a number. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DECRBY_History,0,DECRBY_Tips,0,decrbyCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,DECRBY_Keyspecs,1,NULL,2),.args=DECRBY_Args},
|
||||
{MAKE_CMD("delifeq","Delete key if value matches string.","O(1)","9.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DELIFEQ_History,0,DELIFEQ_Tips,0,delifeqCommand,3,CMD_FAST|CMD_WRITE,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,DELIFEQ_Keyspecs,1,NULL,2),.args=DELIFEQ_Args},
|
||||
{MAKE_CMD("get","Returns the string value of a key.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GET_History,0,GET_Tips,0,getCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_READ|ACL_CATEGORY_STRING,NULL,GET_Keyspecs,1,NULL,1),.args=GET_Args},
|
||||
{MAKE_CMD("getdel","Returns the string value of a key after deleting the key.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETDEL_History,0,GETDEL_Tips,0,getdelCommand,2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,GETDEL_Keyspecs,1,NULL,1),.args=GETDEL_Args},
|
||||
@@ -12017,15 +12102,16 @@ struct COMMAND_STRUCT serverCommandTable[] = {
|
||||
{MAKE_CMD("getset","Returns the previous string value of a key after setting it to a new value.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETSET_History,0,GETSET_Tips,0,getsetCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,GETSET_Keyspecs,1,NULL,2),.args=GETSET_Args},
|
||||
{MAKE_CMD("incr","Increments the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCR_History,0,INCR_Tips,0,incrCommand,2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,INCR_Keyspecs,1,NULL,1),.args=INCR_Args},
|
||||
{MAKE_CMD("incrby","Increments the integer value of a key by a number. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCRBY_History,0,INCRBY_Tips,0,incrbyCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,INCRBY_Keyspecs,1,NULL,2),.args=INCRBY_Args},
|
||||
{MAKE_CMD("incrbyfloat","Increment the floating point value of a key by a number. Uses 0 as initial value if the key doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCRBYFLOAT_History,0,INCRBYFLOAT_Tips,0,incrbyfloatCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,INCRBYFLOAT_Keyspecs,1,NULL,2),.args=INCRBYFLOAT_Args},
|
||||
{MAKE_CMD("incrbyfloat","Increments the floating point value of a key by a number. Uses 0 as initial value if the key doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCRBYFLOAT_History,0,INCRBYFLOAT_Tips,0,incrbyfloatCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,INCRBYFLOAT_Keyspecs,1,NULL,2),.args=INCRBYFLOAT_Args},
|
||||
{MAKE_CMD("lcs","Finds the longest common substring.","O(N*M) where N and M are the lengths of s1 and s2, respectively","7.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,LCS_History,0,LCS_Tips,0,lcsCommand,-3,CMD_READONLY,ACL_CATEGORY_READ|ACL_CATEGORY_SLOW|ACL_CATEGORY_STRING,NULL,LCS_Keyspecs,1,NULL,6),.args=LCS_Args},
|
||||
{MAKE_CMD("mget","Atomically returns the string values of one or more keys.","O(N) where N is the number of keys to retrieve.","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MGET_History,0,MGET_Tips,1,mgetCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_READ|ACL_CATEGORY_STRING,NULL,MGET_Keyspecs,1,NULL,1),.args=MGET_Args},
|
||||
{MAKE_CMD("mset","Atomically creates or modifies the string values of one or more keys.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSET_History,0,MSET_Tips,2,msetCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SLOW|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,MSET_Keyspecs,1,NULL,1),.args=MSET_Args},
|
||||
{MAKE_CMD("msetex","Atomically creates or modifies the string values of one or more keys, and optionally set their expiration.","O(N) where N is the number of keys to set.","9.1.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSETEX_History,0,MSETEX_Tips,2,msetexCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE|ACL_CATEGORY_SLOW,NULL,MSETEX_Keyspecs,1,NULL,4),.args=MSETEX_Args},
|
||||
{MAKE_CMD("msetnx","Atomically modifies the string values of one or more keys only when all keys don't exist.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSETNX_History,0,MSETNX_Tips,0,msetnxCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SLOW|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,MSETNX_Keyspecs,1,NULL,1),.args=MSETNX_Args},
|
||||
{MAKE_CMD("psetex","Sets both string value and expiration time in milliseconds of a key. The key is created if it doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,PSETEX_History,0,PSETEX_Tips,0,psetexCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SLOW|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,PSETEX_Keyspecs,1,NULL,3),.args=PSETEX_Args},
|
||||
{MAKE_CMD("set","Sets the string value of a key, ignoring its type. The key is created if it doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SET_History,5,SET_Tips,0,setCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SLOW|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,SET_Keyspecs,1,setGetKeys,5),.args=SET_Args},
|
||||
{MAKE_CMD("setex","Sets the string value and expiration time of a key. Creates the key if it doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SETEX_History,0,SETEX_Tips,0,setexCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SLOW|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,SETEX_Keyspecs,1,NULL,3),.args=SETEX_Args},
|
||||
{MAKE_CMD("setnx","Set the string value of a key only when the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SETNX_History,0,SETNX_Tips,0,setnxCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,SETNX_Keyspecs,1,NULL,2),.args=SETNX_Args},
|
||||
{MAKE_CMD("setnx","Sets the string value of a key only when the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SETNX_History,0,SETNX_Tips,0,setnxCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,SETNX_Keyspecs,1,NULL,2),.args=SETNX_Args},
|
||||
{MAKE_CMD("setrange","Overwrites a part of a string value with another by an offset. Creates the key if it doesn't exist.","O(1), not counting the time taken to copy the new string in place. Usually, this string is very small so the amortized complexity is O(1). Otherwise, complexity is O(M) with M being the length of the value argument.","2.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SETRANGE_History,0,SETRANGE_Tips,0,setrangeCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SLOW|ACL_CATEGORY_STRING|ACL_CATEGORY_WRITE,NULL,SETRANGE_Keyspecs,1,NULL,3),.args=SETRANGE_Args},
|
||||
{MAKE_CMD("strlen","Returns the length of a string value.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,STRLEN_History,0,STRLEN_Tips,0,strlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_FAST|ACL_CATEGORY_READ|ACL_CATEGORY_STRING,NULL,STRLEN_Keyspecs,1,NULL,1),.args=STRLEN_Args},
|
||||
{MAKE_CMD("substr","Returns a substring from a string value.","O(N) where N is the length of the returned string. The complexity is ultimately determined by the returned length, but because creating a substring from an existing string is very cheap, it can be considered O(1) for small strings.","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SUBSTR_History,0,SUBSTR_Tips,0,getrangeCommand,4,CMD_READONLY,ACL_CATEGORY_READ|ACL_CATEGORY_SLOW|ACL_CATEGORY_STRING,NULL,SUBSTR_Keyspecs,1,NULL,3),.args=SUBSTR_Args},
|
||||
|
||||
@@ -19,6 +19,47 @@ typedef enum {
|
||||
#define CMD_ARG_MULTIPLE (1 << 1)
|
||||
#define CMD_ARG_MULTIPLE_TOKEN (1 << 2)
|
||||
|
||||
#define COMMAND_GET 0
|
||||
#define COMMAND_SET 1
|
||||
#define COMMAND_HGET 2
|
||||
#define COMMAND_HSET 3
|
||||
#define COMMAND_MSET 4
|
||||
|
||||
/* Command flags. Please check the definition of struct serverCommand in this file
|
||||
* for more information about the meaning of every flag. */
|
||||
#define CMD_WRITE (1ULL << 0)
|
||||
#define CMD_READONLY (1ULL << 1)
|
||||
#define CMD_DENYOOM (1ULL << 2)
|
||||
#define CMD_MODULE (1ULL << 3) /* Command exported by module. */
|
||||
#define CMD_ADMIN (1ULL << 4)
|
||||
#define CMD_PUBSUB (1ULL << 5)
|
||||
#define CMD_NOSCRIPT (1ULL << 6)
|
||||
#define CMD_BLOCKING (1ULL << 8) /* Has potential to block. */
|
||||
#define CMD_LOADING (1ULL << 9)
|
||||
#define CMD_STALE (1ULL << 10)
|
||||
#define CMD_SKIP_MONITOR (1ULL << 11)
|
||||
#define CMD_SKIP_COMMANDLOG (1ULL << 12)
|
||||
#define CMD_ASKING (1ULL << 13)
|
||||
#define CMD_FAST (1ULL << 14)
|
||||
#define CMD_NO_AUTH (1ULL << 15)
|
||||
#define CMD_MAY_REPLICATE (1ULL << 16)
|
||||
#define CMD_SENTINEL (1ULL << 17)
|
||||
#define CMD_ONLY_SENTINEL (1ULL << 18)
|
||||
#define CMD_NO_MANDATORY_KEYS (1ULL << 19)
|
||||
#define CMD_PROTECTED (1ULL << 20)
|
||||
#define CMD_MODULE_GETKEYS (1ULL << 21) /* Use the modules getkeys interface. */
|
||||
#define CMD_MODULE_NO_CLUSTER (1ULL << 22) /* Deny on Cluster. */
|
||||
#define CMD_NO_ASYNC_LOADING (1ULL << 23)
|
||||
#define CMD_NO_MULTI (1ULL << 24)
|
||||
#define CMD_MOVABLE_KEYS (1ULL << 25) /* The legacy range spec doesn't cover all keys. \
|
||||
* Populated by populateCommandLegacyRangeSpec. */
|
||||
#define CMD_ALLOW_BUSY ((1ULL << 26))
|
||||
#define CMD_MODULE_GETCHANNELS (1ULL << 27) /* Use the modules getchannels interface. */
|
||||
#define CMD_TOUCHES_ARBITRARY_KEYS (1ULL << 28)
|
||||
#define CMD_ALL_DBS (1ULL << 29)
|
||||
/* Command flags. Please don't forget to add command flag documentation in struct
|
||||
* serverCommand in server.h file. */
|
||||
|
||||
/* Must be compatible with RedisModuleCommandArg. See moduleCopyCommandArgs. */
|
||||
typedef struct serverCommandArg {
|
||||
const char *name;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"BRPOPLPUSH": {
|
||||
"summary": "Pops an element from a list, pushes it to another list and returns it. Block until an element is available otherwise. Deletes the list if the last element was popped.",
|
||||
"summary": "Pops an element from a list, pushes it to another list and returns it. Blocks until an element is available otherwise. Deletes the list if the last element was popped.",
|
||||
"complexity": "O(1)",
|
||||
"group": "list",
|
||||
"since": "2.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"BZPOPMAX": {
|
||||
"summary": "Removes and returns the member with the highest score from one or more sorted sets. Blocks until a member available otherwise. Deletes the sorted set if the last element was popped.",
|
||||
"summary": "Removes and returns the member with the highest score from one or more sorted sets. Blocks until a member is available otherwise. Deletes the sorted set if the last element was popped.",
|
||||
"complexity": "O(log(N)) with N being the number of elements in the sorted set.",
|
||||
"group": "sorted_set",
|
||||
"since": "5.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"IMPORT-SOURCE": {
|
||||
"summary": "Mark this client as an import source when server is in import mode.",
|
||||
"summary": "Marks this client as an import source when the server is in import mode.",
|
||||
"complexity": "O(1)",
|
||||
"group": "connection",
|
||||
"since": "8.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"FLUSHSLOT": {
|
||||
"summary": "Remove all keys from the target slot.",
|
||||
"summary": "Removes all keys from the target slot.",
|
||||
"complexity": "O(N) where N is the number of keys in the target slot",
|
||||
"group": "cluster",
|
||||
"since": "9.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"GETSLOTMIGRATIONS": {
|
||||
"summary": "Get the status of ongoing and recently finished slot import and export operations.",
|
||||
"summary": "Gets the status of ongoing and recently finished slot import and export operations.",
|
||||
"complexity": "O(N), where N is the number of active slot import and export jobs.",
|
||||
"group": "cluster",
|
||||
"since": "9.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"REPLICATE": {
|
||||
"summary": "Configure a node as replica of a primary node or detach a replica from its primary.",
|
||||
"summary": "Configures a node as replica of a primary node or detaches a replica from its primary.",
|
||||
"complexity": "O(1)",
|
||||
"group": "cluster",
|
||||
"since": "3.0.0",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"history": [
|
||||
[
|
||||
"9.1.0",
|
||||
"Added shard id field to CLUSTER SHARDS response"
|
||||
"Added shard level `id` field and node level `availability-zone` field."
|
||||
]
|
||||
],
|
||||
"command_flags": [
|
||||
@@ -84,6 +84,9 @@
|
||||
"const": "online"
|
||||
}
|
||||
]
|
||||
},
|
||||
"availability-zone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
[
|
||||
"7.0.0",
|
||||
"Added additional networking metadata field."
|
||||
],
|
||||
[
|
||||
"9.1.0",
|
||||
"Added `availability-zone` field inside the networking metadata."
|
||||
]
|
||||
],
|
||||
"command_flags": [
|
||||
@@ -68,7 +72,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Array of node descriptions.",
|
||||
"description": "Map of additional networking metadata fields.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -77,6 +81,9 @@
|
||||
},
|
||||
"ip": {
|
||||
"type": "string"
|
||||
},
|
||||
"availability-zone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +118,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Array of node descriptions.",
|
||||
"description": "Map of additional networking metadata fields.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -120,6 +127,9 @@
|
||||
},
|
||||
"ip": {
|
||||
"type": "string"
|
||||
},
|
||||
"availability-zone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user