Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9913c92651 | ||
|
|
e4dcf76670 | ||
|
|
681246c248 | ||
|
|
d4c381df7a | ||
|
|
fc282edb61 | ||
|
|
2802b52b55 | ||
|
|
dccb672d83 | ||
|
|
db884a49bf | ||
|
|
26c2446172 | ||
|
|
a9354c7d10 | ||
|
|
70c05639d3 | ||
|
|
6ac9556613 | ||
|
|
0c5a8cca96 | ||
|
|
5a752e1978 | ||
|
|
7916e58211 | ||
|
|
c76d618209 | ||
|
|
f35b72dd17 | ||
|
|
080b99d982 | ||
|
|
d0eeee6e31 | ||
|
|
35eff3d49a | ||
|
|
e7cd611be1 | ||
|
|
89aee9556d | ||
|
|
50e91ca7db | ||
|
|
31d93c5928 | ||
|
|
62b766aef6 | ||
|
|
42fb340ce4 | ||
|
|
21d5e64ace | ||
|
|
067a0dac61 | ||
|
|
9af9c4deff | ||
|
|
779a20058b | ||
|
|
a26774cee1 | ||
|
|
9c5c2dc3a8 | ||
|
|
6e819e188b | ||
|
|
8e72b42a9a | ||
|
|
bdc78175b5 | ||
|
|
30fe743638 | ||
|
|
7278a0c26a | ||
|
|
ba18105722 | ||
|
|
ee4f0c5af7 | ||
|
|
e344b2b587 | ||
|
|
15e212bf69 | ||
|
|
941ca6c07f | ||
|
|
ffde2cf74d | ||
|
|
52b6c0a27b | ||
|
|
a904067f6f | ||
|
|
3ce29e0b11 | ||
|
|
17be6d92e8 | ||
|
|
f4a6721dfd | ||
|
|
0e06e67a36 | ||
|
|
045617e10a | ||
|
|
4a92d66ca2 | ||
|
|
2c8ae06b67 | ||
|
|
e2aaa9096f | ||
|
|
9f823685d2 | ||
|
|
1936746e63 | ||
|
|
ae6a2aa95c | ||
|
|
c8649f8e85 | ||
|
|
b351d5a321 | ||
|
|
fe8de4313f | ||
|
|
2ad2548747 | ||
|
|
f60370ce28 | ||
|
|
464aad9ee7 | ||
|
|
439b8da475 | ||
|
|
1caaf581f1 | ||
|
|
e46ddde28d | ||
|
|
8f70fcc65b | ||
|
|
8d3a1c978a | ||
|
|
c34e64848f | ||
|
|
1be66b98f5 | ||
|
|
423d1909c5 | ||
|
|
1bda797dc0 | ||
|
|
d5bae505ba | ||
|
|
099a2f40ec | ||
|
|
4bd614c52d | ||
|
|
d2c8a4b91e | ||
|
|
85408b7391 | ||
|
|
5a2f4a1e94 | ||
|
|
c4776cafcf | ||
|
|
4cbf903083 | ||
|
|
a91b57eff7 | ||
|
|
8359ce266c | ||
|
|
856c8a47d0 | ||
|
|
5f5f298a4a | ||
|
|
7f4bae8176 | ||
|
|
cee6d309d9 | ||
|
|
cf013b83df | ||
|
|
d424259a12 | ||
|
|
7f6de086fe | ||
|
|
8c6ebf84ae | ||
|
|
ba113c8337 | ||
|
|
348eda81e0 | ||
|
|
3f6bd4fc25 | ||
|
|
d6601de73f | ||
|
|
00023873a8 | ||
|
|
413c8be5ad | ||
|
|
1119ecae6f | ||
|
|
cc244370a2 | ||
|
|
3e7523aceb | ||
|
|
459acdeeb1 | ||
|
|
0b2cbf138e | ||
|
|
9e505e6cd8 | ||
|
|
68305c5b99 | ||
|
|
29622276ec | ||
|
|
3d1d3e23ac | ||
|
|
941cdc2759 | ||
|
|
1a58981868 | ||
|
|
c7b3ce90f1 | ||
|
|
819d291bd7 | ||
|
|
a51eb05b18 | ||
|
|
986dbf716e | ||
|
|
d4439bd41c | ||
|
|
e26a769d96 |
@@ -1 +1 @@
|
||||
codespell==2.2.5
|
||||
codespell==2.2.4
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
sudo apt-get install tcl8.6 tclx
|
||||
./runtest --verbose --tags -slow --dump-logs
|
||||
- name: module api test
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs
|
||||
run: ./runtest-moduleapi --verbose --dump-logs
|
||||
- name: validate commands.def up to date
|
||||
run: |
|
||||
touch src/commands/ping.json
|
||||
@@ -31,13 +31,13 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: make
|
||||
# build with TLS module just for compilation coverage
|
||||
run: make SANITIZER=address REDIS_CFLAGS='-Werror -DDEBUG_ASSERTIONS' BUILD_TLS=module
|
||||
run: make SANITIZER=address REDIS_CFLAGS='-Werror' BUILD_TLS=module
|
||||
- name: testprep
|
||||
run: sudo apt-get install tcl8.6 tclx -y
|
||||
- name: test
|
||||
run: ./runtest --verbose --tags -slow --dump-logs
|
||||
- name: module api test
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs
|
||||
run: ./runtest-moduleapi --verbose --dump-logs
|
||||
|
||||
build-debian-old:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -22,12 +22,12 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# Creates and uploads a Coverity build on a schedule
|
||||
name: Coverity Scan
|
||||
on:
|
||||
schedule:
|
||||
# Run once daily, since below 500k LOC can have 21 builds per week, per https://scan.coverity.com/faq#frequency
|
||||
- cron: '0 0 * * *'
|
||||
# Support manual execution
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
coverity:
|
||||
if: github.repository == 'redis/redis'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- name: Download and extract the Coverity Build Tool
|
||||
run: |
|
||||
wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=redis-unstable" -O cov-analysis-linux64.tar.gz
|
||||
mkdir cov-analysis-linux64
|
||||
tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64
|
||||
- name: Install Redis dependencies
|
||||
run: sudo apt install -y gcc tcl8.6 tclx procps libssl-dev
|
||||
- name: Build with cov-build
|
||||
run: cov-analysis-linux64/bin/cov-build --dir cov-int make
|
||||
- name: Upload the result
|
||||
run: |
|
||||
tar czvf cov-int.tgz cov-int
|
||||
curl \
|
||||
--form project=redis-unstable \
|
||||
--form email=${{ secrets.COVERITY_SCAN_EMAIL }} \
|
||||
--form token=${{ secrets.COVERITY_SCAN_TOKEN }} \
|
||||
--form file=@cov-int.tgz \
|
||||
https://scan.coverity.com/builds
|
||||
+79
-36
@@ -60,7 +60,7 @@ jobs:
|
||||
run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -179,7 +179,7 @@ jobs:
|
||||
run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: |
|
||||
make -C tests/modules 32bit # the script below doesn't have an argument, we must build manually ahead of time
|
||||
CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -265,7 +265,7 @@ jobs:
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: |
|
||||
CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --tls --dump-logs ${{github.event.inputs.test_args}}
|
||||
./runtest-moduleapi --verbose --dump-logs --tls --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: |
|
||||
@@ -309,7 +309,7 @@ jobs:
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: |
|
||||
CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: |
|
||||
@@ -487,7 +487,7 @@ jobs:
|
||||
sudo apt-get install tcl8.6 tclx valgrind -y
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: |
|
||||
@@ -552,7 +552,7 @@ jobs:
|
||||
sudo apt-get install tcl8.6 tclx valgrind -y
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: |
|
||||
@@ -585,7 +585,7 @@ jobs:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: make SANITIZER=address REDIS_CFLAGS='-DREDIS_TEST -Werror -DDEBUG_ASSERTIONS'
|
||||
run: make SANITIZER=address REDIS_CFLAGS='-DREDIS_TEST -Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -595,7 +595,7 @@ jobs:
|
||||
run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -642,7 +642,7 @@ jobs:
|
||||
run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -687,7 +687,7 @@ jobs:
|
||||
run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -732,7 +732,7 @@ jobs:
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: |
|
||||
CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --tls-module --dump-logs ${{github.event.inputs.test_args}}
|
||||
./runtest-moduleapi --verbose --dump-logs --tls-module --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: |
|
||||
@@ -779,7 +779,7 @@ jobs:
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: |
|
||||
CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: |
|
||||
@@ -813,10 +813,10 @@ jobs:
|
||||
run: make REDIS_CFLAGS='-Werror'
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --accurate --verbose --clients 1 --no-latency --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest --accurate --verbose --verbose --clients 1 --no-latency --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --clients 1 --no-latency --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --verbose --clients 1 --no-latency --dump-logs ${{github.event.inputs.test_args}}
|
||||
|
||||
test-macos-latest-sentinel:
|
||||
runs-on: macos-latest
|
||||
@@ -870,19 +870,13 @@ jobs:
|
||||
if: true && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
|
||||
build-macos:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-13, macos-15]
|
||||
runs-on: ${{ matrix.os }}
|
||||
test-freebsd:
|
||||
runs-on: macos-13
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'macos')
|
||||
!contains(github.event.inputs.skipjobs, 'freebsd') && !(contains(github.event.inputs.skiptests, 'redis') && contains(github.event.inputs.skiptests, 'modules'))
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
@@ -896,14 +890,27 @@ jobs:
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
- name: test
|
||||
uses: cross-platform-actions/action@v0.21.1
|
||||
env:
|
||||
MAKE: gmake
|
||||
with:
|
||||
operating_system: freebsd
|
||||
environment_variables: MAKE
|
||||
version: 13.2
|
||||
memory: 8GB
|
||||
shell: bash
|
||||
run: |
|
||||
sudo pkg install -y bash gmake lang/tcl86 lang/tclx
|
||||
gmake
|
||||
if echo "${{github.event.inputs.skiptests}}" | grep -vq redis ; then ./runtest --verbose --timeout 2400 --no-latency --dump-logs ${{github.event.inputs.test_args}} ; fi
|
||||
if echo "${{github.event.inputs.skiptests}}" | grep -vq modules ; then ./runtest-moduleapi --verbose --timeout 2400 --no-latency --dump-logs ${{github.event.inputs.test_args}} ; fi
|
||||
|
||||
test-freebsd:
|
||||
test-freebsd-sentinel:
|
||||
runs-on: macos-13
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'freebsd')
|
||||
!contains(github.event.inputs.skipjobs, 'freebsd') && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- name: prep
|
||||
@@ -911,21 +918,57 @@ jobs:
|
||||
run: |
|
||||
echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV
|
||||
echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV
|
||||
echo "skipjobs: ${{github.event.inputs.skipjobs}}"
|
||||
echo "skiptests: ${{github.event.inputs.skiptests}}"
|
||||
echo "test_args: ${{github.event.inputs.test_args}}"
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: test
|
||||
uses: cross-platform-actions/action@v0.22.0
|
||||
uses: cross-platform-actions/action@v0.21.1
|
||||
with:
|
||||
operating_system: freebsd
|
||||
environment_variables: MAKE
|
||||
version: 13.2
|
||||
memory: 8GB
|
||||
shell: bash
|
||||
run: |
|
||||
sudo pkg install -y bash gmake lang/tcl86 lang/tclx
|
||||
gmake
|
||||
./runtest --single unit/keyspace --single unit/auth --single unit/networking --single unit/protocol
|
||||
if echo "${{github.event.inputs.skiptests}}" | grep -vq sentinel ; then ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} ; fi
|
||||
|
||||
test-freebsd-cluster:
|
||||
runs-on: macos-13
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'freebsd') && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV
|
||||
echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV
|
||||
echo "skipjobs: ${{github.event.inputs.skipjobs}}"
|
||||
echo "skiptests: ${{github.event.inputs.skiptests}}"
|
||||
echo "test_args: ${{github.event.inputs.test_args}}"
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: test
|
||||
uses: cross-platform-actions/action@v0.21.1
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: 13.2
|
||||
memory: 8GB
|
||||
shell: bash
|
||||
run: |
|
||||
sudo pkg install -y bash gmake lang/tcl86 lang/tclx
|
||||
gmake
|
||||
if echo "${{github.event.inputs.skiptests}}" | grep -vq cluster ; then ./runtest-cluster ${{github.event.inputs.cluster_test_args}} ; fi
|
||||
|
||||
test-alpine-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -958,7 +1001,7 @@ jobs:
|
||||
run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -997,7 +1040,7 @@ jobs:
|
||||
run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -1034,7 +1077,7 @@ jobs:
|
||||
run: ./runtest --log-req-res --no-latency --dont-clean --force-resp3 --tags -slow --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --log-req-res --no-latency --dont-clean --force-resp3 --dont-pre-clean --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest-moduleapi --log-req-res --no-latency --dont-clean --force-resp3 --dont-pre-clean --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel --log-req-res --dont-clean --force-resp3 ${{github.event.inputs.cluster_test_args}}
|
||||
|
||||
@@ -23,7 +23,6 @@ jobs:
|
||||
run: |
|
||||
./runtest \
|
||||
--host 127.0.0.1 --port 6379 \
|
||||
--verbose \
|
||||
--tags -slow
|
||||
- name: Archive redis log
|
||||
if: ${{ failure() }}
|
||||
@@ -42,7 +41,7 @@ jobs:
|
||||
run: make REDIS_CFLAGS=-Werror
|
||||
- name: Start redis-server
|
||||
run: |
|
||||
./src/redis-server --cluster-enabled yes --daemonize yes --save "" --logfile external-redis-cluster.log \
|
||||
./src/redis-server --cluster-enabled yes --daemonize yes --save "" --logfile external-redis.log \
|
||||
--enable-protected-configs yes --enable-debug-command yes --enable-module-command yes
|
||||
- name: Create a single node cluster
|
||||
run: ./src/redis-cli cluster addslots $(for slot in {0..16383}; do echo $slot; done); sleep 5
|
||||
@@ -50,7 +49,6 @@ jobs:
|
||||
run: |
|
||||
./runtest \
|
||||
--host 127.0.0.1 --port 6379 \
|
||||
--verbose \
|
||||
--cluster-mode \
|
||||
--tags -slow
|
||||
- name: Archive redis log
|
||||
@@ -58,7 +56,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-external-cluster-log
|
||||
path: external-redis-cluster.log
|
||||
path: external-redis.log
|
||||
|
||||
test-external-nodebug:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -70,16 +68,15 @@ jobs:
|
||||
run: make REDIS_CFLAGS=-Werror
|
||||
- name: Start redis-server
|
||||
run: |
|
||||
./src/redis-server --daemonize yes --save "" --logfile external-redis-nodebug.log
|
||||
./src/redis-server --daemonize yes --save "" --logfile external-redis.log
|
||||
- name: Run external test
|
||||
run: |
|
||||
./runtest \
|
||||
--host 127.0.0.1 --port 6379 \
|
||||
--verbose \
|
||||
--tags "-slow -needs:debug"
|
||||
- name: Archive redis log
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-external-redis-nodebug-log
|
||||
path: external-redis-nodebug.log
|
||||
name: test-external-redis-log
|
||||
path: external-redis.log
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup nodejs
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install packages
|
||||
run: npm install ajv
|
||||
- name: linter
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: pip cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
|
||||
+519
-162
@@ -1,5 +1,5 @@
|
||||
Redis Community Edition 7.4 release notes
|
||||
=========================================
|
||||
Redis 7.2 release notes
|
||||
=======================
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
@@ -13,60 +13,51 @@ SECURITY: There are security fixes in the release.
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.4.7 Released Sun 2 Nov 2025 10:00:00 IST
|
||||
Redis 7.2.12 Released Sun 2 Nov 2025 10:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Update urgency: `HIGH`: There is a critical bug that may affect a subset of users.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- An overflow in `HyperLogLog` with 2GB+ entries may result in a Redis crash
|
||||
* An overflow in `HyperLogLog` with 2GB+ entries may result in a Redis crash
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.4.6 Released Fri 3 Oct 2025 10:00:00 IST
|
||||
Redis 7.2.11 Released Fri 3 Oct 2025 10:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
### Security fixes
|
||||
|
||||
- (CVE-2025-49844) A Lua script may lead to remote code execution
|
||||
- (CVE-2025-46817) A Lua script may lead to integer overflow and potential RCE
|
||||
- (CVE-2025-46818) A Lua script can be executed in the context of another user
|
||||
- (CVE-2025-46819) LUA out-of-bound read
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- #14330 Potential use-after-free after pubsub and Lua defrag
|
||||
- #14319 Potential crash on Lua script defrag
|
||||
- #14164 Prevent `CLIENT UNBLOCK` from unblocking `CLIENT PAUSE`
|
||||
- #14165 Endless client blocking for blocking commands
|
||||
- #14163 `EVAL` crash when error table is empty
|
||||
- #14227 `HINCRBYFLOAT` removes field expiration on replica
|
||||
* (CVE-2025-49844) A Lua script may lead to remote code execution
|
||||
* (CVE-2025-46817) A Lua script may lead to integer overflow and potential RCE
|
||||
* (CVE-2025-46818) A Lua script can be executed in the context of another user
|
||||
* (CVE-2025-46819) LUA out-of-bound read
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.4.5 Released Sun 6 Jul 2025 12:00:00 IST
|
||||
Redis 7.2.10 Released Sun 6 Jul 2025 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
### Security fixes
|
||||
|
||||
- (CVE-2025-32023) Fix out-of-bounds write in `HyperLogLog` commands
|
||||
- (CVE-2025-48367) Retry accepting other connections even if the accepted connection reports an error
|
||||
* (CVE-2025-32023) Fix out-of-bounds write in `HyperLogLog` commands
|
||||
* (CVE-2025-48367) Retry accepting other connections even if the accepted connection reports an error
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.4.4 Released Tue 27 May 2025 12:00:00 IST
|
||||
Redis 7.2.9 Released Tue 27 May 2025 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
### Security fixes
|
||||
|
||||
- (CVE-2025-27151) redis-check-aof may lead to stack overflow and potential RCE
|
||||
* (CVE-2025-27151) redis-check-aof may lead to stack overflow and potential RCE
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@@ -75,52 +66,46 @@ Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.4.3 Released Wed 23 Apr 2025 12:00:00 IST
|
||||
Redis 7.2.8 Released Wed 23 Apr 2025 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
### Security fixes
|
||||
|
||||
- (CVE-2025-21605) An unauthenticated client can cause an unlimited growth of output buffers
|
||||
* (CVE-2025-21605) An unauthenticated client can cause an unlimited growth of output buffers
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- #13661 `FUNCTION FLUSH` - memory leak when using jemalloc
|
||||
- #13793 `WAITAOF` returns prematurely
|
||||
- #13853 `SLAVEOF` - crash when clients are blocked on lazy free
|
||||
- #13863 `RANDOMKEY` - infinite loop during client pause
|
||||
- #13877 ShardID inconsistency when both primary and replica support it
|
||||
* #12817, #12905 Fix race condition issues between the main thread and module threads
|
||||
* #13863 `RANDOMKEY` - infinite loop during client pause
|
||||
* #13877 ShardID inconsistency when both primary and replica support it
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis Community Edition 7.4.2 Released Mon 6 Jan 2025 12:30:00 IDT
|
||||
Redis 7.2.7 Released Mon 6 Jan 2025 12:30:00 IDT
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
### Security fixes
|
||||
Security fixes
|
||||
==============
|
||||
* (CVE-2024-46981) Lua script commands may lead to remote code execution
|
||||
* (CVE-2024-51741) Denial-of-service due to malformed ACL selectors
|
||||
|
||||
- (CVE-2024-46981) Lua script commands may lead to remote code execution
|
||||
- (CVE-2024-51741) Denial-of-service due to malformed ACL selectors
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- #13627 Crash on module memory defragmentation
|
||||
- #13338 Streams: `XINFO` lag field is wrong when tombstone is after the `last_id` of the consume group
|
||||
- #13473 Streams: `XTRIM` does not update the maximal tombstone, leading to an incorrect lag
|
||||
- #13470 `INFO` after `HDEL` show wrong number of hash keys with expiration
|
||||
- #13476 Fix a race condition in the `cache_memory` of `functionsLibCtx`
|
||||
- #13626 Memory leak on failed RDB loading
|
||||
- #13539 Hash: fix key ref for a hash that no longer has fields with expiration on `RENAME`/`MOVE`/`SWAPDB`/`RESTORE`
|
||||
- #13443 Cluster: crash when loading cluster config
|
||||
- #13422 Cluster: `CLUSTER SHARDS` returns empty array
|
||||
- #13465 Cluster: incompatibility with older node versions
|
||||
- #13608 Cluster: `SORT ... GET #`: incorrect error message
|
||||
Bug fixes
|
||||
=========
|
||||
* #13380 Possible crash due to OOM panic on invalid command
|
||||
* #13338 Streams: `XINFO` lag field is wrong when tombstone is after the `last_id` of the consume group
|
||||
* #13473 Streams: `XTRIM` does not update the maximal tombstone, leading to an incorrect lag
|
||||
* #13311 Cluster: crash due to unblocking client during slot migration
|
||||
* #13443 Cluster: crash when loading cluster config
|
||||
* #13422 Cluster: `CLUSTER SHARDS` returns empty array
|
||||
* #13465 Cluster: incompatibility with older node versions
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis Community Edition 7.4.1 Released Wed 02 Oct 2024 20:17:04 IDT
|
||||
Redis 7.2.6 Released Wed 02 Oct 2024 20:17:04 IDT
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
@@ -132,153 +117,525 @@ Security fixes
|
||||
* (CVE-2024-31227) Potential Denial-of-service due to malformed ACL selectors.
|
||||
* (CVE-2024-31228) Potential Denial-of-service due to unbounded pattern matching.
|
||||
|
||||
Bug fixes
|
||||
=========
|
||||
|
||||
* Fixed crashes in cluster mode (#13315)
|
||||
|
||||
================================================================================
|
||||
Redis Community Edition 7.4.0 GA Released Mon Jul 29 2024 9:00:00 IDT
|
||||
Redis 7.2.5 Released Thu 16 May 2024 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
This is the General Availability release of Redis Community Edition 7.4.
|
||||
|
||||
Changes to new 7.4 features (compared to 7.4 RC2)
|
||||
=================================================
|
||||
* #13391,#13438 Hash - expiration of individual fields: RDB file format changes
|
||||
* #13372 Hash - expiration of individual fields: rename and fix counting of `expired_subkeys` metric
|
||||
* #13372 Hash - expiration of individual fields: rename `INFO` keyspace field to `subexpiry`
|
||||
|
||||
Configuration parameters
|
||||
========================
|
||||
* #13400 Add hide-user-data-from-log - allows hiding user data from the log file
|
||||
Upgrade urgency MODERATE: Program an upgrade of the server, but it's not urgent.
|
||||
|
||||
Bug fixes
|
||||
=========
|
||||
* #13407 trigger Lua GC after `SCRIPT LOAD`
|
||||
* #13380 Fix possible crash due to OOM panic on invalid command
|
||||
* #13383 `FUNCTION FLUSH` - improve Lua GC behavior and fix thread race in ASYNC mode
|
||||
* #13408 `HEXPIRE`-like commands should emit HDEL keyspace notification if expire time is in the past
|
||||
|
||||
* A single shard cluster leaves failed replicas in CLUSTER SLOTS instead of removing them (#12824)
|
||||
* Crash in LSET command when replacing small items and exceeding 4GB (#12955)
|
||||
* Blocking commands timeout is reset due to re-processing command (#13004)
|
||||
* Conversion of numbers in Lua args to redis args can fail. Bug introduced in 7.2.0 (#13115)
|
||||
|
||||
Bug fixes in CLI tools
|
||||
======================
|
||||
|
||||
* redis-cli: --count (for --scan, --bigkeys, etc) was ignored unless --pattern was also used (#13092)
|
||||
* redis-check-aof: incorrectly considering data in manifest format as MP-AOF (#12958)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis Community Edition 7.4 RC2 Released Thu 27 Jun 2024 10:00:00 IST
|
||||
Redis 7.2.4 Released Tue 09 Jan 2024 10:45:52 IST
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the second Release Candidate for Redis Community Edition 7.4.
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Performance and resource utilization improvements
|
||||
Security fixes
|
||||
==============
|
||||
* (CVE-2023-41056) In some cases, Redis may incorrectly handle resizing of memory
|
||||
buffers which can result in incorrect accounting of buffer sizes and lead to
|
||||
heap overflow and potential remote code execution.
|
||||
|
||||
Bug fixes
|
||||
=========
|
||||
|
||||
* Fix crashes of cluster commands clusters with mixed versions of 7.0 and 7.2 (#12805, #12832)
|
||||
* Fix slot ownership not being properly handled when deleting a slot from a node (#12564)
|
||||
* Fix atomicity issues with the RedisModuleEvent_Key module API event (#12733)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.2.3 Released Wed 01 Nov 2023 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: HIGH, Fixes critical bugs affecting most users.
|
||||
|
||||
Bug fixes
|
||||
=========
|
||||
|
||||
* Fix file descriptor leak preventing deleted files from freeing disk space on
|
||||
replicas (#12693)
|
||||
* Fix a possible crash after cluster node removal (#12702)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.2.2 Released Wed 18 Oct 2023 10:33:40 IDT
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Security fixes
|
||||
==============
|
||||
|
||||
* (CVE-2023-45145) The wrong order of listen(2) and chmod(2) calls creates a
|
||||
race condition that can be used by another process to bypass desired Unix
|
||||
socket permissions on startup.
|
||||
|
||||
|
||||
Platform / toolchain support related changes
|
||||
=================================================
|
||||
* #13296 Optimize CPU cache efficiency
|
||||
|
||||
Changes to new 7.4 new features (compared to 7.4 RC1)
|
||||
=====================================================
|
||||
* #13343 Hash - expiration of individual fields: when key does not exist - reply with an array (nonexisting code for each field)
|
||||
* #13329 Hash - expiration of individual fields: new keyspace event: `hexpired`
|
||||
* Fix compilation error on MacOS 13 (#12611)
|
||||
|
||||
Modules API - Potentially breaking changes to new 7.4 features (compared to 7.4 RC1)
|
||||
====================================================================================
|
||||
* #13326 Hash - expiration of individual fields: avoid lazy expire when called from a Modules API function
|
||||
Bug fixes
|
||||
=========
|
||||
|
||||
* WAITAOF could timeout in the absence of write traffic in case a new AOF is
|
||||
created and an AOF rewrite can't immediately start (#12620)
|
||||
|
||||
Redis cluster
|
||||
=============
|
||||
|
||||
* Fix crash when running rebalance command in a mixed cluster of 7.0 and 7.2
|
||||
nodes (#12604)
|
||||
* Fix the return type of the slot number in cluster shards to integer, which
|
||||
makes it consistent with past behavior (#12561)
|
||||
* Fix CLUSTER commands are called from modules or scripts to return TLS info
|
||||
appropriately (#12569)
|
||||
|
||||
Changes in CLI tools
|
||||
====================
|
||||
|
||||
* redis-cli, fix crash on reconnect when in SUBSCRIBE mode (#12571)
|
||||
|
||||
Module API changes
|
||||
==================
|
||||
|
||||
* Fix overflow calculation for next timer event (#12474)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis Community Edition 7.4 RC1 Released Thu 6 Jun 2024 10:00:00 IST
|
||||
Redis 7.2.1 Released Wed 06 Sep 2023 15:00:00 IDT
|
||||
================================================================================
|
||||
|
||||
Note: License changed - see LICENSE.txt
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Upgrade urgency LOW: This is the first Release Candidate for Redis Community Edition 7.4.
|
||||
Security Fixes
|
||||
==============
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 7.2.5.
|
||||
* (CVE-2023-41053) Redis does not correctly identify keys accessed by SORT_RO and,
|
||||
as a result, may grant users executing this command access to keys that are not
|
||||
explicitly authorized by the ACL configuration.
|
||||
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Fix crashes when joining a node to an existing 7.0 Redis Cluster (#12538)
|
||||
* Correct request_policy and response_policy command tips on for some admin /
|
||||
configuration commands (#12545, #12530)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.2.0 GA Released Tue Aug 15 12:00:00 IDT 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the first stable Release for Redis 7.2.
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* redis-cli in cluster mode handles `unknown-endpoint` (#12273)
|
||||
* Update request / response policy hints for a few commands (#12417)
|
||||
* Ensure that the function load timeout is disabled during loading from RDB/AOF and on replicas. (#12451)
|
||||
* Fix false success and a memory leak for ACL selector with bad parenthesis combination (#12452)
|
||||
* Fix the assertion when script timeout occurs after it signaled a blocked client (#12459)
|
||||
|
||||
Fixes for issues in previous releases of Redis 7.2
|
||||
--------------------------------------------------
|
||||
|
||||
* Update MONITOR client's memory correctly for INFO and client-eviction (#12420)
|
||||
* The response of cluster nodes was unnecessarily adding an extra comma when no
|
||||
hostname was present. (#12411)
|
||||
|
||||
================================================================================
|
||||
Redis 7.2 RC3 Released Mon July 10 12:00:00 IDT 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the third Release Candidate for Redis 7.2.
|
||||
Upgrade urgency SECURITY: If you're using a previous release candidate of 7.2.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2022-24834) A specially crafted Lua script executing in Redis can trigger
|
||||
a heap overflow in the cjson and cmsgpack libraries, and result in heap
|
||||
corruption and potentially remote code execution. The problem exists in all
|
||||
versions of Redis with Lua scripting support, starting from 2.6, and affects
|
||||
only authenticated and authorized users.
|
||||
* (CVE-2023-36824) Extracting key names from a command and a list of arguments
|
||||
may, in some cases, trigger a heap overflow and result in reading random heap
|
||||
memory, heap corruption and potentially remote code execution. Specifically:
|
||||
using COMMAND GETKEYS* and validation of key names in ACL rules.
|
||||
|
||||
New Features
|
||||
============
|
||||
* #13303 Hash - expiration of individual fields. 9 commands were introduced:
|
||||
- `HEXPIRE` and `HPEXPIRE` set the remaining time to live for specific fields
|
||||
- `HEXPIREAT` and `HPEXPIREAT` set the expiration time to a UNIX timestamp for specific fields
|
||||
- `HPERSIST` removes the expiration for specific fields
|
||||
- `HEXPIRETIME` and `HPEXPIRETIME` get the expiration time for specific fields
|
||||
- `HTTL` and `HPTTL` get the remaining time to live for specific fields
|
||||
* #13117 `XREAD`: new id value `+` to start reading from the last message
|
||||
* #12765 `HSCAN`: new [NOVALUES] flag to report only field names
|
||||
* #12728 `SORT`, `SORT_RO`: allow `BY` and `GET` options in cluster mode when the pattern maps to the same slot as the key
|
||||
* #12299 `CLIENT KILL`: new optional filter: `MAXAGE maxage` - retain connections older than `maxage` seconds
|
||||
* #12971 Lua: expose `os.clock()` API for getting the elapsed time of Lua code execution
|
||||
* #13276 Allow `SPUBLISH` command within `MULTI ... EXEC` transactions on replica
|
||||
|
||||
Bug fixes
|
||||
=========
|
||||
* #12898 `XREADGROUP`: fix entries-read inconsistency between master and replicas
|
||||
* #13042 `SORT ... STORE`: fix created lists to respect list compression and packing configs
|
||||
* #12817, #12905 Fix race condition issues between the main thread and module threads
|
||||
* #12577 Unsubscribe all clients from replica for shard channel if the master ownership changes
|
||||
* #12622 `WAITAOF` could timeout or hang if used after a module command that propagated effects only to replicas and not to AOF
|
||||
* #11734 `BITCOUNT` and `BITPOS` with nonexistent key and illegal arguments return an error, not 0
|
||||
* #12394 `BITCOUNT`: check for wrong argument before checking if key exists
|
||||
* #12961 Allow execution of read-only transactions when out of memory
|
||||
* #13274 Fix crash when a client performs ACL change that disconnects itself
|
||||
* #13311 Cluster: Fix crash due to unblocking client during slot migration
|
||||
New administrative and introspection commands and command arguments
|
||||
-------------------------------------------------------------------
|
||||
|
||||
Security improvements
|
||||
=====================
|
||||
* #13108 Lua: LRU eviction for scripts generated with `EVAL` *** BEHAVIOR CHANGE ***
|
||||
* #12961 Restrict the total request size of `MULTI ... EXEC` transactions
|
||||
* #12860 Redact ACL username information and mark '*-key-file-pass configs' as sensitive
|
||||
* Make SENTINEL CONFIG [SET|GET] variadic. (#10362)
|
||||
|
||||
Potentially Breaking / Behavior Changes
|
||||
=======================================
|
||||
|
||||
* Cluster SHARD IDs are no longer visible in the cluster nodes output,
|
||||
introduced in 7.2-RC1. (#10536, #12166)
|
||||
* When calling PUBLISH with a RESP3 client that's also subscribed to the same channel,
|
||||
the order is changed and the reply is sent before the published message (#12326)
|
||||
|
||||
New configuration options
|
||||
=========================
|
||||
|
||||
* Add a new loglevel "nothing" to disable logging (#12133)
|
||||
* Add cluster-announce-human-nodename - a unique identifier for a node that is
|
||||
be used in logs for debugging (#9564)
|
||||
|
||||
Other General Improvements
|
||||
==========================
|
||||
|
||||
* Allow CLUSTER SLOTS / SHARDS commands during loading (#12269)
|
||||
* Support TLS service when "tls-cluster" is not enabled and persist both plain
|
||||
and TLS port in nodes.conf (#12233)
|
||||
* Update SPOP and RESTORE commands to replicate unlink commands to replicas
|
||||
when the server is configured to use async server deletes (#12320)
|
||||
* Try lazyfree the temporary zset in ZUNION / ZINTER / ZDIFF (#12229)
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
* #12838 Improve performance when many clients call `PUNSUBSCRIBE` / `SUNSUBSCRIBE` simultaneously
|
||||
* #12627 Reduce lag when waking `WAITAOF` clients and there is not much traffic
|
||||
* #12754 Optimize `KEYS` when pattern includes hashtag and implies a single slot
|
||||
* #11695 Reduce memory and improve performance by replacing cluster metadata with slot specific dictionaries
|
||||
* #13087 `SCRIPT FLUSH ASYNC` now does not block the main thread
|
||||
* #12996 Active memory defragmentation efficiency improvements
|
||||
* #12899 Improve performance of read/update operation during rehashing
|
||||
* #12536 `SCAN ... MATCH`: Improve performance when the pattern implies cluster slot
|
||||
* #12450 `ZRANGE ... LIMIT`: improved performance
|
||||
|
||||
Other general improvements
|
||||
==========================
|
||||
* #13133 Lua: allocate VM code with jemalloc instead of libc and count it as used memory *** BEHAVIOR CHANGE ***
|
||||
* #12171 `ACL LOAD`: do not disconnect all clients *** BEHAVIOR CHANGE ***
|
||||
* #13020 Allow adjusting defrag configurations while active defragmentation is running
|
||||
* #12949 Increase the accuracy of avg_ttl (the average keyspace keys TTL)
|
||||
* #12977 Allow running `WAITAOF` in scripts
|
||||
* #12782 Implement TCP keep-alive across most Unix-like systems
|
||||
* #12707 Improved error codes when rejecting scripts in cluster mode
|
||||
* #12596 Support `XREAD ... BLOCK` in scripts; rejected only if it ends up blocking
|
||||
* Optimize PSUBSCRIBE and PUNSUBSCRIBE from O(N*M) to O(N) (#12298)
|
||||
* Optimize SCAN, SSCAN, HSCAN, ZSCAN commands (#12209)
|
||||
* Set Jemalloc --disable-cache-oblivious to reduce memory overhead (#12315)
|
||||
* Optimize ZINTERCARD to avoid create a temporary zset (#12229)
|
||||
* Optimize HRANDFIELD and ZRANDMEMBER listpack encoded (#12205)
|
||||
* Numerous other optimizations (#12155, #12082, #11626, #11944, #12316, #12250,
|
||||
#12177, #12185)
|
||||
|
||||
New metrics
|
||||
===========
|
||||
* #12849 `INFO`: `pubsub_clients` - number of clients in Pub/Sub mode
|
||||
* #12966 `INFO`: `watching_clients` - number of clients that are watching keys
|
||||
* #12966 `INFO`: `total_watched_keys` - number of watched keys
|
||||
* #12476 `INFO`: `client_query_buffer_limit_disconnections` - count client input buffer OOM events
|
||||
* #12476 `INFO`: `client_output_buffer_limit_disconnections` - count client output buffer OOM events
|
||||
* #12996 `INFO`: `allocator_muzzy` - memory returned to the OS but still shows as RSS until the OS reclaims it
|
||||
* #13108 `INFO`: `evicted_scripts` - number of evicted eval scripts. Users can check it to see if they are abusing EVAL
|
||||
* #12996 `MEMORY STATS`: `allocator.muzzy` - memory returned to the OS but still shows as RSS until the OS reclaims it
|
||||
* #12913 `INFO MEMORY` `mem_overhead_db_hashtable_rehashing` - memory resharding overhead (only the memory that will be released soon)
|
||||
* #12913 `MEMORY STATS': `overhead.db.hashtable.lut` - total overhead of dictionary buckets in databases
|
||||
* #12913 `MEMORY STATS': `overhead.db.hashtable.rehashing` - temporary memory overhead of database dictionaries currently being rehashed
|
||||
* #12913 `MEMORY STATS': `db.dict.rehashing.count` - number of top level dictionaries currently being rehashed
|
||||
* #12966 `CLIENT LIST`: `watch` - number of keys each client is currently watching
|
||||
|
||||
Modules API
|
||||
===========
|
||||
* #12985 New API calls: `RM_TryCalloc` and `RM_TryRealloc` - allow modules to handle memory allocation failures gracefully
|
||||
* #13069 New API call: `RM_ClusterKeySlot` - which slot a key will hash to
|
||||
* #13069 New API call: `RM_ClusterCanonicalKeyNameInSlot` - get a consistent key that will map to a slot
|
||||
* #12486 New API call: `RM_AddACLCategory` - allow modules to declare new ACL categories
|
||||
Changes in CLI tools
|
||||
====================
|
||||
|
||||
Configuration parameters
|
||||
========================
|
||||
* #12178 New configuration parameters: `max-new-connections-per-cycle` and `max-new-tls-connections-per-cycle` to limit the number of new client connections per event-loop cycle
|
||||
* #7351 Rename some CPU configuration parameters for style alignment. Added alias to the old names to avoid breaking change
|
||||
* redis-cli: Handle RESP3 double responses that contain a NaN (#12254)
|
||||
* redis-cli: Support URIs with IPv6 (#11834)
|
||||
|
||||
CLI tools
|
||||
Module API changes
|
||||
==================
|
||||
|
||||
* Align semantics of the new (v7.2 RC2) RM_ReplyWithErrorFormat with RM_ReplyWithError.
|
||||
This is a breaking change that affects the generated error code. (#12321)
|
||||
* Forbid RM_AddPostNotificationJob on loading and on read-only replicas (#12304)
|
||||
* Add ability for module command filter to know which client is being handled (#12219)
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
* #10609 redis-cli: new `-t <timeout>` argument: specify server connection timeout in seconds
|
||||
* #11315 redis-cli: new `-4` and `-6` flags to prefer IPV4 or IPV6 on DNS lookup
|
||||
* #12862 redis-cli: allows pressing up arrow to return any command (including sensitive commands which are still not persisted)
|
||||
* #12543 redis-cli: add reverse history search (like Ctrl+R in terminals)
|
||||
* #12826 redis-cli: add `--keystats` and `--keystats-samples` to combines `--memkeys` and `--bigkeys` with additional distribution data
|
||||
* #12735 redis-cli: fix: `--bigkeys` and `--memkeys` now work on cluster replicas
|
||||
* #9411 redis-benchmark: add support for binary strings
|
||||
* #12986 redis-benchmark: fix: pick random slot for a node to distribute operation across slots
|
||||
|
||||
* Fix broken protocol when PUBLISH is used inside MULTI when the RESP3
|
||||
publishing client is also subscribed for the channel (#12326)
|
||||
* Fix WAIT to be effective after a blocked module command being unblocked (#12220)
|
||||
* Re-enable downscale rehashing while there is a fork child (#12276)
|
||||
* Fix possible hang in HRANDFIELD, SRANDMEMBER, ZRANDMEMBER when used with `<count>` (#12276)
|
||||
* Improve fairness issue in RANDOMKEY, HRANDFIELD, SRANDMEMBER, ZRANDMEMBER, SPOP, and eviction (#12276)
|
||||
* Cluster: fix a race condition where a slot migration may revert on a subsequent failover or node joining (#12344)
|
||||
|
||||
Fixes for issues in previous releases of Redis 7.2
|
||||
--------------------------------------------------
|
||||
|
||||
* Fix XREADGROUP BLOCK with ">" from hanging (#12301)
|
||||
* Fix assertion when a blocked command is rejected when re-processed. (#12247)
|
||||
* Fix use after free on a blocking RM_Call. (#12342)
|
||||
|
||||
================================================================================
|
||||
Redis 7.2 RC2 Released Mon May 15 12:00:00 IST 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the second Release Candidate for Redis 7.2.
|
||||
|
||||
INFO fields and introspection changes
|
||||
=====================================
|
||||
|
||||
* Add a few low level event loop metrics to help diagnose latency (#11963)
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
|
||||
* Minor performance improvement to SADD and HSET (#12019)
|
||||
|
||||
Platform / toolchain support related changes
|
||||
=================================================
|
||||
|
||||
* Upgrade to Jemalloc 5.3.0, resolves a rare fork child hang (#12115)
|
||||
* Fix a compiler fortification induced crash when used with link time optimizations (#11982)
|
||||
* Fix local clients detection, 127.*.*.* instead of 127.0.0.1 (#11664)
|
||||
* Report AOF failure status to systemd in shutdown (#12065)
|
||||
|
||||
Changes in CLI tools
|
||||
====================
|
||||
|
||||
* redis-cli: Reimplement and improve help hints based on actual command arg docs (#10515)
|
||||
* redis-cli: Add option --count for tuning SCAN based features (#12042)
|
||||
* redis-benchmark: Add --seed option to seed the random number generator (#11945)
|
||||
|
||||
Module API changes
|
||||
==================
|
||||
|
||||
* Add RM_RdbLoad and RM_RdbSave APIs (#11852)
|
||||
* Add RM_ReplyWithErrorFormat that can support format string (#11923)
|
||||
* Fix: Delete empty key when RM_ZsetAdd, RM_ZsetIncrby, RM_StreamAdd fail (#12129)
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* LPOS with RANK set to LONG_MIN returning wrong result (#12167)
|
||||
* Avoid unnecessary full sync after master restart in a rare case (#12088)
|
||||
* Iterate clients fairly when processing background chores (#12025)
|
||||
* Avoid incorrect shrinking of query buffer when reading large data from clients (#12000)
|
||||
* Sentinel: Fix config rewrite error when old known-slave is used (#11775)
|
||||
* ACL: Disconnect pub-sub subscribers when revoking allchannels permission (#11992)
|
||||
* Add a missing fsync of AOF file in rare cases (#11973)
|
||||
|
||||
Fixes for issues in previous releases of Redis 7.2
|
||||
--------------------------------------------------
|
||||
|
||||
* Fix tracking of command duration metrics for MULTI, EVAL, WAIT and modules (#11970)
|
||||
|
||||
================================================================================
|
||||
Redis 7.2 RC1 Released Wed Mar 22 12:00:00 IST 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the first Release Candidate for Redis 7.2.
|
||||
|
||||
Redis Release Candidate (RC) versions are early versions that are made available
|
||||
for early adopters in the community to test them. We do not consider
|
||||
them suitable for production environments.
|
||||
|
||||
Introduction to the Redis 7.2 release
|
||||
=====================================
|
||||
|
||||
Redis 7.2 includes optimizations, several new commands, some improvements,
|
||||
bug fixes, and several new module APIs.
|
||||
|
||||
In particular, users should be aware of the following changes:
|
||||
|
||||
1. Redis 7.2 uses a new format (version 11) for RDB files, which is incompatible
|
||||
with older versions.
|
||||
2. See section about breaking changes mentioned below.
|
||||
3. If you use modules, see the module API breaking changes section below.
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 7.0.10.
|
||||
Each one includes the PR number that added it so that you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
New Features
|
||||
============
|
||||
|
||||
* Introduce WAITAOF command, to block the client until a specified number
|
||||
of Redises have synced all previous write commands to the AOF on disk,
|
||||
see https://redis.io/commands/waitaof/
|
||||
|
||||
New user commands or command arguments
|
||||
--------------------------------------
|
||||
|
||||
* WAITAOF blocks until writes have been synced to disk (#11713)
|
||||
* Add WITHSCORE option to ZRANK and ZREVRANK (#11235)
|
||||
|
||||
New administrative and introspection commands and command arguments
|
||||
-------------------------------------------------------------------
|
||||
|
||||
* CLIENT SETINFO lets client library report name and version Redis (#11758)
|
||||
* CLIENT NO-TOUCH for clients to run commands without affecting LRU/LFU of keys (#11483)
|
||||
* Introduce Shard IDs to logically group nodes in cluster mode based on
|
||||
replication. Shard IDs are automatically assigned and visible via
|
||||
`CLUSTER MYSHARDID`. (#10536)
|
||||
|
||||
Command replies that have been extended
|
||||
---------------------------------------
|
||||
|
||||
* ACL LOG - Add entry id, timestamp created, and timestamp last updated time (#11477)
|
||||
* COMMAND DOCS - Repurpose arg names as the unique ID (#11051)
|
||||
* CLIENT LIST has `T` flag to indicate CLIENT NO-TOUCH (#11483)
|
||||
* CLIENT LIST show lib-name, lib-ver (#11758)
|
||||
|
||||
Potentially Breaking / Behavior Changes
|
||||
=======================================
|
||||
|
||||
* Client side tracking for scripts now tracks the keys that are read by the
|
||||
script instead of the keys that are declared by the caller of EVAL / FCALL (#11770)
|
||||
* Freeze time sampling during command execution and in scripts (#10300)
|
||||
* When a blocked command is being unblocked, checks like ACL, OOM, etc are
|
||||
re-evaluated (#11012)
|
||||
* Unify ACL failure error message text and error codes (#11160)
|
||||
* Blocked stream command that's released when key no longer exists carries a
|
||||
different error code (#11012)
|
||||
* Command stats are updated for blocked commands only when / if the command
|
||||
actually executes (#11012)
|
||||
* The way ACL users are stored internally no longer removes redundant command
|
||||
and category rules, which may alter the way those rules are displayed as part
|
||||
of `ACL SAVE`, `ACL GETUSER` and `ACL LIST` (#11224)
|
||||
* Client connections created for TLS-based replication use SNI if possible (#11458)
|
||||
* Stream consumers: Re-purpose seen-time, add active-time (#11099)
|
||||
* XREADGROUP and X[AUTO]CLAIM create the consumer regardless of whether it was
|
||||
able to perform some reading/claiming (#11099)
|
||||
* ACL default newly created user set sanitize-payload flag in ACL LIST/GETUSER #11279
|
||||
* Fix HELLO command not to affect the client state unless successful (#11659)
|
||||
* Normalize `NAN` in replies to a single nan type, like we do with `inf` (#11597)
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
* Mark the QUIT command as deprecated (#11439)
|
||||
* Delete RDB loading code for pre-release RDB formats (#11058)
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
|
||||
* Significant memory optimization of small list type keys (#11303)
|
||||
* Significant memory optimization for small set type keys (#11290)
|
||||
* Significant memory optimization for large sets (#11595)
|
||||
* Significant speed optimization in ZRANGE replies WITHSCORES in case of integer scores (#11779)
|
||||
* Significant speed optimization in double replies, mainly sorted sets commands (#10587)
|
||||
* Optimize the performance of commands with multiple keys in cluster mode (#11044)
|
||||
* Incrementally reclaim OS page cache of RDB file (#11248)
|
||||
* Improve memory management of cluster bus links when there is a large number of pending messages (#11343)
|
||||
* Minor performance improvement for workloads that use commands without pipelining (#11220)
|
||||
|
||||
Changes in CLI tools
|
||||
====================
|
||||
|
||||
* redis-cli accepts commands in subscribed mode (#11873)
|
||||
|
||||
Other General Improvements
|
||||
==========================
|
||||
|
||||
* WAIT now no longer waits for the replication offset after your last command,
|
||||
but rather the replication offset after your last write (#11713)
|
||||
* Automatically propagate node deletion to other nodes in a cluster when
|
||||
`CLUSTER FORGET` is called, allowing nodes to be deleted with a single call
|
||||
in most cases (#10869)
|
||||
* Blocking commands that were disallowed in scripts now behave in scripts the
|
||||
same they did in MULTI (#11568)
|
||||
|
||||
Platform / toolchain support related changes
|
||||
=================================================
|
||||
|
||||
* 32-bit builds compiled without HAVE_MALLOC_SIZE (not jemalloc or glibc)
|
||||
will consume more memory (#11595)
|
||||
* Use jemalloc by default also on ARM (#11407)
|
||||
* Adds stack trace and register dump support in crash report for illumos/solaris (#11335)
|
||||
|
||||
|
||||
New configuration options
|
||||
=========================
|
||||
|
||||
* locale-collate runtime config to control setlocale affecting Lua and SORT (#11059)
|
||||
* Add CONFIG SET and GET loglevel feature in Sentinel (#11214)
|
||||
|
||||
INFO fields and introspection changes
|
||||
=====================================
|
||||
|
||||
* Added 4 new info fields for authentication errors and commands denied access
|
||||
for keys, channels and commands (#11288)
|
||||
* INFO SERVER includes a list of listeners (#9320)
|
||||
|
||||
|
||||
Module API changes
|
||||
==================
|
||||
|
||||
* Make it possible for module commands to be part of ACL categories (#11708)
|
||||
* Add K flag to RM_Call to allow running blocking commands and set a callback to get the response (#11568)
|
||||
* Add RM_AddPostNotificationJob to allow writes after keyspace notification hooks (#11199)
|
||||
* RedisModule_Event_Key to notify about keys being unlinked together with reason and value (#9406)
|
||||
* Add RM_BlockClient[Set|Get]PrivateData to associate a module data with the blocked client (#11568)
|
||||
* APIs to allow modules to participate / handle AUTH validation (#11659)
|
||||
* RM_GetContextFlags supports a new flag: REDISMODULE_CTX_FLAGS_SERVER_STARTUP (#9320)
|
||||
* Add REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS and RedisModule_GetModuleOptionsAll (#11199)
|
||||
* RM_BlockClientOnKeysWithFlags allows module to request being unblocked when the key is deleted (#11310)
|
||||
* Introduce aux_save2 makes it possible to skip saving that field in the RDB and
|
||||
enable loading the file in the absence of the module (#11374)
|
||||
* Add a dry run flag to RM_Call to do validations before actual execution (#11158)
|
||||
* Add RM_Microseconds and RM_CachedMicroseconds (#11016)
|
||||
* Add RM_ACLAddLogEntryByUserName API to be used without a user object (#11659)
|
||||
* Make it possible to keep the RM_Call reply for longer than the context lifetime in case
|
||||
auto memory was not used (#11568)
|
||||
|
||||
Potentially Breaking Changes in Module API
|
||||
------------------------------------------
|
||||
|
||||
* RM_Call only enforces OOM on scripts if 'M' flag is set (#11425)
|
||||
* Block some specific characters in module command names (#11434)
|
||||
* Fix replication inconsistency on modules that uses keyspace notifications (#10969)
|
||||
* Prevent command, configs, data types registration after the onload handler (#11708)
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Introduce socket shutdown to properly disconnect a client while a fork is active (#11376)
|
||||
* CLIENT RESET clears the CLIENT NO-EVICT flag (#11483)
|
||||
* Reduce memory usage on strings loaded by a module from an RDB file (#11050)
|
||||
* Fix a bug where nodes in a cluster may not replicate or handle internal events for
|
||||
keys deleted when another node in the cluster claimed a slot (#11084)
|
||||
* Fix HINCRBYFLOAT not to create a key if the new value is invalid (#11149)
|
||||
* Make cluster config file saving atomic and fsync acl file saving (#10924)
|
||||
* WAIT command would not block if used in RM_Call (#11713)
|
||||
* Minor fixes to command metadata in COMMAND command (#11201, #10273)
|
||||
|
||||
|
||||
Thanks to all the users and developers who made this release possible.
|
||||
We'll follow up with more RC releases, until the code looks production ready
|
||||
and we don't get reports of serious issues for a while.
|
||||
|
||||
A special thank you for the amount of work put into this release by:
|
||||
|
||||
- Meir Shpilraien
|
||||
- Guy Benoish
|
||||
- Viktor Söderqvist
|
||||
- Zhu Binbin
|
||||
- Oran Agra
|
||||
- sundb
|
||||
- Ran Shidlansik
|
||||
- Zhenwei Pi
|
||||
- Jason Elbaum
|
||||
- Karthik Subbarao
|
||||
- Madelyn Olson
|
||||
- Huang Zhw
|
||||
- Ping Xie
|
||||
- Ozan Tezcan
|
||||
- Chen Tianjie
|
||||
- Deng Ju
|
||||
- Wen Hui
|
||||
- Brennan Cathcart
|
||||
- Itamar Haber
|
||||
- Shaya Potter
|
||||
- Roshan Khatri
|
||||
- Slava Koyfman
|
||||
- Zhu Tian
|
||||
- Moti Cohen
|
||||
- Arad Zilberstein
|
||||
- Basel Naamna
|
||||
- Mingyi Kang
|
||||
- Uri Yagelnik
|
||||
- Filipe Oliveira
|
||||
- Zhao Zhao
|
||||
- Valentino Geron
|
||||
- Yaacov Hazan
|
||||
- Adi Pinsky
|
||||
- David Carlier
|
||||
- Li Changjun
|
||||
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ Examples of unacceptable behavior include:
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
* Publishing others’ private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
@@ -89,7 +89,7 @@ Attribution
|
||||
This Code of Conduct is adapted from the Contributor Covenant,
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
Community Impact Guidelines were inspired by Mozilla's code of conduct
|
||||
Community Impact Guidelines were inspired by Mozilla’s code of conduct
|
||||
enforcement ladder.
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
|
||||
+18
-80
@@ -1,82 +1,20 @@
|
||||
By contributing code to the Redis project in any form you agree to the Redis Software Grant and
|
||||
Contributor License Agreement attached below. Only contributions made under the Redis Software Grant
|
||||
and Contributor License Agreement may be accepted by Redis, and any contribution is subject to the
|
||||
terms of the Redis dual-license under RSALv2/SSPLv1 as described in the LICENSE.txt file included in
|
||||
the Redis source distribution.
|
||||
|
||||
# REDIS SOFTWARE GRANT AND CONTRIBUTOR LICENSE AGREEMENT
|
||||
|
||||
To specify the intellectual property license granted in any Contribution, Redis Ltd., ("**Redis**")
|
||||
requires a Software Grant and Contributor License Agreement ("**Agreement**"). This Agreement is for
|
||||
your protection as a contributor as well as the protection of Redis and its users; it does not
|
||||
change your rights to use your own Contribution for any other purpose.
|
||||
|
||||
By making any Contribution, You accept and agree to the following terms and conditions for the
|
||||
Contribution. Except for the license granted in this Agreement to Redis and the recipients of the
|
||||
software distributed by Redis, You reserve all right, title, and interest in and to Your
|
||||
Contribution.
|
||||
|
||||
1. **Definitions**
|
||||
|
||||
1.1. "**You**" (or "**Your**") means the copyright owner or legal entity authorized by the
|
||||
copyright owner that is entering into this Agreement with Redis. For legal entities, the entity
|
||||
making a Contribution and all other entities that Control, are Controlled by, or are under
|
||||
common Control with that entity are considered to be a single contributor. 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.
|
||||
|
||||
1.2. "**Contribution**" means the code, documentation, or any original work of authorship,
|
||||
including any modifications or additions to an existing work described above.
|
||||
|
||||
2. "**Work**" means any software project stewarded by Redis.
|
||||
|
||||
3. **Grant of Copyright License**. Subject to the terms and conditions of this Agreement, You grant
|
||||
to Redis and to the recipients of the software distributed by Redis 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 Your
|
||||
Contribution and such derivative works.
|
||||
|
||||
4. **Grant of Patent License**. Subject to the terms and conditions of this Agreement, You grant to
|
||||
Redis and to the recipients of the software distributed by Redis 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 You that are necessarily
|
||||
infringed by Your Contribution alone or by a combination of Your Contribution with the Work to
|
||||
which such Contribution was submitted. If any entity institutes patent litigation against You or
|
||||
any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your
|
||||
Contribution, or the Work to which you have contributed, constitutes a direct or contributory
|
||||
patent infringement, then any patent licenses granted to the claimant entity under this Agreement
|
||||
for that Contribution or Work terminate as of the date such litigation is filed.
|
||||
|
||||
5. **Representations and Warranties**. You represent and warrant that: (i) You are legally entitled
|
||||
to grant the above licenses; and (ii) if You are an entity, each employee or agent designated by
|
||||
You is authorized to submit the Contribution on behalf of You; and (iii) your Contribution is
|
||||
Your original work, and that it will not infringe on any third party's intellectual property
|
||||
right(s).
|
||||
|
||||
6. **Disclaimer**. You are not expected to provide support for Your Contribution, except to the
|
||||
extent You desire to provide support. You may provide support for free, for a fee, or not at all.
|
||||
Unless required by applicable law or agreed to in writing, You provide Your Contribution 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.
|
||||
|
||||
7. **Enforceability**. Nothing in this Agreement will be construed as creating any joint venture,
|
||||
employment relationship, or partnership between You and Redis. If any provision of this Agreement
|
||||
is held to be unenforceable, the remaining provisions of this Agreement will not be affected.
|
||||
This represents the entire agreement between You and Redis relating to the Contribution.
|
||||
Note: by contributing code to the Redis project in any form, including sending
|
||||
a pull request via Github, a code fragment or patch via private email or
|
||||
public discussion groups, you agree to release your code under the terms
|
||||
of the BSD license that you can find in the COPYING file included in the Redis
|
||||
source distribution. You will include BSD license in the COPYING file within
|
||||
each source file that you contribute.
|
||||
|
||||
# IMPORTANT: HOW TO USE REDIS GITHUB ISSUES
|
||||
|
||||
GitHub issues SHOULD ONLY BE USED to report bugs and for DETAILED feature
|
||||
requests. Everything else should be asked on Discord:
|
||||
Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature
|
||||
requests. Everything else belongs to the Redis Google Group:
|
||||
|
||||
https://discord.com/invite/redis
|
||||
https://groups.google.com/forum/m/#!forum/Redis-db
|
||||
|
||||
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
|
||||
bugs in the GitHub issues system. We'll be delighted to help you and provide
|
||||
all the support on Discord.
|
||||
bugs in the Github issues system. We'll be very happy to help you and provide
|
||||
all the support in the mailing list.
|
||||
|
||||
There is also an active community of Redis users at Stack Overflow:
|
||||
|
||||
@@ -95,24 +33,24 @@ straight away: if your feature is not a conceptual fit you'll lose a lot of
|
||||
time writing the code without any reason. Start by posting in the mailing list
|
||||
and creating an issue at Github with the description of, exactly, what you want
|
||||
to accomplish and why. Use cases are important for features to be accepted.
|
||||
Here you can see if there is consensus about your idea.
|
||||
Here you'll see if there is consensus about your idea.
|
||||
|
||||
2. If in step 1 you get an acknowledgment from the project leaders, use the
|
||||
following procedure to submit a patch:
|
||||
|
||||
a. Fork Redis on GitHub ( https://docs.github.com/en/github/getting-started-with-github/fork-a-repo )
|
||||
a. Fork Redis on github ( https://docs.github.com/en/github/getting-started-with-github/fork-a-repo )
|
||||
b. Create a topic branch (git checkout -b my_branch)
|
||||
c. Push to your branch (git push origin my_branch)
|
||||
d. Initiate a pull request on GitHub ( https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request )
|
||||
d. Initiate a pull request on github ( https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request )
|
||||
e. Done :)
|
||||
|
||||
3. Keep in mind that we are very overloaded, so issues and PRs sometimes wait
|
||||
for a *very* long time. However this is not a lack of interest, as the project
|
||||
for a *very* long time. However this is not lack of interest, as the project
|
||||
gets more and more users, we find ourselves in a constant need to prioritize
|
||||
certain issues/PRs over others. If you think your issue/PR is very important
|
||||
try to popularize it, have other users commenting and sharing their point of
|
||||
view, and so forth. This helps.
|
||||
view and so forth. This helps.
|
||||
|
||||
4. For minor fixes - open a pull request on GitHub.
|
||||
4. For minor fixes just open a pull request on Github.
|
||||
|
||||
Additional information on the RSALv2/SSPLv1 dual-license is also found in the LICENSE.txt file.
|
||||
Thanks!
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Copyright (c) 2006-2020, Salvatore Sanfilippo
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-733
@@ -1,733 +0,0 @@
|
||||
Starting on March 20th, 2024, Redis follows a dual-licensing model with all Redis project code
|
||||
contributions under version 7.4 and subsequent releases governed by the Redis Software Grant and
|
||||
Contributor License Agreement. After this date, contributions are subject to the user's choice of
|
||||
the Redis Source Available License v2 (RSALv2) or the Server Side Public License v1 (SSPLv1), as
|
||||
follows:
|
||||
|
||||
|
||||
1. Redis Source Available License 2.0 (RSALv2) Agreement
|
||||
========================================================
|
||||
|
||||
Last Update: December 30, 2023
|
||||
|
||||
Acceptance
|
||||
----------
|
||||
|
||||
This Agreement sets forth the terms and conditions on which the Licensor
|
||||
makes available the Software. By installing, downloading, accessing,
|
||||
Using, or distributing any of the Software, You agree to all of the
|
||||
terms and conditions of this Agreement.
|
||||
|
||||
If You are receiving the Software on behalf of Your Company, You
|
||||
represent and warrant that You have the authority to agree to this
|
||||
Agreement on behalf of such entity.
|
||||
|
||||
The Licensor reserves the right to update this Agreement from time to
|
||||
time.
|
||||
|
||||
The terms below have the meanings set forth below for purposes of this
|
||||
Agreement:
|
||||
|
||||
Definitions
|
||||
-----------
|
||||
|
||||
Agreement: this Redis Source Available License 2.0 Agreement.
|
||||
|
||||
Control: ownership, directly or indirectly, of substantially all the
|
||||
assets of an entity, or the power to direct its management and policies
|
||||
by vote, contract, or otherwise.
|
||||
|
||||
License: the License as described in the License paragraph below.
|
||||
|
||||
Licensor: the entity offering these terms, which includes Redis Ltd. on
|
||||
behalf of itself and its subsidiaries and affiliates worldwide.
|
||||
|
||||
Modify, Modified, or Modification: copy from or adapt all or part of the
|
||||
work in a fashion requiring copyright permission other than making an
|
||||
exact copy. The resulting work is called a Modified version of the
|
||||
earlier work.
|
||||
|
||||
Redis: the Redis software as described in redis.com redis.io.
|
||||
|
||||
Software: certain Software components designed to work with Redis and
|
||||
provided to You under this Agreement.
|
||||
|
||||
Trademark: the trademarks, service marks, and any other similar rights.
|
||||
|
||||
Use: anything You do with the Software requiring one of Your Licenses.
|
||||
|
||||
You: the recipient of the Software, the individual or entity on whose
|
||||
behalf You are agreeing to this Agreement.
|
||||
|
||||
Your Company: any legal entity, sole proprietorship, or other kind of
|
||||
organization that You work for, plus all organizations that have control
|
||||
over, are under the control of, or are under common control with that
|
||||
organization.
|
||||
|
||||
Your Licenses: means all the Licenses granted to You for the Software
|
||||
under this Agreement.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The Licensor grants You a non-exclusive, royalty-free, worldwide,
|
||||
non-sublicensable, non-transferable license to use, copy, distribute,
|
||||
make available, and prepare derivative works of the Software, in each
|
||||
case subject to the limitations and conditions below.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
You may not make the functionality of the Software or a Modified version
|
||||
available to third parties as a service or distribute the Software or a
|
||||
Modified version in a manner that makes the functionality of the
|
||||
Software available to third parties.
|
||||
|
||||
Making the functionality of the Software or Modified version available
|
||||
to third parties includes, without limitation, enabling third parties to
|
||||
interact with the functionality of the Software or Modified version in
|
||||
distributed form or remotely through a computer network, offering a
|
||||
product or service, the value of which entirely or primarily derives
|
||||
from the value of the Software or Modified version, or offering a
|
||||
product or service that accomplishes for users the primary purpose of
|
||||
the Software or Modified version.
|
||||
|
||||
You may not alter, remove, or obscure any licensing, copyright, or other
|
||||
notices of the Licensor in the Software. Any use of the Licensor's
|
||||
Trademarks is subject to applicable law.
|
||||
|
||||
Patents
|
||||
-------
|
||||
|
||||
The Licensor grants You a License, under any patent claims the Licensor
|
||||
can License, or becomes able to License, to make, have made, use, sell,
|
||||
offer for sale, import and have imported the Software, in each case
|
||||
subject to the limitations and conditions in this License. This License
|
||||
does not cover any patent claims that You cause to be infringed by
|
||||
Modifications or additions to the Software. If You or Your Company make
|
||||
any written claim that the Software infringes or contributes to
|
||||
infringement of any patent, your patent License for the Software granted
|
||||
under this Agreement ends immediately. If Your Company makes such a
|
||||
claim, your patent License ends immediately for work on behalf of Your
|
||||
Company.
|
||||
|
||||
Notices
|
||||
-------
|
||||
|
||||
You must ensure that anyone who gets a copy of any part of the Software
|
||||
from You also gets a copy of the terms and conditions in this Agreement.
|
||||
|
||||
If You modify the Software, You must include in any Modified copies of
|
||||
the Software prominent notices stating that You have Modified the
|
||||
Software.
|
||||
|
||||
No Other Rights
|
||||
---------------
|
||||
|
||||
The terms and conditions of this Agreement do not imply any Licenses
|
||||
other than those expressly granted in this Agreement.
|
||||
|
||||
Termination
|
||||
-----------
|
||||
|
||||
If You Use the Software in violation of this Agreement, such Use is not
|
||||
Licensed, and Your Licenses will automatically terminate. If the
|
||||
Licensor provides You with a notice of your violation, and You cease all
|
||||
violations of this License no later than 30 days after You receive that
|
||||
notice, Your Licenses will be reinstated retroactively. However, if You
|
||||
violate this Agreement after such reinstatement, any additional
|
||||
violation of this Agreement will cause your Licenses to terminate
|
||||
automatically and permanently.
|
||||
|
||||
No Liability
|
||||
------------
|
||||
|
||||
As far as the law allows, the Software comes as is, without any
|
||||
warranty or condition, and the Licensor will not be liable to You for
|
||||
any damages arising out of this Agreement or the Use or nature of the
|
||||
Software, under any kind of legal claim.
|
||||
|
||||
Governing Law and Jurisdiction
|
||||
------------------------------
|
||||
|
||||
If You are located in Asia, Pacific, Americas, or other jurisdictions
|
||||
not listed below, the Agreement will be construed and enforced in all
|
||||
respects in accordance with the laws of the State of California, U.S.A.,
|
||||
without reference to its choice of law rules. The courts located in the
|
||||
County of Santa Clara, California, have exclusive jurisdiction for all
|
||||
purposes relating to this Agreement.
|
||||
|
||||
If You are located in Israel, the Agreement will be construed and
|
||||
enforced in all respects in accordance with the laws of the State of
|
||||
Israel without reference to its choice of law rules. The courts located
|
||||
in the Central District of the State of Israel have exclusive
|
||||
jurisdiction for all purposes relating to this Agreement.
|
||||
|
||||
If You are located in Europe, United Kingdom, Middle East or Africa, the
|
||||
Agreement will be construed and enforced in all respects in accordance
|
||||
with the laws of England and Wales without reference to its choice of
|
||||
law rules. The competent courts located in London, England, have
|
||||
exclusive jurisdiction for all purposes relating to this Agreement.
|
||||
|
||||
|
||||
|
||||
2. Server Side Public License (SSPL)
|
||||
====================================
|
||||
|
||||
Server Side Public License
|
||||
VERSION 1, OCTOBER 16, 2018
|
||||
|
||||
Copyright (c) 2018 MongoDB, Inc.
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to Server Side Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work in
|
||||
a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based on
|
||||
the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through a
|
||||
computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to the
|
||||
extent that it includes a convenient and prominently visible feature that
|
||||
(1) displays an appropriate copyright notice, and (2) tells the user that
|
||||
there is no warranty for the work (except to the extent that warranties
|
||||
are provided), that licensees may convey the work under this License, and
|
||||
how to view a copy of this License. If the interface presents a list of
|
||||
user commands or options, such as a menu, a prominent item in the list
|
||||
meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of a
|
||||
work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that is
|
||||
widely used among developers working in that language. The "System
|
||||
Libraries" of an executable work include anything, other than the work as
|
||||
a whole, that (a) is included in the normal form of packaging a Major
|
||||
Component, but which is not part of that Major Component, and (b) serves
|
||||
only to enable use of the work with that Major Component, or to implement
|
||||
a Standard Interface for which an implementation is available to the
|
||||
public in source code form. A "Major Component", in this context, means a
|
||||
major essential component (kernel, window system, and so on) of the
|
||||
specific operating system (if any) on which the executable work runs, or
|
||||
a compiler used to produce the work, or an object code interpreter used
|
||||
to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all the
|
||||
source code needed to generate, install, and (for an executable work) run
|
||||
the object code and to modify the work, including scripts to control
|
||||
those activities. However, it does not include the work's System
|
||||
Libraries, or general-purpose tools or generally available free programs
|
||||
which are used unmodified in performing those activities but which are
|
||||
not part of the work. For example, Corresponding Source includes
|
||||
interface definition files associated with source files for the work, and
|
||||
the source code for shared libraries and dynamically linked subprograms
|
||||
that the work is specifically designed to require, such as by intimate
|
||||
data communication or control flow between those subprograms and other
|
||||
parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program, subject to section 13. The
|
||||
output from running a covered work is covered by this License only if the
|
||||
output, given its content, constitutes a covered work. This License
|
||||
acknowledges your rights of fair use or other equivalent, as provided by
|
||||
copyright law. Subject to section 13, you may make, run and propagate
|
||||
covered works that you do not convey, without conditions so long as your
|
||||
license otherwise remains in force. You may convey covered works to
|
||||
others for the sole purpose of having them make modifications exclusively
|
||||
for you, or provide you with facilities for running those works, provided
|
||||
that you comply with the terms of this License in conveying all
|
||||
material for which you do not control copyright. Those thus making or
|
||||
running the covered works for you must do so exclusively on your
|
||||
behalf, under your direction and control, on terms that prohibit them
|
||||
from making any copies of your copyrighted material outside their
|
||||
relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes it
|
||||
unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article 11
|
||||
of the WIPO copyright treaty adopted on 20 December 1996, or similar laws
|
||||
prohibiting or restricting circumvention of such measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention is
|
||||
effected by exercising rights under this License with respect to the
|
||||
covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's users,
|
||||
your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice; keep
|
||||
intact all notices stating that this License and any non-permissive terms
|
||||
added in accord with section 7 apply to the code; keep intact all notices
|
||||
of the absence of any warranty; and give all recipients a copy of this
|
||||
License along with the Program. You may charge any price or no price for
|
||||
each copy that you convey, and you may offer support or warranty
|
||||
protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the terms
|
||||
of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified it,
|
||||
and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is released
|
||||
under this License and any conditions added under section 7. This
|
||||
requirement modifies the requirement in section 4 to "keep intact all
|
||||
notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this License to
|
||||
anyone who comes into possession of a copy. This License will therefore
|
||||
apply, along with any applicable section 7 additional terms, to the
|
||||
whole of the work, and all its parts, regardless of how they are
|
||||
packaged. This License gives no permission to license the work in any
|
||||
other way, but it does not invalidate such permission if you have
|
||||
separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your work
|
||||
need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work, and
|
||||
which are not combined with it such as to form a larger program, in or on
|
||||
a volume of a storage or distribution medium, is called an "aggregate" if
|
||||
the compilation and its resulting copyright are not used to limit the
|
||||
access or legal rights of the compilation's users beyond what the
|
||||
individual works permit. Inclusion of a covered work in an aggregate does
|
||||
not cause this License to apply to the other parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
Corresponding Source under the terms of this License, in one of these
|
||||
ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium customarily
|
||||
used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a written
|
||||
offer, valid for at least three years and valid for as long as you
|
||||
offer spare parts or customer support for that product model, to give
|
||||
anyone who possesses the object code either (1) a copy of the
|
||||
Corresponding Source for all the software in the product that is
|
||||
covered by this License, on a durable physical medium customarily used
|
||||
for software interchange, for a price no more than your reasonable cost
|
||||
of physically performing this conveying of source, or (2) access to
|
||||
copy the Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This alternative is
|
||||
allowed only occasionally and noncommercially, and only if you received
|
||||
the object code with such an offer, in accord with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated place
|
||||
(gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to copy
|
||||
the object code is a network server, the Corresponding Source may be on
|
||||
a different server (operated by you or a third party) that supports
|
||||
equivalent copying facilities, provided you maintain clear directions
|
||||
next to the object code saying where to find the Corresponding Source.
|
||||
Regardless of what server hosts the Corresponding Source, you remain
|
||||
obligated to ensure that it is available for as long as needed to
|
||||
satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided you
|
||||
inform other peers where the object code and Corresponding Source of
|
||||
the work are being offered to the general public at no charge under
|
||||
subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be included
|
||||
in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as part
|
||||
of a transaction in which the right of possession and use of the User
|
||||
Product is transferred to the recipient in perpetuity or for a fixed term
|
||||
(regardless of how the transaction is characterized), the Corresponding
|
||||
Source conveyed under this section must be accompanied by the
|
||||
Installation Information. But this requirement does not apply if neither
|
||||
you nor any third party retains the ability to install modified object
|
||||
code on the User Product (for example, the work has been installed in
|
||||
ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access
|
||||
to a network may be denied when the modification itself materially
|
||||
and adversely affects the operation of the network or violates the
|
||||
rules and protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, in
|
||||
accord with this section must be in a format that is publicly documented
|
||||
(and with an implementation available to the public in source code form),
|
||||
and must require no special password or key for unpacking, reading or
|
||||
copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall be
|
||||
treated as though they were included in this License, to the extent that
|
||||
they are valid under applicable law. If additional permissions apply only
|
||||
to part of the Program, that part may be used separately under those
|
||||
permissions, but the entire Program remains governed by this License
|
||||
without regard to the additional permissions. When you convey a copy of
|
||||
a covered work, you may at your option remove any additional permissions
|
||||
from that copy, or from any part of it. (Additional permissions may be
|
||||
written to require their own removal in certain cases when you modify the
|
||||
work.) You may place additional permissions on material, added by you to
|
||||
a covered work, for which you have or can give appropriate copyright
|
||||
permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you add
|
||||
to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some trade
|
||||
names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that material
|
||||
by anyone who conveys the material (or modified versions of it) with
|
||||
contractual assumptions of liability to the recipient, for any
|
||||
liability that these contractual assumptions directly impose on those
|
||||
licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further restriction,
|
||||
you may remove that term. If a license document contains a further
|
||||
restriction but permits relicensing or conveying under this License, you
|
||||
may add to a covered work material governed by the terms of that license
|
||||
document, provided that the further restriction does not survive such
|
||||
relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you must
|
||||
place, in the relevant source files, a statement of the additional terms
|
||||
that apply to those files, or a notice indicating where to find the
|
||||
applicable terms. Additional terms, permissive or non-permissive, may be
|
||||
stated in the form of a separately written license, or stated as
|
||||
exceptions; the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or modify
|
||||
it is void, and will automatically terminate your rights under this
|
||||
License (including any patent licenses granted under the third paragraph
|
||||
of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally terminates
|
||||
your license, and (b) permanently, if the copyright holder fails to
|
||||
notify you of the violation by some reasonable means prior to 60 days
|
||||
after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is reinstated
|
||||
permanently if the copyright holder notifies you of the violation by some
|
||||
reasonable means, this is the first time you have received notice of
|
||||
violation of this License (for any work) from that copyright holder, and
|
||||
you cure the violation prior to 30 days after your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run a
|
||||
copy of the Program. Ancillary propagation of a covered work occurring
|
||||
solely as a consequence of using peer-to-peer transmission to receive a
|
||||
copy likewise does not require acceptance. However, nothing other than
|
||||
this License grants you permission to propagate or modify any covered
|
||||
work. These actions infringe copyright if you do not accept this License.
|
||||
Therefore, by modifying or propagating a covered work, you indicate your
|
||||
acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically receives
|
||||
a license from the original licensors, to run, modify and propagate that
|
||||
work, subject to this License. You are not responsible for enforcing
|
||||
compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered work
|
||||
results from an entity transaction, each party to that transaction who
|
||||
receives a copy of the work also receives whatever licenses to the work
|
||||
the party's predecessor in interest had or could give under the previous
|
||||
paragraph, plus a right to possession of the Corresponding Source of the
|
||||
work from the predecessor in interest, if the predecessor has it or can
|
||||
get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the rights
|
||||
granted or affirmed under this License. For example, you may not impose a
|
||||
license fee, royalty, or other charge for exercise of rights granted
|
||||
under this License, and you may not initiate litigation (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that any patent claim
|
||||
is infringed by making, using, selling, offering for sale, or importing
|
||||
the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The work
|
||||
thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned or
|
||||
controlled by the contributor, whether already acquired or hereafter
|
||||
acquired, that would be infringed by some manner, permitted by this
|
||||
License, of making, using, or selling its contributor version, but do not
|
||||
include claims that would be infringed only as a consequence of further
|
||||
modification of the contributor version. For purposes of this definition,
|
||||
"control" includes the right to grant patent sublicenses in a manner
|
||||
consistent with the requirements of this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to make,
|
||||
use, sell, offer for sale, import and otherwise run, modify and propagate
|
||||
the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a party
|
||||
means to make such an agreement or commitment not to enforce a patent
|
||||
against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, and
|
||||
the Corresponding Source of the work is not available for anyone to copy,
|
||||
free of charge and under the terms of this License, through a publicly
|
||||
available network server or other readily accessible means, then you must
|
||||
either (1) cause the Corresponding Source to be so available, or (2)
|
||||
arrange to deprive yourself of the benefit of the patent license for this
|
||||
particular work, or (3) arrange, in a manner consistent with the
|
||||
requirements of this License, to extend the patent license to downstream
|
||||
recipients. "Knowingly relying" means you have actual knowledge that, but
|
||||
for the patent license, your conveying the covered work in a country, or
|
||||
your recipient's use of the covered work in a country, would infringe
|
||||
one or more identifiable patents in that country that you have reason
|
||||
to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties receiving
|
||||
the covered work authorizing them to use, propagate, modify or convey a
|
||||
specific copy of the covered work, then the patent license you grant is
|
||||
automatically extended to all recipients of the covered work and works
|
||||
based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on
|
||||
the non-exercise of one or more of the rights that are specifically
|
||||
granted under this License. You may not convey a covered work if you are
|
||||
a party to an arrangement with a third party that is in the business of
|
||||
distributing software, under which you make payment to the third party
|
||||
based on the extent of your activity of conveying the work, and under
|
||||
which the third party grants, to any of the parties who would receive the
|
||||
covered work from you, a discriminatory patent license (a) in connection
|
||||
with copies of the covered work conveyed by you (or copies made from
|
||||
those copies), or (b) primarily for and in connection with specific
|
||||
products or compilations that contain the covered work, unless you
|
||||
entered into that arrangement, or that patent license was granted, prior
|
||||
to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting any
|
||||
implied license or other defenses to infringement that may otherwise be
|
||||
available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot use,
|
||||
propagate or convey a covered work so as to satisfy simultaneously your
|
||||
obligations under this License and any other pertinent obligations, then
|
||||
as a consequence you may not use, propagate or convey it at all. For
|
||||
example, if you agree to terms that obligate you to collect a royalty for
|
||||
further conveying from those to whom you convey the Program, the only way
|
||||
you could satisfy both those terms and this License would be to refrain
|
||||
entirely from conveying the Program.
|
||||
|
||||
13. Offering the Program as a Service.
|
||||
|
||||
If you make the functionality of the Program or a modified version
|
||||
available to third parties as a service, you must make the Service Source
|
||||
Code available via network download to everyone at no charge, under the
|
||||
terms of this License. Making the functionality of the Program or
|
||||
modified version available to third parties as a service includes,
|
||||
without limitation, enabling third parties to interact with the
|
||||
functionality of the Program or modified version remotely through a
|
||||
computer network, offering a service the value of which entirely or
|
||||
primarily derives from the value of the Program or modified version, or
|
||||
offering a service that accomplishes for users the primary purpose of the
|
||||
Program or modified version.
|
||||
|
||||
"Service Source Code" means the Corresponding Source for the Program or
|
||||
the modified version, and the Corresponding Source for all programs that
|
||||
you use to make the Program or modified version available as a service,
|
||||
including, without limitation, management software, user interfaces,
|
||||
application program interfaces, automation software, monitoring software,
|
||||
backup software, storage software and hosting software, all such that a
|
||||
user could run an instance of the service using the Service Source Code
|
||||
you make available.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
MongoDB, Inc. may publish revised and/or new versions of the Server Side
|
||||
Public License from time to time. Such new versions will be similar in
|
||||
spirit to the present version, but may differ in detail to address new
|
||||
problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies that a certain numbered version of the Server Side Public
|
||||
License "or any later version" applies to it, you have the option of
|
||||
following the terms and conditions either of that numbered version or of
|
||||
any later version published by MongoDB, Inc. If the Program does not
|
||||
specify a version number of the Server Side Public License, you may
|
||||
choose any version ever published by MongoDB, Inc.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions of
|
||||
the Server Side Public License can be used, that proxy's public statement
|
||||
of acceptance of a version permanently authorizes you to choose that
|
||||
version for the Program.
|
||||
|
||||
Later license versions may give you additional or different permissions.
|
||||
However, no additional obligations are imposed on any author or copyright
|
||||
holder as a result of your choosing to follow a later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING
|
||||
ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF
|
||||
THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO
|
||||
LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
|
||||
OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided above
|
||||
cannot be given local legal effect according to their terms, reviewing
|
||||
courts shall apply local law that most closely approximates an absolute
|
||||
waiver of all civil liability in connection with the Program, unless a
|
||||
warranty or assumption of liability accompanies a copy of the Program in
|
||||
return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,7 +1,7 @@
|
||||
This README is just a fast *quick start* document. You can find more detailed documentation at [redis.io](https://redis.io).
|
||||
|
||||
What is Redis?
|
||||
---
|
||||
--------------
|
||||
|
||||
Redis is often referred to as a *data structures* server. What this means is that Redis provides access to mutable data structures via a set of commands, which are sent using a *server-client* model with TCP sockets and a simple protocol. So different processes can query and modify the same data structures in a shared way.
|
||||
|
||||
@@ -20,18 +20,8 @@ If you want to know more, this is a list of selected starting points:
|
||||
* The full list of Redis commands. https://redis.io/commands
|
||||
* There is much more inside the official Redis documentation. https://redis.io/documentation
|
||||
|
||||
What is Redis Community Edition?
|
||||
---
|
||||
|
||||
Redis OSS was renamed Redis Community Edition (CE) with the v7.4 release.
|
||||
|
||||
Redis Ltd. also offers [Redis Software](https://redis.io/enterprise/), a self-managed software with additional compliance, reliability, and resiliency for enterprise scaling,
|
||||
and [Redis Cloud](https://redis.io/cloud/), a fully managed service integrated with Google Cloud, Azure, and AWS for production-ready apps.
|
||||
|
||||
Read more about the differences between Redis Community Edition and Redis [here](https://redis.io/comparisons/oss-vs-enterprise/).
|
||||
|
||||
Building Redis
|
||||
---
|
||||
--------------
|
||||
|
||||
Redis can be compiled and used on Linux, OSX, OpenBSD, NetBSD, FreeBSD.
|
||||
We support big endian and little endian architectures, and both 32 bit
|
||||
@@ -75,7 +65,7 @@ installed):
|
||||
|
||||
|
||||
Fixing build problems with dependencies or cached build options
|
||||
---
|
||||
---------
|
||||
|
||||
Redis has some dependencies which are included in the `deps` directory.
|
||||
`make` does not automatically rebuild dependencies even if something in
|
||||
@@ -95,7 +85,7 @@ those options are cached indefinitely until you issue a `make distclean`
|
||||
command.
|
||||
|
||||
Fixing problems building 32 bit binaries
|
||||
---
|
||||
---------
|
||||
|
||||
If after building Redis with a 32 bit target you need to rebuild it
|
||||
with a 64 bit target, or the other way around, you need to perform a
|
||||
@@ -109,7 +99,7 @@ the following steps:
|
||||
`make CFLAGS="-m32 -march=native" LDFLAGS="-m32"`
|
||||
|
||||
Allocator
|
||||
---
|
||||
---------
|
||||
|
||||
Selecting a non-default memory allocator when building Redis is done by setting
|
||||
the `MALLOC` environment variable. Redis is compiled and linked against libc
|
||||
@@ -126,7 +116,7 @@ To compile against jemalloc on Mac OS X systems, use:
|
||||
% make MALLOC=jemalloc
|
||||
|
||||
Monotonic clock
|
||||
---
|
||||
---------------
|
||||
|
||||
By default, Redis will build using the POSIX clock_gettime function as the
|
||||
monotonic clock source. On most modern systems, the internal processor clock
|
||||
@@ -138,7 +128,7 @@ To build with support for the processor's internal instruction clock, use:
|
||||
% make CFLAGS="-DUSE_PROCESSOR_CLOCK"
|
||||
|
||||
Verbose build
|
||||
---
|
||||
-------------
|
||||
|
||||
Redis will build with a user-friendly colorized output by default.
|
||||
If you want to see a more verbose output, use the following:
|
||||
@@ -146,7 +136,7 @@ If you want to see a more verbose output, use the following:
|
||||
% make V=1
|
||||
|
||||
Running Redis
|
||||
---
|
||||
-------------
|
||||
|
||||
To run Redis with the default configuration, just type:
|
||||
|
||||
@@ -168,14 +158,14 @@ as options using the command line. Examples:
|
||||
All the options in redis.conf are also supported as options using the command
|
||||
line, with exactly the same name.
|
||||
|
||||
Running Redis with TLS
|
||||
---
|
||||
Running Redis with TLS:
|
||||
------------------
|
||||
|
||||
Please consult the [TLS.md](TLS.md) file for more information on
|
||||
how to use Redis with TLS.
|
||||
|
||||
Playing with Redis
|
||||
---
|
||||
------------------
|
||||
|
||||
You can use redis-cli to play with Redis. Start a redis-server instance,
|
||||
then in another terminal try the following:
|
||||
@@ -197,7 +187,7 @@ then in another terminal try the following:
|
||||
You can find the list of all the available commands at https://redis.io/commands.
|
||||
|
||||
Installing Redis
|
||||
---
|
||||
-----------------
|
||||
|
||||
In order to install Redis binaries into /usr/local/bin, just use:
|
||||
|
||||
@@ -225,35 +215,25 @@ You'll be able to stop and start Redis using the script named
|
||||
`/etc/init.d/redis_<portnumber>`, for instance `/etc/init.d/redis_6379`.
|
||||
|
||||
Code contributions
|
||||
---
|
||||
-----------------
|
||||
|
||||
By contributing code to the Redis project in any form, including sending a pull request via GitHub,
|
||||
a code fragment or patch via private email or public discussion groups, you agree to release your
|
||||
code under the terms of the [Redis Software Grant and Contributor License Agreement][1]. Redis software
|
||||
contains contributions to the original Redis core project, which are owned by their contributors and
|
||||
licensed under the 3BSD license. Any copy of that license in this repository applies only to those
|
||||
contributions. Redis releases all Redis Community Edition versions from 7.4.x and thereafter under the
|
||||
RSALv2/SSPL dual-license as described in the [LICENSE.txt][2] file included in the Redis Community Edition source distribution.
|
||||
Note: By contributing code to the Redis project in any form, including sending
|
||||
a pull request via Github, a code fragment or patch via private email or
|
||||
public discussion groups, you agree to release your code under the terms
|
||||
of the BSD license that you can find in the [COPYING][1] file included in the Redis
|
||||
source distribution.
|
||||
|
||||
Please see the [CONTRIBUTING.md][1] file in this source distribution for more information. For
|
||||
security bugs and vulnerabilities, please see [SECURITY.md][3].
|
||||
Please see the [CONTRIBUTING.md][2] file in this source distribution for more
|
||||
information. For security bugs and vulnerabilities, please see [SECURITY.md][3].
|
||||
|
||||
[1]: https://github.com/redis/redis/blob/unstable/CONTRIBUTING.md
|
||||
[2]: https://github.com/redis/redis/blob/unstable/LICENSE.txt
|
||||
[1]: https://github.com/redis/redis/blob/unstable/COPYING
|
||||
[2]: https://github.com/redis/redis/blob/unstable/CONTRIBUTING.md
|
||||
[3]: https://github.com/redis/redis/blob/unstable/SECURITY.md
|
||||
|
||||
Redis Trademarks
|
||||
---
|
||||
|
||||
The purpose of a trademark is to identify the goods and services of a person or company without
|
||||
causing confusion. As the registered owner of its name and logo, Redis accepts certain limited uses
|
||||
of its trademarks but it has requirements that must be followed as described in its Trademark
|
||||
Guidelines available at: https://redis.com/legal/trademark-guidelines/.
|
||||
|
||||
Redis internals
|
||||
===
|
||||
|
||||
If you are reading this README you are likely in front of a GitHub page
|
||||
If you are reading this README you are likely in front of a Github page
|
||||
or you just untarred the Redis distribution tar ball. In both the cases
|
||||
you are basically one step away from the source code, so here we explain
|
||||
the Redis source code layout, what is in each file as a general idea, the
|
||||
@@ -269,7 +249,7 @@ Source code layout
|
||||
|
||||
The Redis root directory just contains this README, the Makefile which
|
||||
calls the real Makefile inside the `src` directory and an example
|
||||
configuration for Redis and Redis Sentinel. You can find a few shell
|
||||
configuration for Redis and Sentinel. You can find a few shell
|
||||
scripts that are used in order to execute the Redis, Redis Cluster and
|
||||
Redis Sentinel unit tests, which are implemented inside the `tests`
|
||||
directory.
|
||||
@@ -440,7 +420,7 @@ implementations are the following:
|
||||
* `lookupKeyRead()` and `lookupKeyWrite()` are used in order to get a pointer to the value associated to a given key, or `NULL` if the key does not exist.
|
||||
* `dbAdd()` and its higher level counterpart `setKey()` create a new key in a Redis database.
|
||||
* `dbDelete()` removes a key and its associated value.
|
||||
* `emptyData()` removes an entire single database or all the databases defined.
|
||||
* `emptyDb()` removes an entire single database or all the databases defined.
|
||||
|
||||
The rest of the file implements the generic commands exposed to the client.
|
||||
|
||||
@@ -478,9 +458,9 @@ Script
|
||||
|
||||
The script unit is composed of 3 units:
|
||||
* `script.c` - integration of scripts with Redis (commands execution, set replication/resp, ...)
|
||||
* `script_lua.c` - responsible to execute Lua code, uses `script.c` to interact with Redis from within the Lua code.
|
||||
* `function_lua.c` - contains the Lua engine implementation, uses `script_lua.c` to execute the Lua code.
|
||||
* `functions.c` - contains Redis Functions implementation (`FUNCTION` command), uses `functions_lua.c` if the function it wants to invoke needs the Lua engine.
|
||||
* `script_lua.c` - responsible to execute Lua code, uses script.c to interact with Redis from within the Lua code.
|
||||
* `function_lua.c` - contains the Lua engine implementation, uses script_lua.c to execute the Lua code.
|
||||
* `functions.c` - contains Redis Functions implementation (FUNCTION command), uses functions_lua.c if the function it wants to invoke needs the Lua engine.
|
||||
* `eval.c` - contains the `eval` implementation using `script_lua.c` to invoke the Lua code.
|
||||
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
Copyright (c) 2006-Present, Redis Ltd. and Contributors
|
||||
All rights reserved.
|
||||
|
||||
Note: Continued Applicability of the BSD-3-Clause License
|
||||
|
||||
Despite the shift to the dual-licensing model with Redis Community Edition version 7.4 (RSALv2 or SSPLv1), portions of
|
||||
Redis Community Edition remain available subject to the BSD-3-Clause License (BSD). See below for the full BSD
|
||||
license:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||
and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
||||
and the following disclaimer in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse
|
||||
or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+11
-7
@@ -2,7 +2,7 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Redis is generally backward compatible with very few exceptions, so we
|
||||
Redis is generally backwards compatible with very few exceptions, so we
|
||||
recommend users to always use the latest version to experience stability,
|
||||
performance and security.
|
||||
|
||||
@@ -11,18 +11,17 @@ unless this is not possible or feasible with a reasonable effort.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 7.4.x | :white_check_mark: |
|
||||
| 7.2.x | :white_check_mark: |
|
||||
| < 7.2.x | :x: |
|
||||
| 7.0.x | :white_check_mark: |
|
||||
| 6.2.x | :white_check_mark: |
|
||||
| < 6.2.x | :x: |
|
||||
| 6.0.x | :white_check_mark: |
|
||||
| < 6.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you've discovered a serious vulnerability, please contact the
|
||||
If you believe you’ve discovered a serious vulnerability, please contact the
|
||||
Redis core team at redis@redis.io. We will evaluate your report and if
|
||||
necessary issue a fix and an advisory. If the issue was previously undisclosed,
|
||||
we'll also mention your name in the credits.
|
||||
we’ll also mention your name in the credits.
|
||||
|
||||
## Responsible Disclosure
|
||||
|
||||
@@ -35,5 +34,10 @@ This process involves providing an early notification about the vulnerability,
|
||||
its impact and mitigations to a short list of vendors under a time-limited
|
||||
embargo on public disclosure.
|
||||
|
||||
Vendors on the list are individuals or organizations that maintain Redis
|
||||
distributions or provide Redis as a service, who have third party users who
|
||||
will benefit from the vendor’s ability to prepare for a new version or deploy a
|
||||
fix early.
|
||||
|
||||
If you believe you should be on the list, please contact us and we will
|
||||
consider your request based on the above criteria.
|
||||
|
||||
Vendored
+1
-1
@@ -108,7 +108,7 @@ to search and re-edit already inserted lines of text.
|
||||
|
||||
The followings are the history API calls:
|
||||
|
||||
int linenoiseHistoryAdd(const char *line, int is_sensitive);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(const char *filename);
|
||||
int linenoiseHistoryLoad(const char *filename);
|
||||
|
||||
Vendored
+10
-237
@@ -117,7 +117,6 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
|
||||
@@ -135,18 +134,6 @@ static int atexit_registered = 0; /* Register atexit just 1 time. */
|
||||
static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
|
||||
static int history_len = 0;
|
||||
static char **history = NULL;
|
||||
static int *history_sensitive = NULL; /* An array records whether each line in
|
||||
* history is sensitive. */
|
||||
|
||||
static int reverse_search_mode_enabled = 0;
|
||||
static int reverse_search_direction = 0; /* 1 means forward, -1 means backward. */
|
||||
static int cycle_to_next_search = 0; /* indicates whether to continue the search with CTRL+S or CTRL+R. */
|
||||
static char search_result[LINENOISE_MAX_LINE];
|
||||
static char search_result_friendly[LINENOISE_MAX_LINE];
|
||||
static int search_result_history_index = 0;
|
||||
static int search_result_start_offset = 0;
|
||||
static int ignore_once_hint = 0; /* Flag to ignore hint once, preventing it from interfering
|
||||
* with search results right after exiting search mode. */
|
||||
|
||||
/* The linenoiseState structure represents the state during line editing.
|
||||
* We pass this state to functions implementing specific editing
|
||||
@@ -156,7 +143,6 @@ struct linenoiseState {
|
||||
int ofd; /* Terminal stdout file descriptor. */
|
||||
char *buf; /* Edited line buffer. */
|
||||
size_t buflen; /* Edited line buffer size. */
|
||||
const char *origin_prompt; /* Original prompt, used to restore when exiting search mode. */
|
||||
const char *prompt; /* Prompt to display. */
|
||||
size_t plen; /* Prompt length. */
|
||||
size_t pos; /* Current cursor position. */
|
||||
@@ -167,13 +153,6 @@ struct linenoiseState {
|
||||
int history_index; /* The history index we are currently editing. */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int len; /* Length of the result string. */
|
||||
char *result; /* Search result string. */
|
||||
int search_term_index; /* Position of the search term in the history record. */
|
||||
int search_term_len; /* Length of the search term. */
|
||||
} linenoiseHistorySearchResult;
|
||||
|
||||
enum KEY_ACTION{
|
||||
KEY_NULL = 0, /* NULL */
|
||||
CTRL_A = 1, /* Ctrl+a */
|
||||
@@ -182,7 +161,6 @@ enum KEY_ACTION{
|
||||
CTRL_D = 4, /* Ctrl-d */
|
||||
CTRL_E = 5, /* Ctrl-e */
|
||||
CTRL_F = 6, /* Ctrl-f */
|
||||
CTRL_G = 7, /* Ctrl-g */
|
||||
CTRL_H = 8, /* Ctrl-h */
|
||||
TAB = 9, /* Tab */
|
||||
NL = 10, /* Enter typed before raw mode was enabled */
|
||||
@@ -191,8 +169,6 @@ enum KEY_ACTION{
|
||||
ENTER = 13, /* Enter */
|
||||
CTRL_N = 14, /* Ctrl-n */
|
||||
CTRL_P = 16, /* Ctrl-p */
|
||||
CTRL_R = 18, /* Ctrl-r */
|
||||
CTRL_S = 19, /* Ctrl-s */
|
||||
CTRL_T = 20, /* Ctrl-t */
|
||||
CTRL_U = 21, /* Ctrl+u */
|
||||
CTRL_W = 23, /* Ctrl+w */
|
||||
@@ -201,14 +177,8 @@ enum KEY_ACTION{
|
||||
};
|
||||
|
||||
static void linenoiseAtExit(void);
|
||||
int linenoiseHistoryAdd(const char *line, int is_sensitive);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
static void refreshLine(struct linenoiseState *l);
|
||||
static void refreshSearchResult(struct linenoiseState *ls);
|
||||
|
||||
static inline void resetSearchResult(void) {
|
||||
memset(search_result, 0, sizeof(search_result));
|
||||
memset(search_result_friendly, 0, sizeof(search_result_friendly));
|
||||
}
|
||||
|
||||
/* Debugging macro. */
|
||||
#if 0
|
||||
@@ -249,41 +219,6 @@ void linenoiseSetMultiLine(int ml) {
|
||||
mlmode = ml;
|
||||
}
|
||||
|
||||
#define REVERSE_SEARCH_PROMPT(direction) ((direction) == -1 ? "(reverse-i-search): " : "(i-search): ")
|
||||
|
||||
/* Enables the reverse search mode and refreshes the prompt. */
|
||||
static void enableReverseSearchMode(struct linenoiseState *l) {
|
||||
assert(reverse_search_mode_enabled != 1);
|
||||
reverse_search_mode_enabled = 1;
|
||||
l->origin_prompt = l->prompt;
|
||||
l->prompt = REVERSE_SEARCH_PROMPT(reverse_search_direction);
|
||||
refreshLine(l);
|
||||
}
|
||||
|
||||
/* This function disables the reverse search mode and returns the terminal to its original state.
|
||||
* If the 'discard' parameter is true, it discards the user's input search keyword and search result.
|
||||
* Otherwise, it copies the search result into 'buf', If there is no search result, it copies the
|
||||
* input search keyword instead. */
|
||||
static void disableReverseSearchMode(struct linenoiseState *l, char *buf, size_t buflen, int discard) {
|
||||
if (discard) {
|
||||
buf[0] = '\0';
|
||||
l->pos = l->len = 0;
|
||||
} else {
|
||||
ignore_once_hint = 1;
|
||||
if (strlen(search_result)) {
|
||||
strncpy(buf, search_result, buflen);
|
||||
buf[buflen-1] = '\0';
|
||||
l->pos = l->len = strlen(buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset the state to non-search state. */
|
||||
reverse_search_mode_enabled = 0;
|
||||
l->prompt = l->origin_prompt;
|
||||
resetSearchResult();
|
||||
refreshLine(l);
|
||||
}
|
||||
|
||||
/* Return true if the terminal name is in the list of terminals we know are
|
||||
* not able to understand basic escape sequences. */
|
||||
static int isUnsupportedTerm(void) {
|
||||
@@ -298,10 +233,6 @@ static int isUnsupportedTerm(void) {
|
||||
|
||||
/* Raw mode: 1960 magic shit. */
|
||||
static int enableRawMode(int fd) {
|
||||
if (getenv("FAKETTY_WITH_PROMPT") != NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct termios raw;
|
||||
|
||||
if (!isatty(STDIN_FILENO)) goto fatal;
|
||||
@@ -370,9 +301,6 @@ static int getCursorPosition(int ifd, int ofd) {
|
||||
/* Try to get the number of columns in the current terminal, or assume 80
|
||||
* if it fails. */
|
||||
static int getColumns(int ifd, int ofd) {
|
||||
if (getenv("FAKETTY_WITH_PROMPT") != NULL) {
|
||||
goto failed;
|
||||
}
|
||||
struct winsize ws;
|
||||
|
||||
if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
|
||||
@@ -564,13 +492,6 @@ static void abFree(struct abuf *ab) {
|
||||
* to the right of the prompt. */
|
||||
void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
|
||||
char seq[64];
|
||||
|
||||
/* Show hits when not in reverse search mode and not instructed to ignore once. */
|
||||
if (reverse_search_mode_enabled || ignore_once_hint) {
|
||||
ignore_once_hint = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (hintsCallback && plen+l->len < l->cols) {
|
||||
int color = -1, bold = 0;
|
||||
char *hint = hintsCallback(l->buf,&color,&bold);
|
||||
@@ -683,12 +604,7 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
unsigned int i;
|
||||
for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
|
||||
} else {
|
||||
refreshSearchResult(l);
|
||||
if (strlen(search_result) > 0) {
|
||||
abAppend(&ab, search_result_friendly, strlen(search_result_friendly));
|
||||
} else {
|
||||
abAppend(&ab,l->buf,l->len);
|
||||
}
|
||||
abAppend(&ab,l->buf,l->len);
|
||||
}
|
||||
|
||||
/* Show hits if any. */
|
||||
@@ -721,9 +637,6 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
|
||||
/* Set column. */
|
||||
col = (plen+(int)l->pos) % (int)l->cols;
|
||||
if (strlen(search_result) > 0) {
|
||||
col += search_result_start_offset;
|
||||
}
|
||||
lndebug("set col %d", 1+col);
|
||||
if (col)
|
||||
snprintf(seq,64,"\r\x1b[%dC", col);
|
||||
@@ -905,7 +818,7 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
|
||||
/* The latest history entry is always our current buffer, that
|
||||
* initially is just an empty string. */
|
||||
linenoiseHistoryAdd("", 0);
|
||||
linenoiseHistoryAdd("");
|
||||
|
||||
if (write(l.ofd,prompt,l.plen) == -1) return -1;
|
||||
while(1) {
|
||||
@@ -919,7 +832,7 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
/* Only autocomplete when the callback is set. It returns < 0 when
|
||||
* there was an error reading from fd. Otherwise it will return the
|
||||
* character that should be handled next. */
|
||||
if (c == TAB && completionCallback != NULL && !reverse_search_mode_enabled) {
|
||||
if (c == 9 && completionCallback != NULL) {
|
||||
c = completeLine(&l);
|
||||
/* Return on errors */
|
||||
if (c < 0) return l.len;
|
||||
@@ -930,9 +843,6 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
switch(c) {
|
||||
case NL: /* enter, typed before raw mode was enabled */
|
||||
break;
|
||||
case TAB:
|
||||
if (reverse_search_mode_enabled) disableReverseSearchMode(&l, buf, buflen, 0);
|
||||
break;
|
||||
case ENTER: /* enter */
|
||||
history_len--;
|
||||
free(history[history_len]);
|
||||
@@ -945,14 +855,8 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
refreshLine(&l);
|
||||
hintsCallback = hc;
|
||||
}
|
||||
|
||||
if (reverse_search_mode_enabled) disableReverseSearchMode(&l, buf, buflen, 0);
|
||||
return (int)l.len;
|
||||
case CTRL_C: /* ctrl-c */
|
||||
if (reverse_search_mode_enabled) {
|
||||
disableReverseSearchMode(&l, buf, buflen, 1);
|
||||
break;
|
||||
}
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
case BACKSPACE: /* backspace */
|
||||
@@ -987,23 +891,6 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
case CTRL_P: /* ctrl-p */
|
||||
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
|
||||
break;
|
||||
case CTRL_R:
|
||||
case CTRL_S:
|
||||
reverse_search_direction = c == CTRL_R ? -1 : 1;
|
||||
if (reverse_search_mode_enabled) {
|
||||
/* cycle search results */
|
||||
cycle_to_next_search = 1;
|
||||
l.prompt = REVERSE_SEARCH_PROMPT(reverse_search_direction);
|
||||
refreshLine(&l);
|
||||
break;
|
||||
}
|
||||
buf[0] = '\0';
|
||||
l.pos = l.len = 0;
|
||||
enableReverseSearchMode(&l);
|
||||
break;
|
||||
case CTRL_G:
|
||||
if (reverse_search_mode_enabled) disableReverseSearchMode(&l, buf, buflen, 1);
|
||||
break;
|
||||
case CTRL_N: /* ctrl-n */
|
||||
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
|
||||
break;
|
||||
@@ -1014,11 +901,6 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
if (read(l.ifd,seq,1) == -1) break;
|
||||
if (read(l.ifd,seq+1,1) == -1) break;
|
||||
|
||||
if (reverse_search_mode_enabled) {
|
||||
disableReverseSearchMode(&l, buf, buflen, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
/* ESC [ sequences. */
|
||||
if (seq[0] == '[') {
|
||||
if (seq[1] >= '0' && seq[1] <= '9') {
|
||||
@@ -1185,14 +1067,14 @@ static char *linenoiseNoTTY(void) {
|
||||
* editing function or uses dummy fgets() so that you will be able to type
|
||||
* something even in the most desperate of the conditions. */
|
||||
char *linenoise(const char *prompt) {
|
||||
char buf[LINENOISE_MAX_LINE] = {0};
|
||||
char buf[LINENOISE_MAX_LINE];
|
||||
int count;
|
||||
|
||||
if (getenv("FAKETTY_WITH_PROMPT") == NULL && !isatty(STDIN_FILENO)) {
|
||||
if (!isatty(STDIN_FILENO)) {
|
||||
/* Not a tty: read from file / pipe. In this mode we don't want any
|
||||
* limit to the line size, so we call a function to handle that. */
|
||||
return linenoiseNoTTY();
|
||||
} else if (getenv("FAKETTY_WITH_PROMPT") == NULL && isUnsupportedTerm()) {
|
||||
} else if (isUnsupportedTerm()) {
|
||||
size_t len;
|
||||
|
||||
printf("%s",prompt);
|
||||
@@ -1230,7 +1112,6 @@ static void freeHistory(void) {
|
||||
for (j = 0; j < history_len; j++)
|
||||
free(history[j]);
|
||||
free(history);
|
||||
free(history_sensitive);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,7 +1128,7 @@ static void linenoiseAtExit(void) {
|
||||
* histories, but will work well for a few hundred of entries.
|
||||
*
|
||||
* Using a circular buffer is smarter, but a bit more complex to handle. */
|
||||
int linenoiseHistoryAdd(const char *line, int is_sensitive) {
|
||||
int linenoiseHistoryAdd(const char *line) {
|
||||
char *linecopy;
|
||||
|
||||
if (history_max_len == 0) return 0;
|
||||
@@ -1256,14 +1137,7 @@ int linenoiseHistoryAdd(const char *line, int is_sensitive) {
|
||||
if (history == NULL) {
|
||||
history = malloc(sizeof(char*)*history_max_len);
|
||||
if (history == NULL) return 0;
|
||||
history_sensitive = malloc(sizeof(int)*history_max_len);
|
||||
if (history_sensitive == NULL) {
|
||||
free(history);
|
||||
history = NULL;
|
||||
return 0;
|
||||
}
|
||||
memset(history,0,(sizeof(char*)*history_max_len));
|
||||
memset(history_sensitive,0,(sizeof(int)*history_max_len));
|
||||
}
|
||||
|
||||
/* Don't add duplicated lines. */
|
||||
@@ -1276,11 +1150,9 @@ int linenoiseHistoryAdd(const char *line, int is_sensitive) {
|
||||
if (history_len == history_max_len) {
|
||||
free(history[0]);
|
||||
memmove(history,history+1,sizeof(char*)*(history_max_len-1));
|
||||
memmove(history_sensitive,history_sensitive+1,sizeof(int)*(history_max_len-1));
|
||||
history_len--;
|
||||
}
|
||||
history[history_len] = linecopy;
|
||||
history_sensitive[history_len] = is_sensitive;
|
||||
history_len++;
|
||||
return 1;
|
||||
}
|
||||
@@ -1291,7 +1163,6 @@ int linenoiseHistoryAdd(const char *line, int is_sensitive) {
|
||||
* than the amount of items already inside the history. */
|
||||
int linenoiseHistorySetMaxLen(int len) {
|
||||
char **new;
|
||||
int *new_sensitive;
|
||||
|
||||
if (len < 1) return 0;
|
||||
if (history) {
|
||||
@@ -1299,11 +1170,6 @@ int linenoiseHistorySetMaxLen(int len) {
|
||||
|
||||
new = malloc(sizeof(char*)*len);
|
||||
if (new == NULL) return 0;
|
||||
new_sensitive = malloc(sizeof(int)*len);
|
||||
if (new_sensitive == NULL) {
|
||||
free(new);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If we can't copy everything, free the elements we'll not use. */
|
||||
if (len < tocopy) {
|
||||
@@ -1313,13 +1179,9 @@ int linenoiseHistorySetMaxLen(int len) {
|
||||
tocopy = len;
|
||||
}
|
||||
memset(new,0,sizeof(char*)*len);
|
||||
memset(new_sensitive,0,sizeof(int)*len);
|
||||
memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
|
||||
memcpy(new_sensitive,history_sensitive+(history_len-tocopy), sizeof(int)*tocopy);
|
||||
free(history);
|
||||
free(history_sensitive);
|
||||
history = new;
|
||||
history_sensitive = new_sensitive;
|
||||
}
|
||||
history_max_len = len;
|
||||
if (history_len > history_max_len)
|
||||
@@ -1339,7 +1201,7 @@ int linenoiseHistorySave(const char *filename) {
|
||||
if (fp == NULL) return -1;
|
||||
fchmod(fileno(fp),S_IRUSR|S_IWUSR);
|
||||
for (j = 0; j < history_len; j++)
|
||||
if (!history_sensitive[j]) fprintf(fp,"%s\n",history[j]);
|
||||
fprintf(fp,"%s\n",history[j]);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
@@ -1361,97 +1223,8 @@ int linenoiseHistoryLoad(const char *filename) {
|
||||
p = strchr(buf,'\r');
|
||||
if (!p) p = strchr(buf,'\n');
|
||||
if (p) *p = '\0';
|
||||
linenoiseHistoryAdd(buf, 0);
|
||||
linenoiseHistoryAdd(buf);
|
||||
}
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This function updates the search index based on the direction of the search.
|
||||
* Returns 0 if the beginning or end of the history is reached, otherwise, returns 1. */
|
||||
static int setNextSearchIndex(int *i) {
|
||||
if (reverse_search_direction == 1) {
|
||||
if (*i == history_len-1) return 0;
|
||||
*i = *i + 1;
|
||||
} else {
|
||||
if (*i <= 0) return 0;
|
||||
*i = *i - 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
linenoiseHistorySearchResult searchInHistory(char *search_term) {
|
||||
linenoiseHistorySearchResult result = {0};
|
||||
|
||||
if (!history_len || !strlen(search_term)) return result;
|
||||
|
||||
int i = cycle_to_next_search ? search_result_history_index :
|
||||
(reverse_search_direction == -1 ? history_len-1 : 0);
|
||||
|
||||
while (1) {
|
||||
char *found = strstr(history[i], search_term);
|
||||
|
||||
/* check if we found the same string at another index when cycling, this would be annoying to cycle through
|
||||
* as it might appear that cycling isn't working */
|
||||
int strings_are_the_same = cycle_to_next_search && strcmp(history[i], history[search_result_history_index]) == 0;
|
||||
|
||||
if (found && !strings_are_the_same) {
|
||||
int haystack_index = found - history[i];
|
||||
result.result = history[i];
|
||||
result.len = strlen(history[i]);
|
||||
result.search_term_index = haystack_index;
|
||||
result.search_term_len = strlen(search_term);
|
||||
search_result_history_index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Exit if reached the end. */
|
||||
if (!setNextSearchIndex(&i)) break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void refreshSearchResult(struct linenoiseState *ls) {
|
||||
if (!reverse_search_mode_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
linenoiseHistorySearchResult sr = searchInHistory(ls->buf);
|
||||
int found = sr.result && sr.len;
|
||||
|
||||
/* If the search term has not changed and we are cycling to the next search result
|
||||
* (using CTRL+R or CTRL+S), there is no need to reset the old search result. */
|
||||
if (!cycle_to_next_search || found)
|
||||
resetSearchResult();
|
||||
cycle_to_next_search = 0;
|
||||
|
||||
if (found) {
|
||||
char *bold = "\x1B[1m";
|
||||
char *normal = "\x1B[0m";
|
||||
|
||||
int size_needed = sr.search_term_index + sr.search_term_len + sr.len -
|
||||
(sr.search_term_index+sr.search_term_len) + sizeof(normal) + sizeof(bold) + sizeof(normal);
|
||||
if (size_needed > sizeof(search_result_friendly) - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allocate memory for the prefix, match, and suffix strings, one extra byte for `\0`. */
|
||||
char *prefix = calloc(sizeof(char), sr.search_term_index + 1);
|
||||
char *match = calloc(sizeof(char), sr.search_term_len + 1);
|
||||
char *suffix = calloc(sizeof(char), sr.len - (sr.search_term_index+sr.search_term_len) + 1);
|
||||
|
||||
memcpy(prefix, sr.result, sr.search_term_index);
|
||||
memcpy(match, sr.result + sr.search_term_index, sr.search_term_len);
|
||||
memcpy(suffix, sr.result + sr.search_term_index + sr.search_term_len,
|
||||
sr.len - (sr.search_term_index+sr.search_term_len));
|
||||
sprintf(search_result, "%s%s%s", prefix, match, suffix);
|
||||
sprintf(search_result_friendly, "%s%s%s%s%s%s", normal, prefix, bold, match, normal, suffix);
|
||||
|
||||
free(prefix);
|
||||
free(match);
|
||||
free(suffix);
|
||||
|
||||
search_result_start_offset = sr.search_term_index;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -58,7 +58,7 @@ void linenoiseAddCompletion(linenoiseCompletions *, const char *);
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
void linenoiseFree(void *ptr);
|
||||
int linenoiseHistoryAdd(const char *line, int is_sensitive);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(const char *filename);
|
||||
int linenoiseHistoryLoad(const char *filename);
|
||||
|
||||
Vendored
+1
-8
@@ -234,17 +234,10 @@ static const luaL_Reg syslib[] = {
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
#define UNUSED(V) ((void) V)
|
||||
|
||||
/* Only a subset is loaded currently, for sandboxing concerns. */
|
||||
static const luaL_Reg sandbox_syslib[] = {
|
||||
{"clock", os_clock},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
LUALIB_API int luaopen_os (lua_State *L) {
|
||||
UNUSED(syslib);
|
||||
luaL_register(L, LUA_OSLIBNAME, sandbox_syslib);
|
||||
luaL_register(L, LUA_OSLIBNAME, syslib);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
+6
-37
@@ -51,7 +51,6 @@
|
||||
#
|
||||
# loadmodule /path/to/my_module.so
|
||||
# loadmodule /path/to/other_module.so
|
||||
# loadmodule /path/to/args_module.so [arg [arg ...]]
|
||||
|
||||
################################## NETWORK #####################################
|
||||
|
||||
@@ -389,11 +388,6 @@ databases 16
|
||||
# ASCII art logo in startup logs by setting the following option to yes.
|
||||
always-show-logo no
|
||||
|
||||
# To avoid logging personal identifiable information (PII) into server log file,
|
||||
# uncomment the following:
|
||||
#
|
||||
# hide-user-data-from-log yes
|
||||
|
||||
# By default, Redis modifies the process title (as seen in 'top' and 'ps') to
|
||||
# provide some runtime information. It is possible to disable this and leave
|
||||
# the process name as executed by setting the following to no.
|
||||
@@ -1168,8 +1162,7 @@ acllog-max-len 128
|
||||
# configuration directive.
|
||||
#
|
||||
# The default of 5 produces good enough results. 10 Approximates very closely
|
||||
# true LRU but costs more CPU. 3 is faster but not very accurate. The maximum
|
||||
# value that can be set is 64.
|
||||
# true LRU but costs more CPU. 3 is faster but not very accurate.
|
||||
#
|
||||
# maxmemory-samples 5
|
||||
|
||||
@@ -1389,10 +1382,6 @@ disable-thp yes
|
||||
# If the AOF is enabled on startup Redis will load the AOF, that is the file
|
||||
# with the better durability guarantees.
|
||||
#
|
||||
# Note that changing this value in a config file of an existing database and
|
||||
# restarting the server can lead to data loss. A conversion needs to be done
|
||||
# by setting it via CONFIG command on a live server first.
|
||||
#
|
||||
# Please check https://redis.io/topics/persistence for more information.
|
||||
|
||||
appendonly no
|
||||
@@ -2081,7 +2070,7 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
# amount by default in order to avoid that a protocol desynchronization (for
|
||||
# instance due to a bug in the client) will lead to unbound memory usage in
|
||||
# the query buffer. However you can configure it here if you have very special
|
||||
# needs, such as a command with huge argument, or huge multi/exec requests or alike.
|
||||
# needs, such us huge multi/exec requests or alike.
|
||||
#
|
||||
# client-query-buffer-limit 1gb
|
||||
|
||||
@@ -2206,26 +2195,6 @@ rdb-save-incremental-fsync yes
|
||||
# lfu-log-factor 10
|
||||
# lfu-decay-time 1
|
||||
|
||||
|
||||
# The maximum number of new client connections accepted per event-loop cycle. This configuration
|
||||
# is set independently for TLS connections.
|
||||
#
|
||||
# By default, up to 10 new connection will be accepted per event-loop cycle for normal connections
|
||||
# and up to 1 new connection per event-loop cycle for TLS connections.
|
||||
#
|
||||
# Adjusting this to a larger number can slightly improve efficiency for new connections
|
||||
# at the risk of causing timeouts for regular commands on established connections. It is
|
||||
# not advised to change this without ensuring that all clients have limited connection
|
||||
# pools and exponential backoff in the case of command/connection timeouts.
|
||||
#
|
||||
# If your application is establishing a large number of new connections per second you should
|
||||
# also consider tuning the value of tcp-backlog, which allows the kernel to buffer more
|
||||
# pending connections before dropping or rejecting connections.
|
||||
#
|
||||
# max-new-connections-per-cycle 10
|
||||
# max-new-tls-connections-per-cycle 1
|
||||
|
||||
|
||||
########################### ACTIVE DEFRAGMENTATION #######################
|
||||
#
|
||||
# What is active defragmentation?
|
||||
@@ -2307,16 +2276,16 @@ jemalloc-bg-thread yes
|
||||
# the taskset command:
|
||||
#
|
||||
# Set redis server/io threads to cpu affinity 0,2,4,6:
|
||||
# server-cpulist 0-7:2
|
||||
# server_cpulist 0-7:2
|
||||
#
|
||||
# Set bio threads to cpu affinity 1,3:
|
||||
# bio-cpulist 1,3
|
||||
# bio_cpulist 1,3
|
||||
#
|
||||
# Set aof rewrite child process to cpu affinity 8,9,10,11:
|
||||
# aof-rewrite-cpulist 8-11
|
||||
# aof_rewrite_cpulist 8-11
|
||||
#
|
||||
# Set bgsave child process to cpu affinity 1,10,11
|
||||
# bgsave-cpulist 1,10-11
|
||||
# bgsave_cpulist 1,10-11
|
||||
|
||||
# In some cases redis will emit warnings and even refuse to start if it detects
|
||||
# that the system is in bad state, it is possible to suppress these warnings
|
||||
|
||||
@@ -55,5 +55,4 @@ $TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/async_rm_call \
|
||||
--single unit/moduleapi/moduleauth \
|
||||
--single unit/moduleapi/rdbloadsave \
|
||||
--single unit/moduleapi/crash \
|
||||
"${@}"
|
||||
|
||||
+10
-24
@@ -1,9 +1,6 @@
|
||||
# Redis Makefile
|
||||
# Copyright (c) 2011-Present, Redis Ltd.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Licensed under your choice of the Redis Source Available License 2.0
|
||||
# (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
# Copyright (C) 2009 Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
#
|
||||
# The Makefile composes the final FINAL_CFLAGS and FINAL_LDFLAGS using
|
||||
# what is needed for Redis plus the standard CFLAGS and LDFLAGS passed.
|
||||
@@ -19,20 +16,14 @@ release_hdr := $(shell sh -c './mkreleasehdr.sh')
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
|
||||
CLANG := $(findstring clang,$(shell sh -c '$(CC) --version | head -1'))
|
||||
|
||||
# Optimization flags. To override, the OPTIMIZATION variable can be passed, but
|
||||
# some automatic defaults are added to it. To specify optimization flags
|
||||
# explicitly without any defaults added, pass the OPT variable instead.
|
||||
OPTIMIZATION?=-O3
|
||||
ifeq ($(OPTIMIZATION),-O3)
|
||||
ifeq (clang,$(CLANG))
|
||||
OPTIMIZATION+=-flto
|
||||
REDIS_CFLAGS+=-flto
|
||||
else
|
||||
OPTIMIZATION+=-flto=auto
|
||||
REDIS_CFLAGS+=-flto=auto
|
||||
endif
|
||||
endif
|
||||
ifneq ($(OPTIMIZATION),-O0)
|
||||
OPTIMIZATION+=-fno-omit-frame-pointer
|
||||
REDIS_LDFLAGS+=-O3 -flto
|
||||
endif
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv
|
||||
NODEPS:=clean distclean
|
||||
@@ -129,7 +120,7 @@ endif
|
||||
-include .make-settings
|
||||
|
||||
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_LDFLAGS=$(LDFLAGS) $(OPT) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
@@ -357,7 +348,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
|
||||
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
|
||||
REDIS_SERVER_OBJ=threads_mngr.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o mstr.o kvstore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_legacy.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o
|
||||
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o cli_commands.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
|
||||
@@ -432,7 +423,7 @@ $(TLS_MODULE_NAME): $(REDIS_SERVER_NAME)
|
||||
|
||||
# redis-cli
|
||||
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o ../deps/hdr_histogram/libhdrhistogram.a $(FINAL_LIBS) $(TLS_CLIENT_LIBS)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS) $(TLS_CLIENT_LIBS)
|
||||
|
||||
# redis-benchmark
|
||||
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
@@ -447,16 +438,11 @@ DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ
|
||||
%.o: %.c .make-prerequisites
|
||||
$(REDIS_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.
|
||||
# The file commands.def is checked in and doesn't normally need to be rebuilt. It
|
||||
# is built only if python is available and its prereqs are modified.
|
||||
ifneq (,$(PYTHON))
|
||||
$(COMMANDS_DEF_FILENAME).def: commands/*.json ../utils/generate-command-code.py
|
||||
$(QUIET_GEN)$(PYTHON) ../utils/generate-command-code.py $(GEN_COMMANDS_FLAGS)
|
||||
|
||||
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 $@
|
||||
endif
|
||||
|
||||
commands.c: $(COMMANDS_DEF_FILENAME).def
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2018-Present, Redis Ltd.
|
||||
* Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -38,12 +59,10 @@ static rax *commandId = NULL; /* Command name to id mapping */
|
||||
|
||||
static unsigned long nextid = 0; /* Next command id that has not been assigned */
|
||||
|
||||
#define ACL_MAX_CATEGORIES 64 /* Maximum number of command categories */
|
||||
|
||||
struct ACLCategoryItem {
|
||||
char *name;
|
||||
const char *name;
|
||||
uint64_t flag;
|
||||
} ACLDefaultCommandCategories[] = { /* See redis.conf for details on each category. */
|
||||
} ACLCommandCategories[] = { /* See redis.conf for details on each category. */
|
||||
{"keyspace", ACL_CATEGORY_KEYSPACE},
|
||||
{"read", ACL_CATEGORY_READ},
|
||||
{"write", ACL_CATEGORY_WRITE},
|
||||
@@ -68,54 +87,6 @@ struct ACLCategoryItem {
|
||||
{NULL,0} /* Terminator. */
|
||||
};
|
||||
|
||||
static struct ACLCategoryItem *ACLCommandCategories = NULL;
|
||||
static size_t nextCommandCategory = 0; /* Index of the next command category to be added */
|
||||
|
||||
/* Implements the ability to add to the list of ACL categories at runtime. Since each ACL category
|
||||
* also requires a bit in the acl_categories flag, there is a limit to the number that can be added.
|
||||
* The new ACL categories occupy the remaining bits of acl_categories flag, other than the bits
|
||||
* occupied by the default ACL command categories.
|
||||
*
|
||||
* The optional `flag` argument allows the assignment of the `acl_categories` flag bit to the ACL category.
|
||||
* When adding a new category, except for the default ACL command categories, this arguments should be `0`
|
||||
* to allow the function to assign the next available `acl_categories` flag bit to the new ACL category.
|
||||
*
|
||||
* returns 1 -> Added, 0 -> Failed (out of space)
|
||||
*
|
||||
* This function is present here to gain access to the ACLCommandCategories array and add a new ACL category.
|
||||
*/
|
||||
int ACLAddCommandCategory(const char *name, uint64_t flag) {
|
||||
if (nextCommandCategory >= ACL_MAX_CATEGORIES) return 0;
|
||||
ACLCommandCategories[nextCommandCategory].name = zstrdup(name);
|
||||
ACLCommandCategories[nextCommandCategory].flag = flag != 0 ? flag : (1ULL<<nextCommandCategory);
|
||||
nextCommandCategory++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Initializes ACLCommandCategories with default ACL categories and allocates space for
|
||||
* new ACL categories.
|
||||
*/
|
||||
void ACLInitCommandCategories(void) {
|
||||
ACLCommandCategories = zcalloc(sizeof(struct ACLCategoryItem) * (ACL_MAX_CATEGORIES + 1));
|
||||
for (int j = 0; ACLDefaultCommandCategories[j].flag; j++) {
|
||||
serverAssert(ACLAddCommandCategory(ACLDefaultCommandCategories[j].name, ACLDefaultCommandCategories[j].flag));
|
||||
}
|
||||
}
|
||||
|
||||
/* This function removes the specified number of categories from the trailing end of
|
||||
* the `ACLCommandCategories` array.
|
||||
* The purpose of this is to remove the categories added by modules that fail
|
||||
* during the onload function.
|
||||
*/
|
||||
void ACLCleanupCategoriesOnFailure(size_t num_acl_categories_added) {
|
||||
for (size_t j = nextCommandCategory - num_acl_categories_added; j < nextCommandCategory; j++) {
|
||||
zfree(ACLCommandCategories[j].name);
|
||||
ACLCommandCategories[j].name = NULL;
|
||||
ACLCommandCategories[j].flag = 0;
|
||||
}
|
||||
nextCommandCategory -= num_acl_categories_added;
|
||||
}
|
||||
|
||||
struct ACLUserFlag {
|
||||
const char *name;
|
||||
uint64_t flag;
|
||||
@@ -288,7 +259,7 @@ void *ACLListDupSds(void *item) {
|
||||
/* Structure used for handling key patterns with different key
|
||||
* based permissions. */
|
||||
typedef struct {
|
||||
int flags; /* The ACL key permission types for this key pattern */
|
||||
int flags; /* The CMD_KEYS_* flags for this key pattern */
|
||||
sds pattern; /* The pattern to match keys against */
|
||||
} keyPattern;
|
||||
|
||||
@@ -416,7 +387,7 @@ aclSelector *ACLUserGetRootSelector(user *u) {
|
||||
*
|
||||
* If the user with such name already exists NULL is returned. */
|
||||
user *ACLCreateUser(const char *name, size_t namelen) {
|
||||
if (raxFind(Users,(unsigned char*)name,namelen,NULL)) return NULL;
|
||||
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
|
||||
user *u = zmalloc(sizeof(*u));
|
||||
u->name = sdsnewlen(name,namelen);
|
||||
u->flags = USER_FLAG_DISABLED;
|
||||
@@ -485,7 +456,15 @@ void ACLFreeUserAndKillClients(user *u) {
|
||||
* this may result in some security hole: it's much
|
||||
* more defensive to set the default user and put
|
||||
* it in non authenticated mode. */
|
||||
deauthenticateAndCloseClient(c);
|
||||
c->user = DefaultUser;
|
||||
c->authenticated = 0;
|
||||
/* We will write replies to this client later, so we can't
|
||||
* close it directly even if async. */
|
||||
if (c == server.current_client) {
|
||||
c->flags |= CLIENT_CLOSE_AFTER_COMMAND;
|
||||
} else {
|
||||
freeClientAsync(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
ACLFreeUser(u);
|
||||
@@ -510,6 +489,12 @@ void ACLCopyUser(user *dst, user *src) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Free all the users registered in the radix tree 'users' and free the
|
||||
* radix tree itself. */
|
||||
void ACLFreeUsersSet(rax *users) {
|
||||
raxFreeWithCallback(users,(void(*)(void*))ACLFreeUserAndKillClients);
|
||||
}
|
||||
|
||||
/* Given a command ID, this function set by reference 'word' and 'bit'
|
||||
* so that user->allowed_commands[word] will address the right word
|
||||
* where the corresponding bit for the provided ID is stored, and
|
||||
@@ -928,7 +913,7 @@ void ACLResetFirstArgs(aclSelector *selector) {
|
||||
selector->allowed_firstargs = NULL;
|
||||
}
|
||||
|
||||
/* Add a first-arg to the list of subcommands for the user 'u' and
|
||||
/* Add a first-arh to the list of subcommands for the user 'u' and
|
||||
* the command id specified. */
|
||||
void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub) {
|
||||
/* If this is the first first-arg to be configured for
|
||||
@@ -1418,7 +1403,6 @@ user *ACLCreateDefaultUser(void) {
|
||||
void ACLInit(void) {
|
||||
Users = raxNew();
|
||||
UsersToLoad = listCreate();
|
||||
ACLInitCommandCategories();
|
||||
listSetMatchMethod(UsersToLoad, ACLListMatchLoadedUser);
|
||||
ACLLog = listCreate();
|
||||
DefaultUser = ACLCreateDefaultUser();
|
||||
@@ -1428,7 +1412,7 @@ void ACLInit(void) {
|
||||
* otherwise C_ERR is returned and errno is set to:
|
||||
*
|
||||
* EINVAL: if the username-password do not match.
|
||||
* ENOENT: if the specified user does not exist at all.
|
||||
* ENONENT: if the specified user does not exist at all.
|
||||
*/
|
||||
int ACLCheckUserCredentials(robj *username, robj *password) {
|
||||
user *u = ACLGetUserByName(username->ptr,sdslen(username->ptr));
|
||||
@@ -1523,8 +1507,8 @@ unsigned long ACLGetCommandID(sds cmdname) {
|
||||
sds lowername = sdsdup(cmdname);
|
||||
sdstolower(lowername);
|
||||
if (commandId == NULL) commandId = raxNew();
|
||||
void *id;
|
||||
if (raxFind(commandId,(unsigned char*)lowername,sdslen(lowername),&id)) {
|
||||
void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername));
|
||||
if (id != raxNotFound) {
|
||||
sdsfree(lowername);
|
||||
return (unsigned long)id;
|
||||
}
|
||||
@@ -1555,8 +1539,8 @@ void ACLClearCommandID(void) {
|
||||
|
||||
/* Return an username by its name, or NULL if the user does not exist. */
|
||||
user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||
void *myuser = NULL;
|
||||
raxFind(Users,(unsigned char*)name,namelen,&myuser);
|
||||
void *myuser = raxFind(Users,(unsigned char*)name,namelen);
|
||||
if (myuser == raxNotFound) return NULL;
|
||||
return myuser;
|
||||
}
|
||||
|
||||
@@ -1879,20 +1863,29 @@ int ACLCheckAllPerm(client *c, int *idxptr) {
|
||||
return ACLCheckAllUserCommandPerm(c->user, c->cmd, c->argv, c->argc, idxptr);
|
||||
}
|
||||
|
||||
/* If 'new' can access all channels 'original' could then return NULL;
|
||||
Otherwise return a list of channels that the new user can access */
|
||||
list *getUpcomingChannelList(user *new, user *original) {
|
||||
/* Check if the user's existing pub/sub clients violate the ACL pub/sub
|
||||
* permissions specified via the upcoming argument, and kill them if so. */
|
||||
void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
|
||||
/* Do nothing if there are no subscribers. */
|
||||
if (!dictSize(server.pubsub_patterns) &&
|
||||
!dictSize(server.pubsub_channels) &&
|
||||
!dictSize(server.pubsubshard_channels))
|
||||
return;
|
||||
|
||||
listIter li, lpi;
|
||||
listNode *ln, *lpn;
|
||||
|
||||
/* Optimization: we check if any selector has all channel permissions. */
|
||||
robj *o;
|
||||
int kill = 0;
|
||||
|
||||
/* First optimization is we check if any selector has all channel
|
||||
* permissions. */
|
||||
listRewind(new->selectors,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
aclSelector *s = (aclSelector *) listNodeValue(ln);
|
||||
if (s->flags & SELECTOR_FLAG_ALLCHANNELS) return NULL;
|
||||
if (s->flags & SELECTOR_FLAG_ALLCHANNELS) return;
|
||||
}
|
||||
|
||||
/* Next, check if the new list of channels
|
||||
/* Second optimization is to check if the new list of channels
|
||||
* is a strict superset of the original. This is done by
|
||||
* created an "upcoming" list of all channels that are in
|
||||
* the new user and checking each of the existing channels
|
||||
@@ -1930,87 +1923,58 @@ list *getUpcomingChannelList(user *new, user *original) {
|
||||
if (match) {
|
||||
/* All channels were matched, no need to kill clients. */
|
||||
listRelease(upcoming);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return upcoming;
|
||||
}
|
||||
|
||||
/* Check if the client should be killed because it is subscribed to channels that were
|
||||
* permitted in the past, are not in the `upcoming` channel list. */
|
||||
int ACLShouldKillPubsubClient(client *c, list *upcoming) {
|
||||
robj *o;
|
||||
int kill = 0;
|
||||
|
||||
if (getClientType(c) == CLIENT_TYPE_PUBSUB) {
|
||||
/* Check for pattern violations. */
|
||||
dictIterator *di = dictGetIterator(c->pubsub_patterns);
|
||||
dictEntry *de;
|
||||
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||
o = dictGetKey(de);
|
||||
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 1);
|
||||
kill = (res == ACL_DENIED_CHANNEL);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
/* Check for channel violations. */
|
||||
if (!kill) {
|
||||
/* Check for global channels violation. */
|
||||
di = dictGetIterator(c->pubsub_channels);
|
||||
|
||||
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||
o = dictGetKey(de);
|
||||
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0);
|
||||
kill = (res == ACL_DENIED_CHANNEL);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
if (!kill) {
|
||||
/* Check for shard channels violation. */
|
||||
di = dictGetIterator(c->pubsubshard_channels);
|
||||
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||
o = dictGetKey(de);
|
||||
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0);
|
||||
kill = (res == ACL_DENIED_CHANNEL);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
if (kill) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if the user's existing pub/sub clients violate the ACL pub/sub
|
||||
* permissions specified via the upcoming argument, and kill them if so. */
|
||||
void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
|
||||
/* Do nothing if there are no subscribers. */
|
||||
if (pubsubTotalSubscriptions() == 0)
|
||||
return;
|
||||
|
||||
list *channels = getUpcomingChannelList(new, original);
|
||||
/* If the new user's pubsub permissions are a strict superset of the original, return early. */
|
||||
if (!channels)
|
||||
return;
|
||||
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
}
|
||||
|
||||
/* Permissions have changed, so we need to iterate through all
|
||||
* the clients and disconnect those that are no longer valid.
|
||||
* Scan all connected clients to find the user's pub/subs. */
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
if (c->user != original)
|
||||
continue;
|
||||
if (ACLShouldKillPubsubClient(c, channels))
|
||||
deauthenticateAndCloseClient(c);
|
||||
}
|
||||
kill = 0;
|
||||
|
||||
listRelease(channels);
|
||||
if (c->user == original && getClientType(c) == CLIENT_TYPE_PUBSUB) {
|
||||
/* Check for pattern violations. */
|
||||
dictIterator *di = dictGetIterator(c->pubsub_patterns);
|
||||
dictEntry *de;
|
||||
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||
o = dictGetKey(de);
|
||||
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 1);
|
||||
kill = (res == ACL_DENIED_CHANNEL);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
/* Check for channel violations. */
|
||||
if (!kill) {
|
||||
/* Check for global channels violation. */
|
||||
di = dictGetIterator(c->pubsub_channels);
|
||||
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||
o = dictGetKey(de);
|
||||
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0);
|
||||
kill = (res == ACL_DENIED_CHANNEL);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
if (!kill) {
|
||||
/* Check for shard channels violation. */
|
||||
di = dictGetIterator(c->pubsubshard_channels);
|
||||
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||
o = dictGetKey(de);
|
||||
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0);
|
||||
kill = (res == ACL_DENIED_CHANNEL);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* Kill it. */
|
||||
if (kill) {
|
||||
freeClient(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
listRelease(upcoming);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
@@ -2417,46 +2381,11 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
ACLFreeUser(new_default);
|
||||
raxInsert(Users,(unsigned char*)"default",7,DefaultUser,NULL);
|
||||
raxRemove(old_users,(unsigned char*)"default",7,NULL);
|
||||
|
||||
/* If there are some subscribers, we need to check if we need to drop some clients. */
|
||||
rax *user_channels = NULL;
|
||||
if (pubsubTotalSubscriptions() > 0) {
|
||||
user_channels = raxNew();
|
||||
}
|
||||
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
/* a MASTER client can do everything (and user = NULL) so we can skip it */
|
||||
if (c->flags & CLIENT_MASTER)
|
||||
continue;
|
||||
user *original = c->user;
|
||||
list *channels = NULL;
|
||||
user *new = ACLGetUserByName(c->user->name, sdslen(c->user->name));
|
||||
if (new && user_channels) {
|
||||
if (!raxFind(user_channels, (unsigned char*)(new->name), sdslen(new->name), (void**)&channels)) {
|
||||
channels = getUpcomingChannelList(new, original);
|
||||
raxInsert(user_channels, (unsigned char*)(new->name), sdslen(new->name), channels, NULL);
|
||||
}
|
||||
}
|
||||
/* When the new channel list is NULL, it means the new user's channel list is a superset of the old user's list. */
|
||||
if (!new || (channels && ACLShouldKillPubsubClient(c, channels))) {
|
||||
deauthenticateAndCloseClient(c);
|
||||
continue;
|
||||
}
|
||||
c->user = new;
|
||||
}
|
||||
|
||||
if (user_channels)
|
||||
raxFreeWithCallback(user_channels, (void(*)(void*))listRelease);
|
||||
raxFreeWithCallback(old_users,(void(*)(void*))ACLFreeUser);
|
||||
ACLFreeUsersSet(old_users);
|
||||
sdsfree(errors);
|
||||
return NULL;
|
||||
} else {
|
||||
raxFreeWithCallback(Users,(void(*)(void*))ACLFreeUser);
|
||||
ACLFreeUsersSet(Users);
|
||||
Users = old_users;
|
||||
errors = sdscat(errors,"WARNING: ACL errors detected, no change to the previously active ACL rules was performed");
|
||||
return errors;
|
||||
@@ -2634,15 +2563,6 @@ void ACLUpdateInfoMetrics(int reason){
|
||||
}
|
||||
}
|
||||
|
||||
static void trimACLLogEntriesToMaxLen(void) {
|
||||
while(listLength(ACLLog) > server.acllog_max_len) {
|
||||
listNode *ln = listLast(ACLLog);
|
||||
ACLLogEntry *le = listNodeValue(ln);
|
||||
ACLFreeLogEntry(le);
|
||||
listDelNode(ACLLog,ln);
|
||||
}
|
||||
}
|
||||
|
||||
/* Adds a new entry in the ACL log, making sure to delete the old entry
|
||||
* if we reach the maximum length allowed for the log. This function attempts
|
||||
* to find similar entries in the current log in order to bump the counter of
|
||||
@@ -2662,11 +2582,6 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username
|
||||
/* Update ACL info metrics */
|
||||
ACLUpdateInfoMetrics(reason);
|
||||
|
||||
if (server.acllog_max_len == 0) {
|
||||
trimACLLogEntriesToMaxLen();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a new entry. */
|
||||
struct ACLLogEntry *le = zmalloc(sizeof(*le));
|
||||
le->count = 1;
|
||||
@@ -2729,7 +2644,12 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username
|
||||
* to its maximum size. */
|
||||
ACLLogEntryCount++; /* Incrementing the entry_id count to make each record in the log unique. */
|
||||
listAddNodeHead(ACLLog, le);
|
||||
trimACLLogEntriesToMaxLen();
|
||||
while(listLength(ACLLog) > server.acllog_max_len) {
|
||||
listNode *ln = listLast(ACLLog);
|
||||
ACLLogEntry *le = listNodeValue(ln);
|
||||
ACLFreeLogEntry(le);
|
||||
listDelNode(ACLLog,ln);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2853,7 +2773,8 @@ void aclCommand(client *c) {
|
||||
sds username = c->argv[2]->ptr;
|
||||
/* Check username validity. */
|
||||
if (ACLStringHasSpaces(username,sdslen(username))) {
|
||||
addReplyError(c, "Usernames can't contain spaces or null characters");
|
||||
addReplyErrorFormat(c,
|
||||
"Usernames can't contain spaces or null characters");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2871,10 +2792,6 @@ void aclCommand(client *c) {
|
||||
}
|
||||
return;
|
||||
} else if (!strcasecmp(sub,"deluser") && c->argc >= 3) {
|
||||
/* Initially redact all the arguments to not leak any information
|
||||
* about the users. */
|
||||
for (int j = 2; j < c->argc; j++) redactClientCommandArgument(c, j);
|
||||
|
||||
int deleted = 0;
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
sds username = c->argv[j]->ptr;
|
||||
@@ -2897,9 +2814,6 @@ void aclCommand(client *c) {
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
} else if (!strcasecmp(sub,"getuser") && c->argc == 3) {
|
||||
/* Redact the username to not leak any information about the user. */
|
||||
redactClientCommandArgument(c, 2);
|
||||
|
||||
user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
|
||||
if (u == NULL) {
|
||||
addReplyNull(c);
|
||||
|
||||
+24
-5
@@ -1,10 +1,31 @@
|
||||
/* adlist.c - A generic doubly linked list implementation
|
||||
*
|
||||
* Copyright (c) 2006-Present, Redis Ltd.
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@@ -55,8 +76,6 @@ void listEmpty(list *list)
|
||||
* This function can't fail. */
|
||||
void listRelease(list *list)
|
||||
{
|
||||
if (!list)
|
||||
return;
|
||||
listEmpty(list);
|
||||
zfree(list);
|
||||
}
|
||||
|
||||
+24
-3
@@ -1,10 +1,31 @@
|
||||
/* adlist.h - A generic doubly linked list implementation
|
||||
*
|
||||
* Copyright (c) 2006-Present, Redis Ltd.
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __ADLIST_H__
|
||||
|
||||
@@ -2,11 +2,32 @@
|
||||
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
|
||||
* it in form of a library for easy reuse.
|
||||
*
|
||||
* Copyright (c) 2006-Present, Redis Ltd.
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ae.h"
|
||||
@@ -128,8 +149,6 @@ void aeDeleteEventLoop(aeEventLoop *eventLoop) {
|
||||
aeTimeEvent *next_te, *te = eventLoop->timeEventHead;
|
||||
while (te) {
|
||||
next_te = te->next;
|
||||
if (te->finalizerProc)
|
||||
te->finalizerProc(eventLoop, te->clientData);
|
||||
zfree(te);
|
||||
te = next_te;
|
||||
}
|
||||
@@ -324,8 +343,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
return processed;
|
||||
}
|
||||
|
||||
/* Process every pending file event, then every pending time event
|
||||
* (that may be registered by file event callbacks just processed).
|
||||
/* Process every pending time event, then every pending file event
|
||||
* (that may be registered by time event callbacks just processed).
|
||||
* Without special flags the function sleeps until some file event
|
||||
* fires, or when the next time event occurs (if any).
|
||||
*
|
||||
|
||||
@@ -2,11 +2,32 @@
|
||||
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
|
||||
* it in form of a library for easy reuse.
|
||||
*
|
||||
* Copyright (c) 2006-Present, Redis Ltd.
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __AE_H__
|
||||
|
||||
+24
-3
@@ -1,10 +1,31 @@
|
||||
/* Linux epoll(2) based ae.c module
|
||||
*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
+24
-3
@@ -1,10 +1,31 @@
|
||||
/* Select()-based ae.c module.
|
||||
*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
+54
-136
@@ -1,10 +1,31 @@
|
||||
/* anet.c -- Basic TCP socket stuff made a bit less boring
|
||||
*
|
||||
* Copyright (c) 2006-Present, Redis Ltd.
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
@@ -61,7 +82,7 @@ int anetSetBlock(char *err, int fd, int non_block) {
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
/* Check if this flag has been set or unset, if so,
|
||||
/* Check if this flag has been set or unset, if so,
|
||||
* then there is no need to call fcntl to set/unset it again. */
|
||||
if (!!(flags & O_NONBLOCK) == !!non_block)
|
||||
return ANET_OK;
|
||||
@@ -86,8 +107,8 @@ int anetBlock(char *err, int fd) {
|
||||
return anetSetBlock(err,fd,0);
|
||||
}
|
||||
|
||||
/* Enable the FD_CLOEXEC on the given fd to avoid fd leaks.
|
||||
* This function should be invoked for fd's on specific places
|
||||
/* Enable the FD_CLOEXEC on the given fd to avoid fd leaks.
|
||||
* This function should be invoked for fd's on specific places
|
||||
* where fork + execve system calls are called. */
|
||||
int anetCloexec(int fd) {
|
||||
int r;
|
||||
@@ -109,145 +130,57 @@ int anetCloexec(int fd) {
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Enable TCP keep-alive mechanism to detect dead peers,
|
||||
* TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT will be set accordingly. */
|
||||
/* Set TCP keep alive option to detect dead peers. The interval option
|
||||
* is only used for Linux as we are using Linux-specific APIs to set
|
||||
* the probe send time, interval, and count. */
|
||||
int anetKeepAlive(char *err, int fd, int interval)
|
||||
{
|
||||
int enabled = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled)))
|
||||
int val = 1;
|
||||
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1)
|
||||
{
|
||||
anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
int idle;
|
||||
int intvl;
|
||||
int cnt;
|
||||
|
||||
/* There are platforms that are expected to support the full mechanism of TCP keep-alive,
|
||||
* we want the compiler to emit warnings of unused variables if the preprocessor directives
|
||||
* somehow fail, and other than those platforms, just omit these warnings if they happen.
|
||||
*/
|
||||
#if !(defined(_AIX) || defined(__APPLE__) || defined(__DragonFly__) || \
|
||||
defined(__FreeBSD__) || defined(__illumos__) || defined(__linux__) || \
|
||||
defined(__NetBSD__) || defined(__sun))
|
||||
UNUSED(interval);
|
||||
UNUSED(idle);
|
||||
UNUSED(intvl);
|
||||
UNUSED(cnt);
|
||||
#endif
|
||||
|
||||
#ifdef __sun
|
||||
/* The implementation of TCP keep-alive on Solaris/SmartOS is a bit unusual
|
||||
* compared to other Unix-like systems.
|
||||
* Thus, we need to specialize it on Solaris.
|
||||
*
|
||||
* There are two keep-alive mechanisms on Solaris:
|
||||
* - By default, the first keep-alive probe is sent out after a TCP connection is idle for two hours.
|
||||
* If the peer does not respond to the probe within eight minutes, the TCP connection is aborted.
|
||||
* You can alter the interval for sending out the first probe using the socket option TCP_KEEPALIVE_THRESHOLD
|
||||
* in milliseconds or TCP_KEEPIDLE in seconds.
|
||||
* The system default is controlled by the TCP ndd parameter tcp_keepalive_interval. The minimum value is ten seconds.
|
||||
* The maximum is ten days, while the default is two hours. If you receive no response to the probe,
|
||||
* you can use the TCP_KEEPALIVE_ABORT_THRESHOLD socket option to change the time threshold for aborting a TCP connection.
|
||||
* The option value is an unsigned integer in milliseconds. The value zero indicates that TCP should never time out and
|
||||
* abort the connection when probing. The system default is controlled by the TCP ndd parameter tcp_keepalive_abort_interval.
|
||||
* The default is eight minutes.
|
||||
*
|
||||
* - The second implementation is activated if socket option TCP_KEEPINTVL and/or TCP_KEEPCNT are set.
|
||||
* The time between each consequent probes is set by TCP_KEEPINTVL in seconds.
|
||||
* The minimum value is ten seconds. The maximum is ten days, while the default is two hours.
|
||||
* The TCP connection will be aborted after certain amount of probes, which is set by TCP_KEEPCNT, without receiving response.
|
||||
*/
|
||||
|
||||
idle = interval;
|
||||
if (idle < 10) idle = 10; // kernel expects at least 10 seconds
|
||||
if (idle > 10*24*60*60) idle = 10*24*60*60; // kernel expects at most 10 days
|
||||
|
||||
/* `TCP_KEEPIDLE`, `TCP_KEEPINTVL`, and `TCP_KEEPCNT` were not available on Solaris
|
||||
* until version 11.4, but let's take a chance here. */
|
||||
#if defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT)
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle))) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPIDLE: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
intvl = idle/3;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl))) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPINTVL: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
cnt = 3;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt))) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPCNT: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
#else
|
||||
/* Fall back to the first implementation of tcp-alive mechanism for older Solaris,
|
||||
* simulate the tcp-alive mechanism on other platforms via `TCP_KEEPALIVE_THRESHOLD` + `TCP_KEEPALIVE_ABORT_THRESHOLD`.
|
||||
*/
|
||||
idle *= 1000; // kernel expects milliseconds
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, &idle, sizeof(idle))) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPINTVL: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
/* Note that the consequent probes will not be sent at equal intervals on Solaris,
|
||||
* but will be sent using the exponential backoff algorithm. */
|
||||
intvl = idle/3;
|
||||
cnt = 3;
|
||||
int time_to_abort = intvl * cnt;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD, &time_to_abort, sizeof(time_to_abort))) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPCNT: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ANET_OK;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef TCP_KEEPIDLE
|
||||
#ifdef __linux__
|
||||
/* Default settings are more or less garbage, with the keepalive time
|
||||
* set to 7200 by default on Linux and other Unix-like systems.
|
||||
* Modify settings to make the feature actually useful. */
|
||||
* set to 7200 by default on Linux. Modify settings to make the feature
|
||||
* actually useful. */
|
||||
|
||||
/* Send first probe after interval. */
|
||||
idle = interval;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle))) {
|
||||
val = interval;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPIDLE: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
#elif defined(TCP_KEEPALIVE)
|
||||
/* Darwin/macOS uses TCP_KEEPALIVE in place of TCP_KEEPIDLE. */
|
||||
idle = interval;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &idle, sizeof(idle))) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPALIVE: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TCP_KEEPINTVL
|
||||
/* Send next probes after the specified interval. Note that we set the
|
||||
* delay as interval / 3, as we send three probes before detecting
|
||||
* an error (see the next setsockopt call). */
|
||||
intvl = interval/3;
|
||||
if (intvl == 0) intvl = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl))) {
|
||||
val = interval/3;
|
||||
if (val == 0) val = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPINTVL: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TCP_KEEPCNT
|
||||
/* Consider the socket in error state after three we send three ACK
|
||||
* probes without getting a reply. */
|
||||
cnt = 3;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt))) {
|
||||
val = 3;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPCNT: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
/* Set idle time with interval */
|
||||
val = interval;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPALIVE: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
#else
|
||||
((void) interval); /* Avoid unused var warning for non Linux systems. */
|
||||
#endif
|
||||
|
||||
return ANET_OK;
|
||||
@@ -306,11 +239,7 @@ int anetRecvTimeout(char *err, int fd, long long ms) {
|
||||
*
|
||||
* If flags is set to ANET_IP_ONLY the function only resolves hostnames
|
||||
* that are actually already IPv4 or IPv6 addresses. This turns the function
|
||||
* into a validating / normalizing function.
|
||||
*
|
||||
* If the flag ANET_PREFER_IPV4 is set, IPv4 is preferred over IPv6.
|
||||
* If the flag ANET_PREFER_IPV6 is set, IPv6 is preferred over IPv4.
|
||||
* */
|
||||
* into a validating / normalizing function. */
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
|
||||
int flags)
|
||||
{
|
||||
@@ -320,20 +249,9 @@ int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
|
||||
memset(&hints,0,sizeof(hints));
|
||||
if (flags & ANET_IP_ONLY) hints.ai_flags = AI_NUMERICHOST;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
if (flags & ANET_PREFER_IPV4 && !(flags & ANET_PREFER_IPV6)) {
|
||||
hints.ai_family = AF_INET;
|
||||
} else if (flags & ANET_PREFER_IPV6 && !(flags & ANET_PREFER_IPV4)) {
|
||||
hints.ai_family = AF_INET6;
|
||||
}
|
||||
hints.ai_socktype = SOCK_STREAM; /* specify socktype to avoid dups */
|
||||
|
||||
rv = getaddrinfo(host, NULL, &hints, &info);
|
||||
if (rv != 0 && hints.ai_family != AF_UNSPEC) {
|
||||
/* Try the other IP version. */
|
||||
hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET;
|
||||
rv = getaddrinfo(host, NULL, &hints, &info);
|
||||
}
|
||||
if (rv != 0) {
|
||||
if ((rv = getaddrinfo(host, NULL, &hints, &info)) != 0) {
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
+24
-5
@@ -1,10 +1,31 @@
|
||||
/* anet.c -- Basic TCP socket stuff made a bit less boring
|
||||
*
|
||||
* Copyright (c) 2006-Present, Redis Ltd.
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef ANET_H
|
||||
@@ -19,8 +40,6 @@
|
||||
/* Flags used with certain functions. */
|
||||
#define ANET_NONE 0
|
||||
#define ANET_IP_ONLY (1<<0)
|
||||
#define ANET_PREFER_IPV4 (1<<1)
|
||||
#define ANET_PREFER_IPV6 (1<<2)
|
||||
|
||||
#if defined(__sun) || defined(_AIX)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -814,7 +835,7 @@ int openNewIncrAofForAppend(void) {
|
||||
* is already synced at this point so fsync doesn't matter. */
|
||||
if (server.aof_fd != -1) {
|
||||
aof_background_fsync_and_close(server.aof_fd);
|
||||
server.aof_last_fsync = server.mstime;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
}
|
||||
server.aof_fd = newfd;
|
||||
|
||||
@@ -935,7 +956,7 @@ void stopAppendOnly(void) {
|
||||
if (redis_fsync(server.aof_fd) == -1) {
|
||||
serverLog(LL_WARNING,"Fail to fsync the AOF file: %s",strerror(errno));
|
||||
} else {
|
||||
server.aof_last_fsync = server.mstime;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
}
|
||||
close(server.aof_fd);
|
||||
|
||||
@@ -979,7 +1000,7 @@ int startAppendOnly(void) {
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
server.aof_last_fsync = server.mstime;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
/* If AOF fsync error in bio job, we just ignore it and log the event. */
|
||||
int aof_bio_fsync_status;
|
||||
atomicGet(server.aof_bio_fsync_status, aof_bio_fsync_status);
|
||||
@@ -1048,34 +1069,28 @@ void flushAppendOnlyFile(int force) {
|
||||
mstime_t latency;
|
||||
|
||||
if (sdslen(server.aof_buf) == 0) {
|
||||
if (server.aof_last_incr_fsync_offset == server.aof_last_incr_size) {
|
||||
/* All data is fsync'd already: Update fsynced_reploff_pending just in case.
|
||||
* This is needed to avoid a WAITAOF hang in case a module used RM_Call
|
||||
* with the NO_AOF flag, in which case master_repl_offset will increase but
|
||||
* fsynced_reploff_pending won't be updated (because there's no reason, from
|
||||
* the AOF POV, to call fsync) and then WAITAOF may wait on the higher offset
|
||||
* (which contains data that was only propagated to replicas, and not to AOF) */
|
||||
if (!aofFsyncInProgress())
|
||||
atomicSet(server.fsynced_reploff_pending, server.master_repl_offset);
|
||||
} else {
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* because previously in AOF_FSYNC_EVERYSEC mode, fsync is
|
||||
* called only when aof buffer is not empty, so if users
|
||||
* stop write commands before fsync called in one second,
|
||||
* the data in page cache cannot be flushed in time. */
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.mstime - server.aof_last_fsync >= 1000 &&
|
||||
!(sync_in_progress = aofFsyncInProgress()))
|
||||
goto try_fsync;
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* because previously in AOF_FSYNC_EVERYSEC mode, fsync is
|
||||
* called only when aof buffer is not empty, so if users
|
||||
* stop write commands before fsync called in one second,
|
||||
* the data in page cache cannot be flushed in time. */
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.aof_last_incr_fsync_offset != server.aof_last_incr_size &&
|
||||
server.unixtime > server.aof_last_fsync &&
|
||||
!(sync_in_progress = aofFsyncInProgress())) {
|
||||
goto try_fsync;
|
||||
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* the reason is described in the previous AOF_FSYNC_EVERYSEC block,
|
||||
* and AOF_FSYNC_ALWAYS is also checked here to handle a case where
|
||||
* aof_fsync is changed from everysec to always. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS)
|
||||
goto try_fsync;
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* the reason is described in the previous AOF_FSYNC_EVERYSEC block,
|
||||
* and AOF_FSYNC_ALWAYS is also checked here to handle a case where
|
||||
* aof_fsync is changed from everysec to always. */
|
||||
} else if (server.aof_fsync == AOF_FSYNC_ALWAYS &&
|
||||
server.aof_last_incr_fsync_offset != server.aof_last_incr_size)
|
||||
{
|
||||
goto try_fsync;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
|
||||
@@ -1089,9 +1104,9 @@ void flushAppendOnlyFile(int force) {
|
||||
if (server.aof_flush_postponed_start == 0) {
|
||||
/* No previous write postponing, remember that we are
|
||||
* postponing the flush and return. */
|
||||
server.aof_flush_postponed_start = server.mstime;
|
||||
server.aof_flush_postponed_start = server.unixtime;
|
||||
return;
|
||||
} else if (server.mstime - server.aof_flush_postponed_start < 2000) {
|
||||
} else if (server.unixtime - server.aof_flush_postponed_start < 2) {
|
||||
/* We were already waiting for fsync to finish, but for less
|
||||
* than two seconds this is still ok. Postpone again. */
|
||||
return;
|
||||
@@ -1240,15 +1255,15 @@ try_fsync:
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-fsync-always",latency);
|
||||
server.aof_last_incr_fsync_offset = server.aof_last_incr_size;
|
||||
server.aof_last_fsync = server.mstime;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
atomicSet(server.fsynced_reploff_pending, server.master_repl_offset);
|
||||
} else if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.mstime - server.aof_last_fsync >= 1000) {
|
||||
server.unixtime > server.aof_last_fsync) {
|
||||
if (!sync_in_progress) {
|
||||
aof_background_fsync(server.aof_fd);
|
||||
server.aof_last_incr_fsync_offset = server.aof_last_incr_size;
|
||||
}
|
||||
server.aof_last_fsync = server.mstime;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1834,7 +1849,6 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
|
||||
!rioWriteBulkString(r,"SADD",4) ||
|
||||
!rioWriteBulkObject(r,key))
|
||||
{
|
||||
setTypeReleaseIterator(si);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1938,21 +1952,19 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
|
||||
*
|
||||
* The function returns 0 on error, non-zero on success. */
|
||||
static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) {
|
||||
if ((hi->encoding == OBJ_ENCODING_LISTPACK) || (hi->encoding == OBJ_ENCODING_LISTPACK_EX)) {
|
||||
if (hi->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
long long vll = LLONG_MAX;
|
||||
|
||||
hashTypeCurrentFromListpack(hi, what, &vstr, &vlen, &vll, NULL);
|
||||
hashTypeCurrentFromListpack(hi, what, &vstr, &vlen, &vll);
|
||||
if (vstr)
|
||||
return rioWriteBulkString(r, (char*)vstr, vlen);
|
||||
else
|
||||
return rioWriteBulkLongLong(r, vll);
|
||||
} else if (hi->encoding == OBJ_ENCODING_HT) {
|
||||
char *str;
|
||||
size_t len;
|
||||
hashTypeCurrentFromHashTable(hi, what, &str, &len, NULL);
|
||||
return rioWriteBulkString(r, str, len);
|
||||
sds value = hashTypeCurrentFromHashTable(hi, what);
|
||||
return rioWriteBulkString(r, value, sdslen(value));
|
||||
}
|
||||
|
||||
serverPanic("Unknown hash encoding");
|
||||
@@ -1962,60 +1974,37 @@ static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) {
|
||||
/* Emit the commands needed to rebuild a hash object.
|
||||
* The function returns 0 on error, 1 on success. */
|
||||
int rewriteHashObject(rio *r, robj *key, robj *o) {
|
||||
int res = 0; /*fail*/
|
||||
|
||||
hashTypeIterator *hi;
|
||||
long long count = 0, items = hashTypeLength(o, 0);
|
||||
long long count = 0, items = hashTypeLength(o);
|
||||
|
||||
int isHFE = hashTypeGetMinExpire(o, 0) != EB_EXPIRE_TIME_INVALID;
|
||||
hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi) != C_ERR) {
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
|
||||
if (!isHFE) {
|
||||
while (hashTypeNext(hi, 0) != C_ERR) {
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
if (!rioWriteBulkCount(r, '*', 2 + cmd_items * 2) ||
|
||||
!rioWriteBulkString(r, "HMSET", 5) ||
|
||||
!rioWriteBulkObject(r, key))
|
||||
goto reHashEnd;
|
||||
}
|
||||
|
||||
if (!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) ||
|
||||
!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE))
|
||||
goto reHashEnd;
|
||||
|
||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
} else {
|
||||
while (hashTypeNext(hi, 0) != C_ERR) {
|
||||
|
||||
char hmsetCmd[] = "*4\r\n$5\r\nHMSET\r\n";
|
||||
if ( (!rioWrite(r, hmsetCmd, sizeof(hmsetCmd) - 1)) ||
|
||||
(!rioWriteBulkObject(r, key)) ||
|
||||
(!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY)) ||
|
||||
(!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE)) )
|
||||
goto reHashEnd;
|
||||
|
||||
if (hi->expire_time != EB_EXPIRE_TIME_INVALID) {
|
||||
char cmd[] = "*6\r\n$10\r\nHPEXPIREAT\r\n";
|
||||
if ( (!rioWrite(r, cmd, sizeof(cmd) - 1)) ||
|
||||
(!rioWriteBulkObject(r, key)) ||
|
||||
(!rioWriteBulkLongLong(r, hi->expire_time)) ||
|
||||
(!rioWriteBulkString(r, "FIELDS", 6)) ||
|
||||
(!rioWriteBulkString(r, "1", 1)) ||
|
||||
(!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY)) )
|
||||
goto reHashEnd;
|
||||
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
|
||||
!rioWriteBulkString(r,"HMSET",5) ||
|
||||
!rioWriteBulkObject(r,key))
|
||||
{
|
||||
hashTypeReleaseIterator(hi);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) ||
|
||||
!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE))
|
||||
{
|
||||
hashTypeReleaseIterator(hi);
|
||||
return 0;
|
||||
}
|
||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
|
||||
res = 1; /* success */
|
||||
|
||||
reHashEnd:
|
||||
hashTypeReleaseIterator(hi);
|
||||
return res;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Helper for rewriteStreamObject() that generates a bulk string into the
|
||||
@@ -2246,11 +2235,11 @@ werr:
|
||||
}
|
||||
|
||||
int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
long key_count = 0;
|
||||
long long updated_time = 0;
|
||||
kvstoreIterator *kvs_it = NULL;
|
||||
|
||||
/* Record timestamp at the beginning of rewriting AOF. */
|
||||
if (server.aof_timestamp_enabled) {
|
||||
@@ -2263,16 +2252,17 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
||||
redisDb *db = server.db + j;
|
||||
if (kvstoreSize(db->keys) == 0) continue;
|
||||
redisDb *db = server.db+j;
|
||||
dict *d = db->dict;
|
||||
if (dictSize(d) == 0) continue;
|
||||
di = dictGetSafeIterator(d);
|
||||
|
||||
/* SELECT the new DB */
|
||||
if (rioWrite(aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkLongLong(aof,j) == 0) goto werr;
|
||||
|
||||
kvs_it = kvstoreIteratorInit(db->keys);
|
||||
/* Iterate this DB writing every entry */
|
||||
while((de = kvstoreIteratorNext(kvs_it)) != NULL) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds keystr;
|
||||
robj key, *o;
|
||||
long long expiretime;
|
||||
@@ -2337,12 +2327,13 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
if (server.rdb_key_save_delay)
|
||||
debugDelay(server.rdb_key_save_delay);
|
||||
}
|
||||
kvstoreIteratorRelease(kvs_it);
|
||||
dictReleaseIterator(di);
|
||||
di = NULL;
|
||||
}
|
||||
return C_OK;
|
||||
|
||||
werr:
|
||||
if (kvs_it) kvstoreIteratorRelease(kvs_it);
|
||||
if (di) dictReleaseIterator(di);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
|
||||
+26
-5
@@ -1,16 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
const char *ascii_logo =
|
||||
" _._ \n"
|
||||
" _.-``__ ''-._ \n"
|
||||
" _.-`` `. `_. ''-._ Redis Community Edition \n"
|
||||
" .-`` .-```. ```\\/ _.,_ ''-._ %s (%s/%d) %s bit\n"
|
||||
" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n"
|
||||
" .-`` .-```. ```\\/ _.,_ ''-._ \n"
|
||||
" ( ' , .-` | `, ) Running in %s mode\n"
|
||||
" |`-._`-...-` __...-.``-._|'` _.-'| Port: %d\n"
|
||||
" | `-._ `._ / _.-' | PID: %ld\n"
|
||||
|
||||
+26
-42
@@ -1,41 +1,16 @@
|
||||
/* This file implements atomic counters using c11 _Atomic, __atomic or __sync
|
||||
* macros if available, otherwise we will throw an error when compile.
|
||||
*
|
||||
* The exported interface is composed of the following macros:
|
||||
* The exported interface is composed of three macros:
|
||||
*
|
||||
* atomicIncr(var,count) -- Increment the atomic counter
|
||||
* atomicGetIncr(var,oldvalue_var,count) -- Get and increment the atomic counter
|
||||
* atomicIncrGet(var,newvalue_var,count) -- Increment and get the atomic counter new value
|
||||
* atomicDecr(var,count) -- Decrement the atomic counter
|
||||
* atomicGet(var,dstvar) -- Fetch the atomic counter value
|
||||
* atomicSet(var,value) -- Set the atomic counter value
|
||||
* atomicGetWithSync(var,value) -- 'atomicGet' with inter-thread synchronization
|
||||
* atomicSetWithSync(var,value) -- 'atomicSet' with inter-thread synchronization
|
||||
*
|
||||
* Atomic operations on flags.
|
||||
* Flag type can be int, long, long long or their unsigned counterparts.
|
||||
* The value of the flag can be 1 or 0.
|
||||
*
|
||||
* atomicFlagGetSet(var,oldvalue_var) -- Get and set the atomic counter value
|
||||
*
|
||||
* NOTE1: __atomic* and _Atomic implementations can be actually elaborated to support any value by changing the
|
||||
* hardcoded new value passed to __atomic_exchange* from 1 to @param count
|
||||
* i.e oldvalue_var = atomic_exchange_explicit(&var, count).
|
||||
* However, in order to be compatible with the __sync functions family, we can use only 0 and 1.
|
||||
* The only exchange alternative suggested by __sync is __sync_lock_test_and_set,
|
||||
* But as described by the gnu manual for __sync_lock_test_and_set():
|
||||
* https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
|
||||
* "A target may support reduced functionality here by which the only valid value to store is the immediate constant 1. The exact value
|
||||
* actually stored in *ptr is implementation defined."
|
||||
* Hence, we can't rely on it for a any value other than 1.
|
||||
* We eventually chose to implement this method with __sync_val_compare_and_swap since it satisfies functionality needed for atomicFlagGetSet
|
||||
* (if the flag was 0 -> set to 1, if it's already 1 -> do nothing, but the final result is that the flag is set),
|
||||
* and also it has a full barrier (__sync_lock_test_and_set has acquire barrier).
|
||||
*
|
||||
* NOTE2: Unlike other atomic type, which aren't guaranteed to be lock free, c11 atmoic_flag does.
|
||||
* To check whether a type is lock free, atomic_is_lock_free() can be used.
|
||||
* It can be considered to limit the flag type to atomic_flag to improve performance.
|
||||
*
|
||||
*
|
||||
* Never use return value from the macros, instead use the AtomicGetIncr()
|
||||
* if you need to get the current value and increment it atomically, like
|
||||
* in the following example:
|
||||
@@ -46,11 +21,32 @@
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2015-Present, Redis Ltd.
|
||||
* Copyright (c) 2015, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
@@ -97,8 +93,6 @@
|
||||
#define atomicGetIncr(var,oldvalue_var,count) do { \
|
||||
oldvalue_var = atomic_fetch_add_explicit(&var,(count),memory_order_relaxed); \
|
||||
} while(0)
|
||||
#define atomicIncrGet(var, newvalue_var, count) \
|
||||
newvalue_var = atomicIncr(var,count) + count
|
||||
#define atomicDecr(var,count) atomic_fetch_sub_explicit(&var,(count),memory_order_relaxed)
|
||||
#define atomicGet(var,dstvar) do { \
|
||||
dstvar = atomic_load_explicit(&var,memory_order_relaxed); \
|
||||
@@ -109,8 +103,6 @@
|
||||
} while(0)
|
||||
#define atomicSetWithSync(var,value) \
|
||||
atomic_store_explicit(&var,value,memory_order_seq_cst)
|
||||
#define atomicFlagGetSet(var,oldvalue_var) \
|
||||
oldvalue_var = atomic_exchange_explicit(&var,1,memory_order_relaxed)
|
||||
#define REDIS_ATOMIC_API "c11-builtin"
|
||||
|
||||
#elif !defined(__ATOMIC_VAR_FORCE_SYNC_MACROS) && \
|
||||
@@ -119,8 +111,6 @@
|
||||
/* Implementation using __atomic macros. */
|
||||
|
||||
#define atomicIncr(var,count) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED)
|
||||
#define atomicIncrGet(var, newvalue_var, count) \
|
||||
newvalue_var = __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED)
|
||||
#define atomicGetIncr(var,oldvalue_var,count) do { \
|
||||
oldvalue_var = __atomic_fetch_add(&var,(count),__ATOMIC_RELAXED); \
|
||||
} while(0)
|
||||
@@ -134,16 +124,12 @@
|
||||
} while(0)
|
||||
#define atomicSetWithSync(var,value) \
|
||||
__atomic_store_n(&var,value,__ATOMIC_SEQ_CST)
|
||||
#define atomicFlagGetSet(var,oldvalue_var) \
|
||||
oldvalue_var = __atomic_exchange_n(&var,1,__ATOMIC_RELAXED)
|
||||
#define REDIS_ATOMIC_API "atomic-builtin"
|
||||
|
||||
#elif defined(HAVE_ATOMIC)
|
||||
/* Implementation using __sync macros. */
|
||||
|
||||
#define atomicIncr(var,count) __sync_add_and_fetch(&var,(count))
|
||||
#define atomicIncrGet(var, newvalue_var, count) \
|
||||
newvalue_var = __sync_add_and_fetch(&var,(count))
|
||||
#define atomicGetIncr(var,oldvalue_var,count) do { \
|
||||
oldvalue_var = __sync_fetch_and_add(&var,(count)); \
|
||||
} while(0)
|
||||
@@ -163,8 +149,6 @@
|
||||
ANNOTATE_HAPPENS_BEFORE(&var); \
|
||||
while(!__sync_bool_compare_and_swap(&var,var,value,__sync_synchronize)); \
|
||||
} while(0)
|
||||
#define atomicFlagGetSet(var,oldvalue_var) \
|
||||
oldvalue_var = __sync_val_compare_and_swap(&var,0,1)
|
||||
#define REDIS_ATOMIC_API "sync-builtin"
|
||||
|
||||
#else
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
/* Background I/O service for Redis.
|
||||
*
|
||||
* This file implements operations that we need to perform in the background.
|
||||
* Currently there are 3 operations:
|
||||
* 1) a background close(2) system call. This is needed when the process is
|
||||
* the last owner of a reference to a file closing it means unlinking it, and
|
||||
* the deletion of the file is slow, blocking the server.
|
||||
* 2) AOF fsync
|
||||
* 3) lazyfree of memory
|
||||
* Currently there is only a single operation, that is a background close(2)
|
||||
* system call. This is needed as when the process is the last owner of a
|
||||
* reference to a file closing it means unlinking it, and the deletion of the
|
||||
* file is slow, blocking the server.
|
||||
*
|
||||
* In the future we'll either continue implementing new things we need or
|
||||
* we'll switch to libeio. However there are probably long term uses for this
|
||||
* file as we may want to put here Redis specific background tasks.
|
||||
* file as we may want to put here Redis specific background tasks (for instance
|
||||
* it is not impossible that we'll need a non blocking FLUSHDB/FLUSHALL
|
||||
* implementation).
|
||||
*
|
||||
* DESIGN
|
||||
* ------
|
||||
@@ -26,26 +26,42 @@
|
||||
* least-recently-inserted to the most-recently-inserted (older jobs processed
|
||||
* first).
|
||||
*
|
||||
* To let the creator of the job to be notified about the completion of the
|
||||
* operation, it will need to submit additional dummy job, coined as
|
||||
* completion job request that will be written back eventually, by the
|
||||
* background thread, into completion job response queue. This notification
|
||||
* layout can simplify flows that might submit more than one job, such as
|
||||
* in case of FLUSHALL which for a single command submits multiple jobs. It
|
||||
* is also correct because jobs are processed in FIFO fashion.
|
||||
* Currently there is no way for the creator of the job to be notified about
|
||||
* the completion of the operation, this will only be added when/if needed.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#include "server.h"
|
||||
#include "bio.h"
|
||||
#include <fcntl.h>
|
||||
|
||||
static char* bio_worker_title[] = {
|
||||
"bio_close_file",
|
||||
@@ -60,9 +76,6 @@ static unsigned int bio_job_to_worker[] = {
|
||||
[BIO_AOF_FSYNC] = 1,
|
||||
[BIO_CLOSE_AOF] = 1,
|
||||
[BIO_LAZY_FREE] = 2,
|
||||
[BIO_COMP_RQ_CLOSE_FILE] = 0,
|
||||
[BIO_COMP_RQ_AOF_FSYNC] = 1,
|
||||
[BIO_COMP_RQ_LAZY_FREE] = 2
|
||||
};
|
||||
|
||||
static pthread_t bio_threads[BIO_WORKER_NUM];
|
||||
@@ -71,18 +84,6 @@ static pthread_cond_t bio_newjob_cond[BIO_WORKER_NUM];
|
||||
static list *bio_jobs[BIO_WORKER_NUM];
|
||||
static unsigned long bio_jobs_counter[BIO_NUM_OPS] = {0};
|
||||
|
||||
/* The bio_comp_list is used to hold completion job responses and to handover
|
||||
* to main thread to callback as notification for job completion. Main
|
||||
* thread will be triggered to read the list by signaling via writing to a pipe */
|
||||
static list *bio_comp_list;
|
||||
static pthread_mutex_t bio_mutex_comp;
|
||||
static int job_comp_pipe[2]; /* Pipe used to awake the event loop */
|
||||
|
||||
typedef struct bio_comp_item {
|
||||
comp_fn *func; /* callback after completion job will be processed */
|
||||
uint64_t arg; /* user data to be passed to the function */
|
||||
} bio_comp_item;
|
||||
|
||||
/* This structure represents a background Job. It is only used locally to this
|
||||
* file as the API does not expose the internals at all. */
|
||||
typedef union bio_job {
|
||||
@@ -106,15 +107,9 @@ typedef union bio_job {
|
||||
lazy_free_fn *free_fn; /* Function that will free the provided arguments */
|
||||
void *free_args[]; /* List of arguments to be passed to the free function */
|
||||
} free_args;
|
||||
struct {
|
||||
int type; /* header */
|
||||
comp_fn *fn; /* callback. Handover to main thread to cb as notify for job completion */
|
||||
uint64_t arg; /* callback arguments */
|
||||
} comp_rq;
|
||||
} bio_job;
|
||||
|
||||
void *bioProcessBackgroundJobs(void *arg);
|
||||
void bioPipeReadJobCompList(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
|
||||
/* Make sure we have enough stack to perform all the things we do in the
|
||||
* main thread. */
|
||||
@@ -134,27 +129,6 @@ void bioInit(void) {
|
||||
bio_jobs[j] = listCreate();
|
||||
}
|
||||
|
||||
/* init jobs comp responses */
|
||||
bio_comp_list = listCreate();
|
||||
pthread_mutex_init(&bio_mutex_comp, NULL);
|
||||
|
||||
/* Create a pipe for background thread to be able to wake up the redis main thread.
|
||||
* Make the pipe non blocking. This is just a best effort aware mechanism
|
||||
* and we do not want to block not in the read nor in the write half.
|
||||
* Enable close-on-exec flag on pipes in case of the fork-exec system calls in
|
||||
* sentinels or redis servers. */
|
||||
if (anetPipe(job_comp_pipe, O_CLOEXEC|O_NONBLOCK, O_CLOEXEC|O_NONBLOCK) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Can't create the pipe for bio thread: %s", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Register a readable event for the pipe used to awake the event loop on job completion */
|
||||
if (aeCreateFileEvent(server.el, job_comp_pipe[0], AE_READABLE,
|
||||
bioPipeReadJobCompList, NULL) == AE_ERR) {
|
||||
serverPanic("Error registering the readable event for the bio pipe.");
|
||||
}
|
||||
|
||||
/* Set the stack size as by default it may be small in some system */
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_getstacksize(&attr,&stacksize);
|
||||
@@ -200,28 +174,6 @@ void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) {
|
||||
bioSubmitJob(BIO_LAZY_FREE, job);
|
||||
}
|
||||
|
||||
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data) {
|
||||
int type;
|
||||
switch (assigned_worker) {
|
||||
case BIO_WORKER_CLOSE_FILE:
|
||||
type = BIO_COMP_RQ_CLOSE_FILE;
|
||||
break;
|
||||
case BIO_WORKER_AOF_FSYNC:
|
||||
type = BIO_COMP_RQ_AOF_FSYNC;
|
||||
break;
|
||||
case BIO_WORKER_LAZY_FREE:
|
||||
type = BIO_COMP_RQ_LAZY_FREE;
|
||||
break;
|
||||
default:
|
||||
serverPanic("Invalid worker type in bioCreateCompRq().");
|
||||
}
|
||||
|
||||
bio_job *job = zmalloc(sizeof(*job));
|
||||
job->comp_rq.fn = func;
|
||||
job->comp_rq.arg = user_data;
|
||||
bioSubmitJob(type, job);
|
||||
}
|
||||
|
||||
void bioCreateCloseJob(int fd, int need_fsync, int need_reclaim_cache) {
|
||||
bio_job *job = zmalloc(sizeof(*job));
|
||||
job->fd_args.fd = fd;
|
||||
@@ -333,21 +285,6 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
close(job->fd_args.fd);
|
||||
} else if (job_type == BIO_LAZY_FREE) {
|
||||
job->free_args.free_fn(job->free_args.free_args);
|
||||
} else if ((job_type == BIO_COMP_RQ_CLOSE_FILE) ||
|
||||
(job_type == BIO_COMP_RQ_AOF_FSYNC) ||
|
||||
(job_type == BIO_COMP_RQ_LAZY_FREE)) {
|
||||
bio_comp_item *comp_rsp = zmalloc(sizeof(bio_comp_item));
|
||||
comp_rsp->func = job->comp_rq.fn;
|
||||
comp_rsp->arg = job->comp_rq.arg;
|
||||
|
||||
/* just write it to completion job responses */
|
||||
pthread_mutex_lock(&bio_mutex_comp);
|
||||
listAddNodeTail(bio_comp_list, comp_rsp);
|
||||
pthread_mutex_unlock(&bio_mutex_comp);
|
||||
|
||||
if (write(job_comp_pipe[1],"A",1) != 1) {
|
||||
/* Pipe is non-blocking, write() may fail if it's full. */
|
||||
}
|
||||
} else {
|
||||
serverPanic("Wrong job type in bioProcessBackgroundJobs().");
|
||||
}
|
||||
@@ -406,34 +343,3 @@ void bioKillThreads(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bioPipeReadJobCompList(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
UNUSED(el);
|
||||
UNUSED(mask);
|
||||
UNUSED(privdata);
|
||||
|
||||
char buf[128];
|
||||
list *tmp_list = NULL;
|
||||
|
||||
while (read(fd, buf, sizeof(buf)) == sizeof(buf));
|
||||
|
||||
/* Handle event loop events if pipe was written from event loop API */
|
||||
pthread_mutex_lock(&bio_mutex_comp);
|
||||
if (listLength(bio_comp_list)) {
|
||||
tmp_list = bio_comp_list;
|
||||
bio_comp_list = listCreate();
|
||||
}
|
||||
pthread_mutex_unlock(&bio_mutex_comp);
|
||||
|
||||
if (!tmp_list) return;
|
||||
|
||||
/* callback to all job completions */
|
||||
while (listLength(tmp_list)) {
|
||||
listNode *ln = listFirst(tmp_list);
|
||||
bio_comp_item *rsp = ln->value;
|
||||
listDelNode(tmp_list, ln);
|
||||
rsp->func(rsp->arg);
|
||||
zfree(rsp);
|
||||
}
|
||||
listRelease(tmp_list);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __BIO_H
|
||||
#define __BIO_H
|
||||
|
||||
typedef void lazy_free_fn(void *args[]);
|
||||
typedef void comp_fn(uint64_t user_data);
|
||||
|
||||
typedef enum bio_worker_t {
|
||||
BIO_WORKER_CLOSE_FILE = 0,
|
||||
BIO_WORKER_AOF_FSYNC,
|
||||
BIO_WORKER_LAZY_FREE,
|
||||
BIO_WORKER_NUM
|
||||
} bio_worker_t;
|
||||
|
||||
/* Background job opcodes */
|
||||
typedef enum bio_job_type_t {
|
||||
BIO_CLOSE_FILE = 0, /* Deferred close(2) syscall. */
|
||||
BIO_AOF_FSYNC, /* Deferred AOF fsync. */
|
||||
BIO_LAZY_FREE, /* Deferred objects freeing. */
|
||||
BIO_CLOSE_AOF,
|
||||
BIO_COMP_RQ_CLOSE_FILE, /* Job completion request, registered on close-file worker's queue */
|
||||
BIO_COMP_RQ_AOF_FSYNC, /* Job completion request, registered on aof-fsync worker's queue */
|
||||
BIO_COMP_RQ_LAZY_FREE, /* Job completion request, registered on lazy-free worker's queue */
|
||||
BIO_NUM_OPS
|
||||
} bio_job_type_t;
|
||||
|
||||
/* Exported API */
|
||||
void bioInit(void);
|
||||
@@ -40,7 +41,14 @@ void bioCreateCloseJob(int fd, int need_fsync, int need_reclaim_cache);
|
||||
void bioCreateCloseAofJob(int fd, long long offset, int need_reclaim_cache);
|
||||
void bioCreateFsyncJob(int fd, long long offset, int need_reclaim_cache);
|
||||
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...);
|
||||
void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data);
|
||||
|
||||
/* Background job opcodes */
|
||||
enum {
|
||||
BIO_CLOSE_FILE = 0, /* Deferred close(2) syscall. */
|
||||
BIO_AOF_FSYNC, /* Deferred AOF fsync. */
|
||||
BIO_LAZY_FREE, /* Deferred objects freeing. */
|
||||
BIO_CLOSE_AOF, /* Deferred close for AOF files. */
|
||||
BIO_NUM_OPS
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+51
-53
@@ -1,10 +1,31 @@
|
||||
/* Bit operations.
|
||||
*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -781,12 +802,25 @@ void bitcountCommand(client *c) {
|
||||
int isbit = 0;
|
||||
unsigned char first_byte_neg_mask = 0, last_byte_neg_mask = 0;
|
||||
|
||||
/* Lookup, check for type, and return 0 for non existing keys. */
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,OBJ_STRING)) return;
|
||||
p = getObjectReadOnlyString(o,&strlen,llbuf);
|
||||
|
||||
/* Parse start/end range if any. */
|
||||
if (c->argc == 4 || c->argc == 5) {
|
||||
long long totlen = strlen;
|
||||
/* Make sure we will not overflow */
|
||||
serverAssert(totlen <= LLONG_MAX >> 3);
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK)
|
||||
return;
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK)
|
||||
return;
|
||||
/* Convert negative indexes */
|
||||
if (start < 0 && end < 0 && start > end) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
if (c->argc == 5) {
|
||||
if (!strcasecmp(c->argv[4]->ptr,"bit")) isbit = 1;
|
||||
else if (!strcasecmp(c->argv[4]->ptr,"byte")) isbit = 0;
|
||||
@@ -795,20 +829,6 @@ void bitcountCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Lookup, check for type. */
|
||||
o = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (checkType(c, o, OBJ_STRING)) return;
|
||||
p = getObjectReadOnlyString(o,&strlen,llbuf);
|
||||
long long totlen = strlen;
|
||||
|
||||
/* Make sure we will not overflow */
|
||||
serverAssert(totlen <= LLONG_MAX >> 3);
|
||||
|
||||
/* Convert negative indexes */
|
||||
if (start < 0 && end < 0 && start > end) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
if (isbit) totlen <<= 3;
|
||||
if (start < 0) start = totlen+start;
|
||||
if (end < 0) end = totlen+end;
|
||||
@@ -824,10 +844,6 @@ void bitcountCommand(client *c) {
|
||||
end >>= 3;
|
||||
}
|
||||
} else if (c->argc == 2) {
|
||||
/* Lookup, check for type. */
|
||||
o = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (checkType(c, o, OBJ_STRING)) return;
|
||||
p = getObjectReadOnlyString(o,&strlen,llbuf);
|
||||
/* The whole string. */
|
||||
start = 0;
|
||||
end = strlen-1;
|
||||
@@ -837,12 +853,6 @@ void bitcountCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return 0 for non existing keys. */
|
||||
if (o == NULL) {
|
||||
addReply(c, shared.czero);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Precondition: end >= 0 && end < strlen, so the only condition where
|
||||
* zero can be returned is: start > end. */
|
||||
if (start > end) {
|
||||
@@ -882,8 +892,21 @@ void bitposCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the key does not exist, from our point of view it is an infinite
|
||||
* array of 0 bits. If the user is looking for the first clear bit return 0,
|
||||
* If the user is looking for the first set bit, return -1. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
|
||||
addReplyLongLong(c, bit ? -1 : 0);
|
||||
return;
|
||||
}
|
||||
if (checkType(c,o,OBJ_STRING)) return;
|
||||
p = getObjectReadOnlyString(o,&strlen,llbuf);
|
||||
|
||||
/* Parse start/end range if any. */
|
||||
if (c->argc == 4 || c->argc == 5 || c->argc == 6) {
|
||||
long long totlen = strlen;
|
||||
/* Make sure we will not overflow */
|
||||
serverAssert(totlen <= LLONG_MAX >> 3);
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[3],&start,NULL) != C_OK)
|
||||
return;
|
||||
if (c->argc == 6) {
|
||||
@@ -898,22 +921,10 @@ void bitposCommand(client *c) {
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[4],&end,NULL) != C_OK)
|
||||
return;
|
||||
end_given = 1;
|
||||
}
|
||||
|
||||
/* Lookup, check for type. */
|
||||
o = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (checkType(c, o, OBJ_STRING)) return;
|
||||
p = getObjectReadOnlyString(o, &strlen, llbuf);
|
||||
|
||||
/* Make sure we will not overflow */
|
||||
long long totlen = strlen;
|
||||
serverAssert(totlen <= LLONG_MAX >> 3);
|
||||
|
||||
if (c->argc < 5) {
|
||||
} else {
|
||||
if (isbit) end = (totlen<<3) + 7;
|
||||
else end = totlen-1;
|
||||
}
|
||||
|
||||
if (isbit) totlen <<= 3;
|
||||
/* Convert negative indexes */
|
||||
if (start < 0) start = totlen+start;
|
||||
@@ -930,11 +941,6 @@ void bitposCommand(client *c) {
|
||||
end >>= 3;
|
||||
}
|
||||
} else if (c->argc == 3) {
|
||||
/* Lookup, check for type. */
|
||||
o = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (checkType(c,o,OBJ_STRING)) return;
|
||||
p = getObjectReadOnlyString(o,&strlen,llbuf);
|
||||
|
||||
/* The whole string. */
|
||||
start = 0;
|
||||
end = strlen-1;
|
||||
@@ -944,14 +950,6 @@ void bitposCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the key does not exist, from our point of view it is an infinite
|
||||
* array of 0 bits. If the user is looking for the first clear bit return 0,
|
||||
* If the user is looking for the first set bit, return -1. */
|
||||
if (o == NULL) {
|
||||
addReplyLongLong(c, bit ? -1 : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* For empty ranges (start > end) we return -1 as an empty range does
|
||||
* not contain a 0 nor a 1. */
|
||||
if (start > end) {
|
||||
|
||||
+44
-45
@@ -1,10 +1,31 @@
|
||||
/* blocked.c - generic support for blocking operations like BLPOP & WAIT.
|
||||
*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ---------------------------------------------------------------------------
|
||||
*
|
||||
@@ -68,7 +89,6 @@ void blockClient(client *c, int btype) {
|
||||
/* Master client should never be blocked unless pause or module */
|
||||
serverAssert(!(c->flags & CLIENT_MASTER &&
|
||||
btype != BLOCKED_MODULE &&
|
||||
btype != BLOCKED_LAZYFREE &&
|
||||
btype != BLOCKED_POSTPONE));
|
||||
|
||||
c->flags |= CLIENT_BLOCKED;
|
||||
@@ -176,8 +196,6 @@ void unblockClient(client *c, int queue_for_reprocessing) {
|
||||
c->postponed_list_node = NULL;
|
||||
} else if (c->bstate.btype == BLOCKED_SHUTDOWN) {
|
||||
/* No special cleanup. */
|
||||
} else if (c->bstate.btype == BLOCKED_LAZYFREE) {
|
||||
/* No special cleanup. */
|
||||
} else {
|
||||
serverPanic("Unknown btype in unblockClient().");
|
||||
}
|
||||
@@ -205,31 +223,11 @@ void unblockClient(client *c, int queue_for_reprocessing) {
|
||||
if (queue_for_reprocessing) queueClientForReprocessing(c);
|
||||
}
|
||||
|
||||
/* Check if the specified client can be safely timed out using
|
||||
* unblockClientOnTimeout(). */
|
||||
int blockedClientMayTimeout(client *c) {
|
||||
if (c->bstate.btype == BLOCKED_MODULE) {
|
||||
return moduleBlockedClientMayTimeout(c);
|
||||
}
|
||||
|
||||
if (c->bstate.btype == BLOCKED_LIST ||
|
||||
c->bstate.btype == BLOCKED_ZSET ||
|
||||
c->bstate.btype == BLOCKED_STREAM ||
|
||||
c->bstate.btype == BLOCKED_WAIT ||
|
||||
c->bstate.btype == BLOCKED_WAITAOF)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This function gets called when a blocked client timed out in order to
|
||||
* send it a reply of some kind. After this function is called,
|
||||
* unblockClient() will be called with the same client as argument. */
|
||||
void replyToBlockedClientTimedOut(client *c) {
|
||||
if (c->bstate.btype == BLOCKED_LAZYFREE) {
|
||||
addReply(c, shared.ok); /* No reason lazy-free to fail */
|
||||
} else if (c->bstate.btype == BLOCKED_LIST ||
|
||||
if (c->bstate.btype == BLOCKED_LIST ||
|
||||
c->bstate.btype == BLOCKED_ZSET ||
|
||||
c->bstate.btype == BLOCKED_STREAM) {
|
||||
addReplyNullArray(c);
|
||||
@@ -286,17 +284,9 @@ void disconnectAllBlockedClients(void) {
|
||||
if (c->bstate.btype == BLOCKED_POSTPONE)
|
||||
continue;
|
||||
|
||||
if (c->bstate.btype == BLOCKED_LAZYFREE) {
|
||||
addReply(c, shared.ok); /* No reason lazy-free to fail */
|
||||
updateStatsOnUnblock(c, 0, 0, 0);
|
||||
c->flags &= ~CLIENT_PENDING_COMMAND;
|
||||
unblockClient(c, 1);
|
||||
} else {
|
||||
|
||||
unblockClientOnError(c,
|
||||
"-UNBLOCKED force unblock from blocking operation, "
|
||||
"instance state changed (master -> replica?)");
|
||||
}
|
||||
unblockClientOnError(c,
|
||||
"-UNBLOCKED force unblock from blocking operation, "
|
||||
"instance state changed (master -> replica?)");
|
||||
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||
}
|
||||
}
|
||||
@@ -380,7 +370,7 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
list *l;
|
||||
int j;
|
||||
|
||||
if (!(c->flags & CLIENT_REEXECUTING_COMMAND)) {
|
||||
if (!(c->flags & CLIENT_REPROCESSING_COMMAND)) {
|
||||
/* If the client is re-processing the command, we do not set the timeout
|
||||
* because we need to retain the client's original timeout. */
|
||||
c->bstate.timeout = timeout;
|
||||
@@ -666,7 +656,6 @@ static void unblockClientOnKey(client *c, robj *key) {
|
||||
* we need to re process the command again */
|
||||
if (c->flags & CLIENT_PENDING_COMMAND) {
|
||||
c->flags &= ~CLIENT_PENDING_COMMAND;
|
||||
c->flags |= CLIENT_REEXECUTING_COMMAND;
|
||||
/* We want the command processing and the unblock handler (see RM_Call 'K' option)
|
||||
* to run atomically, this is why we must enter the execution unit here before
|
||||
* running the command, and exit the execution unit after calling the unblock handler (if exists).
|
||||
@@ -685,8 +674,6 @@ static void unblockClientOnKey(client *c, robj *key) {
|
||||
}
|
||||
exitExecutionUnit();
|
||||
afterCommand(c);
|
||||
/* Clear the CLIENT_REEXECUTING_COMMAND flag after the proc is executed. */
|
||||
c->flags &= ~CLIENT_REEXECUTING_COMMAND;
|
||||
server.current_client = old_client;
|
||||
}
|
||||
}
|
||||
@@ -720,9 +707,6 @@ static void moduleUnblockClientOnKey(client *c, robj *key) {
|
||||
* we want to remove the pending flag to indicate we already responded to the
|
||||
* command with timeout reply. */
|
||||
void unblockClientOnTimeout(client *c) {
|
||||
/* The client has been unlocked (in the moduleUnblocked list), return ASAP. */
|
||||
if (c->bstate.btype == BLOCKED_MODULE && isModuleClientUnblocked(c)) return;
|
||||
|
||||
replyToBlockedClientTimedOut(c);
|
||||
if (c->flags & CLIENT_PENDING_COMMAND)
|
||||
c->flags &= ~CLIENT_PENDING_COMMAND;
|
||||
@@ -740,6 +724,21 @@ void unblockClientOnError(client *c, const char *err_str) {
|
||||
unblockClient(c, 1);
|
||||
}
|
||||
|
||||
/* sets blocking_keys to the total number of keys which has at least one client blocked on them
|
||||
* sets blocking_keys_on_nokey to the total number of keys which has at least one client
|
||||
* blocked on them to be written or deleted */
|
||||
void totalNumberOfBlockingKeys(unsigned long *blocking_keys, unsigned long *bloking_keys_on_nokey) {
|
||||
unsigned long bkeys=0, bkeys_on_nokey=0;
|
||||
for (int j = 0; j < server.dbnum; j++) {
|
||||
bkeys += dictSize(server.db[j].blocking_keys);
|
||||
bkeys_on_nokey += dictSize(server.db[j].blocking_keys_unblock_on_nokey);
|
||||
}
|
||||
if (blocking_keys)
|
||||
*blocking_keys = bkeys;
|
||||
if (bloking_keys_on_nokey)
|
||||
*bloking_keys_on_nokey = bkeys_on_nokey;
|
||||
}
|
||||
|
||||
void blockedBeforeSleep(void) {
|
||||
/* Handle precise timeouts of blocked clients. */
|
||||
handleBlockedClientsTimeout();
|
||||
|
||||
+24
-3
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2021, Redis Labs Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
+24
-3
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2021, Redis Labs Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef SRC_CALL_REPLY_H_
|
||||
|
||||
+24
-3
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2016-Present, Redis Ltd.
|
||||
* Copyright (c) 2016, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
+24
-39
@@ -1,16 +1,35 @@
|
||||
/* CLI (command line interface) common methods
|
||||
*
|
||||
* Copyright (c) 2020-Present, Redis Ltd.
|
||||
* Copyright (c) 2020, Redis Labs
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "cli_common.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
@@ -29,9 +48,6 @@
|
||||
|
||||
#define UNUSED(V) ((void) V)
|
||||
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
|
||||
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
|
||||
* not building with TLS support.
|
||||
*/
|
||||
@@ -390,34 +406,3 @@ sds escapeJsonString(sds s, const char *p, size_t len) {
|
||||
}
|
||||
return sdscatlen(s,"\"",1);
|
||||
}
|
||||
|
||||
sds cliVersion(void) {
|
||||
sds version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
|
||||
|
||||
/* Add git commit and working tree status when available. */
|
||||
if (strtoll(redisGitSHA1(),NULL,16)) {
|
||||
version = sdscatprintf(version, " (git:%s", redisGitSHA1());
|
||||
if (strtoll(redisGitDirty(),NULL,10))
|
||||
version = sdscatprintf(version, "-dirty");
|
||||
version = sdscat(version, ")");
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/* This is a wrapper to call redisConnect or redisConnectWithTimeout. */
|
||||
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv) {
|
||||
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
|
||||
return redisConnect(ip, port);
|
||||
} else {
|
||||
return redisConnectWithTimeout(ip, port, tv);
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a wrapper to call redisConnectUnix or redisConnectUnixWithTimeout. */
|
||||
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv) {
|
||||
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
|
||||
return redisConnectUnix(path);
|
||||
} else {
|
||||
return redisConnectUnixWithTimeout(path, tv);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,4 @@ void freeCliConnInfo(cliConnInfo connInfo);
|
||||
|
||||
sds escapeJsonString(sds s, const char *p, size_t len);
|
||||
|
||||
sds cliVersion(void);
|
||||
|
||||
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv);
|
||||
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv);
|
||||
|
||||
#endif /* __CLICOMMON_H */
|
||||
|
||||
+6803
-423
File diff suppressed because it is too large
Load Diff
+405
-101
@@ -2,15 +2,22 @@
|
||||
#define __CLUSTER_H
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Redis cluster exported API.
|
||||
* Redis cluster data structures, defines, exported API.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#define CLUSTER_SLOT_MASK_BITS 14 /* Number of bits used for slot id. */
|
||||
#define CLUSTER_SLOTS (1<<CLUSTER_SLOT_MASK_BITS) /* Total number of slots in cluster mode, which is 16384. */
|
||||
#define CLUSTER_SLOT_MASK ((unsigned long long)(CLUSTER_SLOTS - 1)) /* Bit mask for slot id stored in LSB. */
|
||||
#define CLUSTER_SLOTS 16384
|
||||
#define CLUSTER_OK 0 /* Everything looks ok */
|
||||
#define CLUSTER_FAIL 1 /* The cluster can't work */
|
||||
#define CLUSTER_NAMELEN 40 /* sha1 hex length */
|
||||
#define CLUSTER_PORT_INCR 10000 /* Cluster port = baseport + PORT_INCR */
|
||||
|
||||
/* The following defines are amount of time, sometimes expressed as
|
||||
* multiplicators of the node timeout value (when ending with MULT). */
|
||||
#define CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
|
||||
#define CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
|
||||
#define CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */
|
||||
#define CLUSTER_MF_PAUSE_MULT 2 /* Master pause manual failover mult. */
|
||||
#define CLUSTER_SLAVE_MIGRATION_DELAY 5000 /* Delay for slave migration. */
|
||||
|
||||
/* Redirection errors returned by getNodeByQuery(). */
|
||||
#define CLUSTER_REDIR_NONE 0 /* Node can serve the request. */
|
||||
@@ -22,8 +29,79 @@
|
||||
#define CLUSTER_REDIR_DOWN_UNBOUND 6 /* -CLUSTERDOWN, unbound slot. */
|
||||
#define CLUSTER_REDIR_DOWN_RO_STATE 7 /* -CLUSTERDOWN, allow reads. */
|
||||
|
||||
typedef struct _clusterNode clusterNode;
|
||||
struct clusterState;
|
||||
struct clusterNode;
|
||||
|
||||
/* clusterLink encapsulates everything needed to talk with a remote node. */
|
||||
typedef struct clusterLink {
|
||||
mstime_t ctime; /* Link creation time */
|
||||
connection *conn; /* Connection to remote node */
|
||||
list *send_msg_queue; /* List of messages to be sent */
|
||||
size_t head_msg_send_offset; /* Number of bytes already sent of message at head of queue */
|
||||
unsigned long long send_msg_queue_mem; /* Memory in bytes used by message queue */
|
||||
char *rcvbuf; /* Packet reception buffer */
|
||||
size_t rcvbuf_len; /* Used size of rcvbuf */
|
||||
size_t rcvbuf_alloc; /* Allocated size of rcvbuf */
|
||||
struct clusterNode *node; /* Node related to this link. Initialized to NULL when unknown */
|
||||
int inbound; /* 1 if this link is an inbound link accepted from the related node */
|
||||
} clusterLink;
|
||||
|
||||
/* Cluster node flags and macros. */
|
||||
#define CLUSTER_NODE_MASTER 1 /* The node is a master */
|
||||
#define CLUSTER_NODE_SLAVE 2 /* The node is a slave */
|
||||
#define CLUSTER_NODE_PFAIL 4 /* Failure? Need acknowledge */
|
||||
#define CLUSTER_NODE_FAIL 8 /* The node is believed to be malfunctioning */
|
||||
#define CLUSTER_NODE_MYSELF 16 /* This node is myself */
|
||||
#define CLUSTER_NODE_HANDSHAKE 32 /* We have still to exchange the first ping */
|
||||
#define CLUSTER_NODE_NOADDR 64 /* We don't know the address of this node */
|
||||
#define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */
|
||||
#define CLUSTER_NODE_MIGRATE_TO 256 /* Master eligible for replica migration. */
|
||||
#define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failover. */
|
||||
#define CLUSTER_NODE_EXTENSIONS_SUPPORTED 1024 /* This node supports extensions. */
|
||||
#define CLUSTER_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
|
||||
#define nodeIsMaster(n) ((n)->flags & CLUSTER_NODE_MASTER)
|
||||
#define nodeIsSlave(n) ((n)->flags & CLUSTER_NODE_SLAVE)
|
||||
#define nodeInHandshake(n) ((n)->flags & CLUSTER_NODE_HANDSHAKE)
|
||||
#define nodeHasAddr(n) (!((n)->flags & CLUSTER_NODE_NOADDR))
|
||||
#define nodeWithoutAddr(n) ((n)->flags & CLUSTER_NODE_NOADDR)
|
||||
#define nodeTimedOut(n) ((n)->flags & CLUSTER_NODE_PFAIL)
|
||||
#define nodeFailed(n) ((n)->flags & CLUSTER_NODE_FAIL)
|
||||
#define nodeCantFailover(n) ((n)->flags & CLUSTER_NODE_NOFAILOVER)
|
||||
#define nodeSupportsExtensions(n) ((n)->flags & CLUSTER_NODE_EXTENSIONS_SUPPORTED)
|
||||
|
||||
/* Reasons why a slave is not able to failover. */
|
||||
#define CLUSTER_CANT_FAILOVER_NONE 0
|
||||
#define CLUSTER_CANT_FAILOVER_DATA_AGE 1
|
||||
#define CLUSTER_CANT_FAILOVER_WAITING_DELAY 2
|
||||
#define CLUSTER_CANT_FAILOVER_EXPIRED 3
|
||||
#define CLUSTER_CANT_FAILOVER_WAITING_VOTES 4
|
||||
#define CLUSTER_CANT_FAILOVER_RELOG_PERIOD (10) /* seconds. */
|
||||
|
||||
/* clusterState todo_before_sleep flags. */
|
||||
#define CLUSTER_TODO_HANDLE_FAILOVER (1<<0)
|
||||
#define CLUSTER_TODO_UPDATE_STATE (1<<1)
|
||||
#define CLUSTER_TODO_SAVE_CONFIG (1<<2)
|
||||
#define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
|
||||
#define CLUSTER_TODO_HANDLE_MANUALFAILOVER (1<<4)
|
||||
|
||||
/* Message types.
|
||||
*
|
||||
* Note that the PING, PONG and MEET messages are actually the same exact
|
||||
* kind of packet. PONG is the reply to ping, in the exact format as a PING,
|
||||
* while MEET is a special PING that forces the receiver to add the sender
|
||||
* as a node (if it is not already in the list). */
|
||||
#define CLUSTERMSG_TYPE_PING 0 /* Ping */
|
||||
#define CLUSTERMSG_TYPE_PONG 1 /* Pong (reply to Ping) */
|
||||
#define CLUSTERMSG_TYPE_MEET 2 /* Meet "let's join" message */
|
||||
#define CLUSTERMSG_TYPE_FAIL 3 /* Mark node xxx as failing */
|
||||
#define CLUSTERMSG_TYPE_PUBLISH 4 /* Pub/Sub Publish propagation */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* May I failover? */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6 /* Yes, you have my vote */
|
||||
#define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */
|
||||
#define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */
|
||||
#define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */
|
||||
#define CLUSTERMSG_TYPE_PUBLISHSHARD 10 /* Pub/Sub Publish shard propagation */
|
||||
#define CLUSTERMSG_TYPE_COUNT 11 /* Total number of message types. */
|
||||
|
||||
/* Flags that a module can set in order to prevent certain Redis Cluster
|
||||
* features to be enabled. Useful when implementing a different distributed
|
||||
@@ -32,114 +110,340 @@ struct clusterState;
|
||||
#define CLUSTER_MODULE_FLAG_NO_FAILOVER (1<<1)
|
||||
#define CLUSTER_MODULE_FLAG_NO_REDIRECTION (1<<2)
|
||||
|
||||
/* ---------------------- API exported outside cluster.c -------------------- */
|
||||
/* This structure represent elements of node->fail_reports. */
|
||||
typedef struct clusterNodeFailReport {
|
||||
struct clusterNode *node; /* Node reporting the failure condition. */
|
||||
mstime_t time; /* Time of the last report from this node. */
|
||||
} clusterNodeFailReport;
|
||||
|
||||
/* We have 16384 hash slots. The hash slot of a given key is obtained
|
||||
* as the least significant 14 bits of the crc16 of the key.
|
||||
typedef struct clusterNode {
|
||||
mstime_t ctime; /* Node object creation time. */
|
||||
char name[CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
|
||||
char shard_id[CLUSTER_NAMELEN]; /* shard id, hex string, sha1-size */
|
||||
int flags; /* CLUSTER_NODE_... */
|
||||
uint64_t configEpoch; /* Last configEpoch observed for this node */
|
||||
unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
|
||||
uint16_t *slot_info_pairs; /* Slots info represented as (start/end) pair (consecutive index). */
|
||||
int slot_info_pairs_count; /* Used number of slots in slot_info_pairs */
|
||||
int numslots; /* Number of slots handled by this node */
|
||||
int numslaves; /* Number of slave nodes, if this is a master */
|
||||
struct clusterNode **slaves; /* pointers to slave nodes */
|
||||
struct clusterNode *slaveof; /* pointer to the master node. Note that it
|
||||
may be NULL even if the node is a slave
|
||||
if we don't have the master node in our
|
||||
tables. */
|
||||
unsigned long long last_in_ping_gossip; /* The number of the last carried in the ping gossip section */
|
||||
mstime_t ping_sent; /* Unix time we sent latest ping */
|
||||
mstime_t pong_received; /* Unix time we received the pong */
|
||||
mstime_t data_received; /* Unix time we received any data */
|
||||
mstime_t fail_time; /* Unix time when FAIL flag was set */
|
||||
mstime_t voted_time; /* Last time we voted for a slave of this master */
|
||||
mstime_t repl_offset_time; /* Unix time we received offset for this node */
|
||||
mstime_t orphaned_time; /* Starting time of orphaned master condition */
|
||||
long long repl_offset; /* Last known repl offset for this node. */
|
||||
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
|
||||
sds hostname; /* The known hostname for this node */
|
||||
sds human_nodename; /* The known human readable nodename for this node */
|
||||
int tcp_port; /* Latest known clients TCP port. */
|
||||
int tls_port; /* Latest known clients TLS port */
|
||||
int cport; /* Latest known cluster port of this node. */
|
||||
clusterLink *link; /* TCP/IP link established toward this node */
|
||||
clusterLink *inbound_link; /* TCP/IP link accepted from this node */
|
||||
list *fail_reports; /* List of nodes signaling this as failing */
|
||||
} clusterNode;
|
||||
|
||||
/* Slot to keys for a single slot. The keys in the same slot are linked together
|
||||
* using dictEntry metadata. */
|
||||
typedef struct slotToKeys {
|
||||
uint64_t count; /* Number of keys in the slot. */
|
||||
dictEntry *head; /* The first key-value entry in the slot. */
|
||||
} slotToKeys;
|
||||
|
||||
/* Slot to keys mapping for all slots, opaque outside this file. */
|
||||
struct clusterSlotToKeyMapping {
|
||||
slotToKeys by_slot[CLUSTER_SLOTS];
|
||||
};
|
||||
|
||||
/* Dict entry metadata for cluster mode, used for the Slot to Key API to form a
|
||||
* linked list of the entries belonging to the same slot. */
|
||||
typedef struct clusterDictEntryMetadata {
|
||||
dictEntry *prev; /* Prev entry with key in the same slot */
|
||||
dictEntry *next; /* Next entry with key in the same slot */
|
||||
} clusterDictEntryMetadata;
|
||||
|
||||
typedef struct {
|
||||
redisDb *db; /* A link back to the db this dict belongs to */
|
||||
} clusterDictMetadata;
|
||||
|
||||
typedef struct clusterState {
|
||||
clusterNode *myself; /* This node */
|
||||
uint64_t currentEpoch;
|
||||
int state; /* CLUSTER_OK, CLUSTER_FAIL, ... */
|
||||
int size; /* Num of master nodes with at least one slot */
|
||||
dict *nodes; /* Hash table of name -> clusterNode structures */
|
||||
dict *shards; /* Hash table of shard_id -> list (of nodes) structures */
|
||||
dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */
|
||||
clusterNode *migrating_slots_to[CLUSTER_SLOTS];
|
||||
clusterNode *importing_slots_from[CLUSTER_SLOTS];
|
||||
clusterNode *slots[CLUSTER_SLOTS];
|
||||
rax *slots_to_channels;
|
||||
/* The following fields are used to take the slave state on elections. */
|
||||
mstime_t failover_auth_time; /* Time of previous or next election. */
|
||||
int failover_auth_count; /* Number of votes received so far. */
|
||||
int failover_auth_sent; /* True if we already asked for votes. */
|
||||
int failover_auth_rank; /* This slave rank for current auth request. */
|
||||
uint64_t failover_auth_epoch; /* Epoch of the current election. */
|
||||
int cant_failover_reason; /* Why a slave is currently not able to
|
||||
failover. See the CANT_FAILOVER_* macros. */
|
||||
/* Manual failover state in common. */
|
||||
mstime_t mf_end; /* Manual failover time limit (ms unixtime).
|
||||
It is zero if there is no MF in progress. */
|
||||
/* Manual failover state of master. */
|
||||
clusterNode *mf_slave; /* Slave performing the manual failover. */
|
||||
/* Manual failover state of slave. */
|
||||
long long mf_master_offset; /* Master offset the slave needs to start MF
|
||||
or -1 if still not received. */
|
||||
int mf_can_start; /* If non-zero signal that the manual failover
|
||||
can start requesting masters vote. */
|
||||
/* The following fields are used by masters to take state on elections. */
|
||||
uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */
|
||||
int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
|
||||
/* Stats */
|
||||
/* Messages received and sent by type. */
|
||||
long long stats_bus_messages_sent[CLUSTERMSG_TYPE_COUNT];
|
||||
long long stats_bus_messages_received[CLUSTERMSG_TYPE_COUNT];
|
||||
long long stats_pfail_nodes; /* Number of nodes in PFAIL status,
|
||||
excluding nodes without address. */
|
||||
unsigned long long stat_cluster_links_buffer_limit_exceeded; /* Total number of cluster links freed due to exceeding buffer limit */
|
||||
|
||||
/* Bit map for slots that are no longer claimed by the owner in cluster PING
|
||||
* messages. During slot migration, the owner will stop claiming the slot after
|
||||
* the ownership transfer. Set the bit corresponding to the slot when a node
|
||||
* stops claiming the slot. This prevents spreading incorrect information (that
|
||||
* source still owns the slot) using UPDATE messages. */
|
||||
unsigned char owner_not_claiming_slot[CLUSTER_SLOTS / 8];
|
||||
} clusterState;
|
||||
|
||||
/* Redis cluster messages header */
|
||||
|
||||
/* Initially we don't know our "name", but we'll find it once we connect
|
||||
* to the first node, using the getsockname() function. Then we'll use this
|
||||
* address for all the next messages. */
|
||||
typedef struct {
|
||||
char nodename[CLUSTER_NAMELEN];
|
||||
uint32_t ping_sent;
|
||||
uint32_t pong_received;
|
||||
char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */
|
||||
uint16_t port; /* primary port last time it was seen */
|
||||
uint16_t cport; /* cluster port last time it was seen */
|
||||
uint16_t flags; /* node->flags copy */
|
||||
uint16_t pport; /* secondary port last time it was seen */
|
||||
uint16_t notused1;
|
||||
} clusterMsgDataGossip;
|
||||
|
||||
typedef struct {
|
||||
char nodename[CLUSTER_NAMELEN];
|
||||
} clusterMsgDataFail;
|
||||
|
||||
typedef struct {
|
||||
uint32_t channel_len;
|
||||
uint32_t message_len;
|
||||
unsigned char bulk_data[8]; /* 8 bytes just as placeholder. */
|
||||
} clusterMsgDataPublish;
|
||||
|
||||
typedef struct {
|
||||
uint64_t configEpoch; /* Config epoch of the specified instance. */
|
||||
char nodename[CLUSTER_NAMELEN]; /* Name of the slots owner. */
|
||||
unsigned char slots[CLUSTER_SLOTS/8]; /* Slots bitmap. */
|
||||
} clusterMsgDataUpdate;
|
||||
|
||||
typedef struct {
|
||||
uint64_t module_id; /* ID of the sender module. */
|
||||
uint32_t len; /* ID of the sender module. */
|
||||
uint8_t type; /* Type from 0 to 255. */
|
||||
unsigned char bulk_data[3]; /* 3 bytes just as placeholder. */
|
||||
} clusterMsgModule;
|
||||
|
||||
/* The cluster supports optional extension messages that can be sent
|
||||
* along with ping/pong/meet messages to give additional info in a
|
||||
* consistent manner. */
|
||||
typedef enum {
|
||||
CLUSTERMSG_EXT_TYPE_HOSTNAME,
|
||||
CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME,
|
||||
CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE,
|
||||
CLUSTERMSG_EXT_TYPE_SHARDID,
|
||||
} clusterMsgPingtypes;
|
||||
|
||||
/* Helper function for making sure extensions are eight byte aligned. */
|
||||
#define EIGHT_BYTE_ALIGN(size) ((((size) + 7) / 8) * 8)
|
||||
|
||||
typedef struct {
|
||||
char hostname[1]; /* The announced hostname, ends with \0. */
|
||||
} clusterMsgPingExtHostname;
|
||||
|
||||
typedef struct {
|
||||
char human_nodename[1]; /* The announced nodename, ends with \0. */
|
||||
} clusterMsgPingExtHumanNodename;
|
||||
|
||||
typedef struct {
|
||||
char name[CLUSTER_NAMELEN]; /* Node name. */
|
||||
uint64_t ttl; /* Remaining time to blacklist the node, in seconds. */
|
||||
} clusterMsgPingExtForgottenNode;
|
||||
|
||||
static_assert(sizeof(clusterMsgPingExtForgottenNode) % 8 == 0, "");
|
||||
|
||||
typedef struct {
|
||||
char shard_id[CLUSTER_NAMELEN]; /* The shard_id, 40 bytes fixed. */
|
||||
} clusterMsgPingExtShardId;
|
||||
|
||||
typedef struct {
|
||||
uint32_t length; /* Total length of this extension message (including this header) */
|
||||
uint16_t type; /* Type of this extension message (see clusterMsgPingExtTypes) */
|
||||
uint16_t unused; /* 16 bits of padding to make this structure 8 byte aligned. */
|
||||
union {
|
||||
clusterMsgPingExtHostname hostname;
|
||||
clusterMsgPingExtHumanNodename human_nodename;
|
||||
clusterMsgPingExtForgottenNode forgotten_node;
|
||||
clusterMsgPingExtShardId shard_id;
|
||||
} ext[]; /* Actual extension information, formatted so that the data is 8
|
||||
* byte aligned, regardless of its content. */
|
||||
} clusterMsgPingExt;
|
||||
|
||||
union clusterMsgData {
|
||||
/* PING, MEET and PONG */
|
||||
struct {
|
||||
/* Array of N clusterMsgDataGossip structures */
|
||||
clusterMsgDataGossip gossip[1];
|
||||
/* Extension data that can optionally be sent for ping/meet/pong
|
||||
* messages. We can't explicitly define them here though, since
|
||||
* the gossip array isn't the real length of the gossip data. */
|
||||
} ping;
|
||||
|
||||
/* FAIL */
|
||||
struct {
|
||||
clusterMsgDataFail about;
|
||||
} fail;
|
||||
|
||||
/* PUBLISH */
|
||||
struct {
|
||||
clusterMsgDataPublish msg;
|
||||
} publish;
|
||||
|
||||
/* UPDATE */
|
||||
struct {
|
||||
clusterMsgDataUpdate nodecfg;
|
||||
} update;
|
||||
|
||||
/* MODULE */
|
||||
struct {
|
||||
clusterMsgModule msg;
|
||||
} module;
|
||||
};
|
||||
|
||||
#define CLUSTER_PROTO_VER 1 /* Cluster bus protocol version. */
|
||||
|
||||
typedef struct {
|
||||
char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */
|
||||
uint32_t totlen; /* Total length of this message */
|
||||
uint16_t ver; /* Protocol version, currently set to 1. */
|
||||
uint16_t port; /* Primary port number (TCP or TLS). */
|
||||
uint16_t type; /* Message type */
|
||||
uint16_t count; /* Only used for some kind of messages. */
|
||||
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
|
||||
uint64_t configEpoch; /* The config epoch if it's a master, or the last
|
||||
epoch advertised by its master if it is a
|
||||
slave. */
|
||||
uint64_t offset; /* Master replication offset if node is a master or
|
||||
processed replication offset if node is a slave. */
|
||||
char sender[CLUSTER_NAMELEN]; /* Name of the sender node */
|
||||
unsigned char myslots[CLUSTER_SLOTS/8];
|
||||
char slaveof[CLUSTER_NAMELEN];
|
||||
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
|
||||
uint16_t extensions; /* Number of extensions sent along with this packet. */
|
||||
char notused1[30]; /* 30 bytes reserved for future usage. */
|
||||
uint16_t pport; /* Secondary port number: if primary port is TCP port, this is
|
||||
TLS port, and if primary port is TLS port, this is TCP port.*/
|
||||
uint16_t cport; /* Sender TCP cluster bus port */
|
||||
uint16_t flags; /* Sender node flags */
|
||||
unsigned char state; /* Cluster state from the POV of the sender */
|
||||
unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */
|
||||
union clusterMsgData data;
|
||||
} clusterMsg;
|
||||
|
||||
/* clusterMsg defines the gossip wire protocol exchanged among Redis cluster
|
||||
* members, which can be running different versions of redis-server bits,
|
||||
* especially during cluster rolling upgrades.
|
||||
*
|
||||
* However, if the key contains the {...} pattern, only the part between
|
||||
* { and } is hashed. This may be useful in the future to force certain
|
||||
* keys to be in the same node (assuming no resharding is in progress). */
|
||||
static inline unsigned int keyHashSlot(char *key, int keylen) {
|
||||
int s, e; /* start-end indexes of { and } */
|
||||
* Therefore, fields in this struct should remain at the same offset from
|
||||
* release to release. The static asserts below ensures that incompatible
|
||||
* changes in clusterMsg be caught at compile time.
|
||||
*/
|
||||
|
||||
for (s = 0; s < keylen; s++)
|
||||
if (key[s] == '{') break;
|
||||
static_assert(offsetof(clusterMsg, sig) == 0, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, totlen) == 4, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, ver) == 8, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, port) == 10, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, type) == 12, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, count) == 14, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, currentEpoch) == 16, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, configEpoch) == 24, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, offset) == 32, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, sender) == 40, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, myslots) == 80, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, slaveof) == 2128, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, myip) == 2168, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, extensions) == 2214, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, notused1) == 2216, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, pport) == 2246, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, cport) == 2248, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, flags) == 2250, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, state) == 2252, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, mflags) == 2253, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, data) == 2256, "unexpected field offset");
|
||||
|
||||
/* No '{' ? Hash the whole key. This is the base case. */
|
||||
if (likely(s == keylen)) return crc16(key,keylen) & 0x3FFF;
|
||||
#define CLUSTERMSG_MIN_LEN (sizeof(clusterMsg)-sizeof(union clusterMsgData))
|
||||
|
||||
/* '{' found? Check if we have the corresponding '}'. */
|
||||
for (e = s+1; e < keylen; e++)
|
||||
if (key[e] == '}') break;
|
||||
/* Message flags better specify the packet content or are used to
|
||||
* provide some information about the node state. */
|
||||
#define CLUSTERMSG_FLAG0_PAUSED (1<<0) /* Master paused for manual failover. */
|
||||
#define CLUSTERMSG_FLAG0_FORCEACK (1<<1) /* Give ACK to AUTH_REQUEST even if
|
||||
master is up. */
|
||||
#define CLUSTERMSG_FLAG0_EXT_DATA (1<<2) /* Message contains extension data */
|
||||
|
||||
/* No '}' or nothing between {} ? Hash the whole key. */
|
||||
if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
|
||||
|
||||
/* If we are here there is both a { and a } on its right. Hash
|
||||
* what is in the middle between { and }. */
|
||||
return crc16(key+s+1,e-s-1) & 0x3FFF;
|
||||
}
|
||||
|
||||
/* functions requiring mechanism specific implementations */
|
||||
/* ---------------------- API exported outside cluster.c -------------------- */
|
||||
void clusterInit(void);
|
||||
void clusterInitLast(void);
|
||||
void clusterInitListeners(void);
|
||||
void clusterCron(void);
|
||||
void clusterBeforeSleep(void);
|
||||
int verifyClusterConfigWithData(void);
|
||||
|
||||
int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, const char *payload, uint32_t len);
|
||||
|
||||
void clusterUpdateMyselfFlags(void);
|
||||
void clusterUpdateMyselfIp(void);
|
||||
void clusterUpdateMyselfHostname(void);
|
||||
void clusterUpdateMyselfAnnouncedPorts(void);
|
||||
void clusterUpdateMyselfHumanNodename(void);
|
||||
|
||||
void clusterPropagatePublish(robj *channel, robj *message, int sharded);
|
||||
|
||||
unsigned long getClusterConnectionsCount(void);
|
||||
int isClusterHealthy(void);
|
||||
|
||||
sds clusterGenNodesDescription(client *c, int filter, int tls_primary);
|
||||
sds genClusterInfoString(void);
|
||||
/* handle implementation specific debug cluster commands. Return 1 if handled, 0 otherwise. */
|
||||
int handleDebugClusterCommand(client *c);
|
||||
const char **clusterDebugCommandExtendedHelp(void);
|
||||
/* handle implementation specific cluster commands. Return 1 if handled, 0 otherwise. */
|
||||
int clusterCommandSpecial(client *c);
|
||||
const char** clusterCommandExtendedHelp(void);
|
||||
|
||||
int clusterAllowFailoverCmd(client *c);
|
||||
void clusterPromoteSelfToMaster(void);
|
||||
int clusterManualFailoverTimeLimit(void);
|
||||
|
||||
void clusterCommandSlots(client * c);
|
||||
void clusterCommandMyId(client *c);
|
||||
void clusterCommandMyShardId(client *c);
|
||||
void clusterCommandShards(client *c);
|
||||
sds clusterGenNodeDescription(client *c, clusterNode *node, int tls_primary);
|
||||
|
||||
int clusterNodeCoversSlot(clusterNode *n, int slot);
|
||||
int getNodeDefaultClientPort(clusterNode *n);
|
||||
int clusterNodeIsMyself(clusterNode *n);
|
||||
clusterNode *getMyClusterNode(void);
|
||||
char *getMyClusterId(void);
|
||||
int getClusterSize(void);
|
||||
int getMyShardSlotCount(void);
|
||||
int handleDebugClusterCommand(client *c);
|
||||
int clusterNodePending(clusterNode *node);
|
||||
int clusterNodeIsMaster(clusterNode *n);
|
||||
char **getClusterNodesList(size_t *numnodes);
|
||||
int clusterNodeIsMaster(clusterNode *n);
|
||||
char *clusterNodeIp(clusterNode *node);
|
||||
int clusterNodeIsSlave(clusterNode *node);
|
||||
clusterNode *clusterNodeGetSlaveof(clusterNode *node);
|
||||
clusterNode *clusterNodeGetMaster(clusterNode *node);
|
||||
char *clusterNodeGetName(clusterNode *node);
|
||||
int clusterNodeTimedOut(clusterNode *node);
|
||||
int clusterNodeIsFailing(clusterNode *node);
|
||||
int clusterNodeIsNoFailover(clusterNode *node);
|
||||
char *clusterNodeGetShardId(clusterNode *node);
|
||||
int clusterNodeNumSlaves(clusterNode *node);
|
||||
clusterNode *clusterNodeGetSlave(clusterNode *node, int slave_idx);
|
||||
clusterNode *getMigratingSlotDest(int slot);
|
||||
clusterNode *getImportingSlotSource(int slot);
|
||||
clusterNode *getNodeBySlot(int slot);
|
||||
int clusterNodeClientPort(clusterNode *n, int use_tls);
|
||||
char *clusterNodeHostname(clusterNode *node);
|
||||
const char *clusterNodePreferredEndpoint(clusterNode *n);
|
||||
long long clusterNodeReplOffset(clusterNode *node);
|
||||
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);
|
||||
int verifyClusterNodeId(const char *name, int length);
|
||||
clusterNode *clusterLookupNode(const char *name, int length);
|
||||
|
||||
/* functions with shared implementations */
|
||||
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, uint64_t cmd_flags, int *error_code);
|
||||
int clusterRedirectBlockedClientIfNeeded(client *c);
|
||||
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code);
|
||||
void migrateCloseTimedoutSockets(void);
|
||||
int patternHashSlot(char *pattern, int length);
|
||||
int verifyClusterConfigWithData(void);
|
||||
unsigned long getClusterConnectionsCount(void);
|
||||
int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, const char *payload, uint32_t len);
|
||||
void clusterPropagatePublish(robj *channel, robj *message, int sharded);
|
||||
unsigned int keyHashSlot(char *key, int keylen);
|
||||
void slotToKeyAddEntry(dictEntry *entry, redisDb *db);
|
||||
void slotToKeyDelEntry(dictEntry *entry, redisDb *db);
|
||||
void slotToKeyReplaceEntry(dict *d, dictEntry *entry);
|
||||
void slotToKeyInit(redisDb *db);
|
||||
void slotToKeyFlush(redisDb *db);
|
||||
void slotToKeyDestroy(redisDb *db);
|
||||
void clusterUpdateMyselfFlags(void);
|
||||
void clusterUpdateMyselfIp(void);
|
||||
void slotToChannelAdd(sds channel);
|
||||
void slotToChannelDel(sds channel);
|
||||
void clusterUpdateMyselfHostname(void);
|
||||
void clusterUpdateMyselfAnnouncedPorts(void);
|
||||
sds clusterGenNodesDescription(client *c, int filter, int tls_primary);
|
||||
sds genClusterInfoString(void);
|
||||
void freeClusterLink(clusterLink *link);
|
||||
void clusterUpdateMyselfHumanNodename(void);
|
||||
int isValidAuxString(char *s, unsigned int length);
|
||||
void migrateCommand(client *c);
|
||||
void clusterCommand(client *c);
|
||||
ConnectionType *connTypeOfCluster(void);
|
||||
int getNodeDefaultClientPort(clusterNode *n);
|
||||
|
||||
#endif /* __CLUSTER_H */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,374 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2024-present, Valkey contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
*
|
||||
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
*/
|
||||
|
||||
#ifndef CLUSTER_LEGACY_H
|
||||
#define CLUSTER_LEGACY_H
|
||||
|
||||
#define CLUSTER_PORT_INCR 10000 /* Cluster port = baseport + PORT_INCR */
|
||||
|
||||
/* The following defines are amount of time, sometimes expressed as
|
||||
* multiplicators of the node timeout value (when ending with MULT). */
|
||||
#define CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
|
||||
#define CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
|
||||
#define CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */
|
||||
#define CLUSTER_MF_PAUSE_MULT 2 /* Master pause manual failover mult. */
|
||||
#define CLUSTER_SLAVE_MIGRATION_DELAY 5000 /* Delay for slave migration. */
|
||||
|
||||
/* Reasons why a slave is not able to failover. */
|
||||
#define CLUSTER_CANT_FAILOVER_NONE 0
|
||||
#define CLUSTER_CANT_FAILOVER_DATA_AGE 1
|
||||
#define CLUSTER_CANT_FAILOVER_WAITING_DELAY 2
|
||||
#define CLUSTER_CANT_FAILOVER_EXPIRED 3
|
||||
#define CLUSTER_CANT_FAILOVER_WAITING_VOTES 4
|
||||
#define CLUSTER_CANT_FAILOVER_RELOG_PERIOD (10) /* seconds. */
|
||||
|
||||
/* clusterState todo_before_sleep flags. */
|
||||
#define CLUSTER_TODO_HANDLE_FAILOVER (1<<0)
|
||||
#define CLUSTER_TODO_UPDATE_STATE (1<<1)
|
||||
#define CLUSTER_TODO_SAVE_CONFIG (1<<2)
|
||||
#define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
|
||||
#define CLUSTER_TODO_HANDLE_MANUALFAILOVER (1<<4)
|
||||
|
||||
/* clusterLink encapsulates everything needed to talk with a remote node. */
|
||||
typedef struct clusterLink {
|
||||
mstime_t ctime; /* Link creation time */
|
||||
connection *conn; /* Connection to remote node */
|
||||
list *send_msg_queue; /* List of messages to be sent */
|
||||
size_t head_msg_send_offset; /* Number of bytes already sent of message at head of queue */
|
||||
unsigned long long send_msg_queue_mem; /* Memory in bytes used by message queue */
|
||||
char *rcvbuf; /* Packet reception buffer */
|
||||
size_t rcvbuf_len; /* Used size of rcvbuf */
|
||||
size_t rcvbuf_alloc; /* Allocated size of rcvbuf */
|
||||
clusterNode *node; /* Node related to this link. Initialized to NULL when unknown */
|
||||
int inbound; /* 1 if this link is an inbound link accepted from the related node */
|
||||
} clusterLink;
|
||||
|
||||
/* Cluster node flags and macros. */
|
||||
#define CLUSTER_NODE_MASTER 1 /* The node is a master */
|
||||
#define CLUSTER_NODE_SLAVE 2 /* The node is a slave */
|
||||
#define CLUSTER_NODE_PFAIL 4 /* Failure? Need acknowledge */
|
||||
#define CLUSTER_NODE_FAIL 8 /* The node is believed to be malfunctioning */
|
||||
#define CLUSTER_NODE_MYSELF 16 /* This node is myself */
|
||||
#define CLUSTER_NODE_HANDSHAKE 32 /* We have still to exchange the first ping */
|
||||
#define CLUSTER_NODE_NOADDR 64 /* We don't know the address of this node */
|
||||
#define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */
|
||||
#define CLUSTER_NODE_MIGRATE_TO 256 /* Master eligible for replica migration. */
|
||||
#define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failover. */
|
||||
#define CLUSTER_NODE_EXTENSIONS_SUPPORTED 1024 /* This node supports extensions. */
|
||||
#define CLUSTER_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
|
||||
#define nodeIsSlave(n) ((n)->flags & CLUSTER_NODE_SLAVE)
|
||||
#define nodeInHandshake(n) ((n)->flags & CLUSTER_NODE_HANDSHAKE)
|
||||
#define nodeHasAddr(n) (!((n)->flags & CLUSTER_NODE_NOADDR))
|
||||
#define nodeTimedOut(n) ((n)->flags & CLUSTER_NODE_PFAIL)
|
||||
#define nodeFailed(n) ((n)->flags & CLUSTER_NODE_FAIL)
|
||||
#define nodeCantFailover(n) ((n)->flags & CLUSTER_NODE_NOFAILOVER)
|
||||
#define nodeSupportsExtensions(n) ((n)->flags & CLUSTER_NODE_EXTENSIONS_SUPPORTED)
|
||||
|
||||
/* This structure represent elements of node->fail_reports. */
|
||||
typedef struct clusterNodeFailReport {
|
||||
clusterNode *node; /* Node reporting the failure condition. */
|
||||
mstime_t time; /* Time of the last report from this node. */
|
||||
} clusterNodeFailReport;
|
||||
|
||||
/* Redis cluster messages header */
|
||||
|
||||
/* Message types.
|
||||
*
|
||||
* Note that the PING, PONG and MEET messages are actually the same exact
|
||||
* kind of packet. PONG is the reply to ping, in the exact format as a PING,
|
||||
* while MEET is a special PING that forces the receiver to add the sender
|
||||
* as a node (if it is not already in the list). */
|
||||
#define CLUSTERMSG_TYPE_PING 0 /* Ping */
|
||||
#define CLUSTERMSG_TYPE_PONG 1 /* Pong (reply to Ping) */
|
||||
#define CLUSTERMSG_TYPE_MEET 2 /* Meet "let's join" message */
|
||||
#define CLUSTERMSG_TYPE_FAIL 3 /* Mark node xxx as failing */
|
||||
#define CLUSTERMSG_TYPE_PUBLISH 4 /* Pub/Sub Publish propagation */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* May I failover? */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6 /* Yes, you have my vote */
|
||||
#define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */
|
||||
#define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */
|
||||
#define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */
|
||||
#define CLUSTERMSG_TYPE_PUBLISHSHARD 10 /* Pub/Sub Publish shard propagation */
|
||||
#define CLUSTERMSG_TYPE_COUNT 11 /* Total number of message types. */
|
||||
|
||||
/* Initially we don't know our "name", but we'll find it once we connect
|
||||
* to the first node, using the getsockname() function. Then we'll use this
|
||||
* address for all the next messages. */
|
||||
typedef struct {
|
||||
char nodename[CLUSTER_NAMELEN];
|
||||
uint32_t ping_sent;
|
||||
uint32_t pong_received;
|
||||
char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */
|
||||
uint16_t port; /* primary port last time it was seen */
|
||||
uint16_t cport; /* cluster port last time it was seen */
|
||||
uint16_t flags; /* node->flags copy */
|
||||
uint16_t pport; /* secondary port last time it was seen */
|
||||
uint16_t notused1;
|
||||
} clusterMsgDataGossip;
|
||||
|
||||
typedef struct {
|
||||
char nodename[CLUSTER_NAMELEN];
|
||||
} clusterMsgDataFail;
|
||||
|
||||
typedef struct {
|
||||
uint32_t channel_len;
|
||||
uint32_t message_len;
|
||||
unsigned char bulk_data[8]; /* 8 bytes just as placeholder. */
|
||||
} clusterMsgDataPublish;
|
||||
|
||||
typedef struct {
|
||||
uint64_t configEpoch; /* Config epoch of the specified instance. */
|
||||
char nodename[CLUSTER_NAMELEN]; /* Name of the slots owner. */
|
||||
unsigned char slots[CLUSTER_SLOTS/8]; /* Slots bitmap. */
|
||||
} clusterMsgDataUpdate;
|
||||
|
||||
typedef struct {
|
||||
uint64_t module_id; /* ID of the sender module. */
|
||||
uint32_t len; /* ID of the sender module. */
|
||||
uint8_t type; /* Type from 0 to 255. */
|
||||
unsigned char bulk_data[3]; /* 3 bytes just as placeholder. */
|
||||
} clusterMsgModule;
|
||||
|
||||
/* The cluster supports optional extension messages that can be sent
|
||||
* along with ping/pong/meet messages to give additional info in a
|
||||
* consistent manner. */
|
||||
typedef enum {
|
||||
CLUSTERMSG_EXT_TYPE_HOSTNAME,
|
||||
CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME,
|
||||
CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE,
|
||||
CLUSTERMSG_EXT_TYPE_SHARDID,
|
||||
} clusterMsgPingtypes;
|
||||
|
||||
/* Helper function for making sure extensions are eight byte aligned. */
|
||||
#define EIGHT_BYTE_ALIGN(size) ((((size) + 7) / 8) * 8)
|
||||
|
||||
typedef struct {
|
||||
char hostname[1]; /* The announced hostname, ends with \0. */
|
||||
} clusterMsgPingExtHostname;
|
||||
|
||||
typedef struct {
|
||||
char human_nodename[1]; /* The announced nodename, ends with \0. */
|
||||
} clusterMsgPingExtHumanNodename;
|
||||
|
||||
typedef struct {
|
||||
char name[CLUSTER_NAMELEN]; /* Node name. */
|
||||
uint64_t ttl; /* Remaining time to blacklist the node, in seconds. */
|
||||
} clusterMsgPingExtForgottenNode;
|
||||
|
||||
static_assert(sizeof(clusterMsgPingExtForgottenNode) % 8 == 0, "");
|
||||
|
||||
typedef struct {
|
||||
char shard_id[CLUSTER_NAMELEN]; /* The shard_id, 40 bytes fixed. */
|
||||
} clusterMsgPingExtShardId;
|
||||
|
||||
typedef struct {
|
||||
uint32_t length; /* Total length of this extension message (including this header) */
|
||||
uint16_t type; /* Type of this extension message (see clusterMsgPingExtTypes) */
|
||||
uint16_t unused; /* 16 bits of padding to make this structure 8 byte aligned. */
|
||||
union {
|
||||
clusterMsgPingExtHostname hostname;
|
||||
clusterMsgPingExtHumanNodename human_nodename;
|
||||
clusterMsgPingExtForgottenNode forgotten_node;
|
||||
clusterMsgPingExtShardId shard_id;
|
||||
} ext[]; /* Actual extension information, formatted so that the data is 8
|
||||
* byte aligned, regardless of its content. */
|
||||
} clusterMsgPingExt;
|
||||
|
||||
union clusterMsgData {
|
||||
/* PING, MEET and PONG */
|
||||
struct {
|
||||
/* Array of N clusterMsgDataGossip structures */
|
||||
clusterMsgDataGossip gossip[1];
|
||||
/* Extension data that can optionally be sent for ping/meet/pong
|
||||
* messages. We can't explicitly define them here though, since
|
||||
* the gossip array isn't the real length of the gossip data. */
|
||||
} ping;
|
||||
|
||||
/* FAIL */
|
||||
struct {
|
||||
clusterMsgDataFail about;
|
||||
} fail;
|
||||
|
||||
/* PUBLISH */
|
||||
struct {
|
||||
clusterMsgDataPublish msg;
|
||||
} publish;
|
||||
|
||||
/* UPDATE */
|
||||
struct {
|
||||
clusterMsgDataUpdate nodecfg;
|
||||
} update;
|
||||
|
||||
/* MODULE */
|
||||
struct {
|
||||
clusterMsgModule msg;
|
||||
} module;
|
||||
};
|
||||
|
||||
#define CLUSTER_PROTO_VER 1 /* Cluster bus protocol version. */
|
||||
|
||||
typedef struct {
|
||||
char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */
|
||||
uint32_t totlen; /* Total length of this message */
|
||||
uint16_t ver; /* Protocol version, currently set to 1. */
|
||||
uint16_t port; /* Primary port number (TCP or TLS). */
|
||||
uint16_t type; /* Message type */
|
||||
uint16_t count; /* Only used for some kind of messages. */
|
||||
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
|
||||
uint64_t configEpoch; /* The config epoch if it's a master, or the last
|
||||
epoch advertised by its master if it is a
|
||||
slave. */
|
||||
uint64_t offset; /* Master replication offset if node is a master or
|
||||
processed replication offset if node is a slave. */
|
||||
char sender[CLUSTER_NAMELEN]; /* Name of the sender node */
|
||||
unsigned char myslots[CLUSTER_SLOTS/8];
|
||||
char slaveof[CLUSTER_NAMELEN];
|
||||
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
|
||||
uint16_t extensions; /* Number of extensions sent along with this packet. */
|
||||
char notused1[30]; /* 30 bytes reserved for future usage. */
|
||||
uint16_t pport; /* Secondary port number: if primary port is TCP port, this is
|
||||
TLS port, and if primary port is TLS port, this is TCP port.*/
|
||||
uint16_t cport; /* Sender TCP cluster bus port */
|
||||
uint16_t flags; /* Sender node flags */
|
||||
unsigned char state; /* Cluster state from the POV of the sender */
|
||||
unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */
|
||||
union clusterMsgData data;
|
||||
} clusterMsg;
|
||||
|
||||
/* clusterMsg defines the gossip wire protocol exchanged among Redis cluster
|
||||
* members, which can be running different versions of redis-server bits,
|
||||
* especially during cluster rolling upgrades.
|
||||
*
|
||||
* Therefore, fields in this struct should remain at the same offset from
|
||||
* release to release. The static asserts below ensures that incompatible
|
||||
* changes in clusterMsg be caught at compile time.
|
||||
*/
|
||||
|
||||
static_assert(offsetof(clusterMsg, sig) == 0, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, totlen) == 4, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, ver) == 8, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, port) == 10, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, type) == 12, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, count) == 14, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, currentEpoch) == 16, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, configEpoch) == 24, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, offset) == 32, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, sender) == 40, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, myslots) == 80, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, slaveof) == 2128, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, myip) == 2168, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, extensions) == 2214, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, notused1) == 2216, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, pport) == 2246, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, cport) == 2248, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, flags) == 2250, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, state) == 2252, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, mflags) == 2253, "unexpected field offset");
|
||||
static_assert(offsetof(clusterMsg, data) == 2256, "unexpected field offset");
|
||||
|
||||
#define CLUSTERMSG_MIN_LEN (sizeof(clusterMsg)-sizeof(union clusterMsgData))
|
||||
|
||||
/* Message flags better specify the packet content or are used to
|
||||
* provide some information about the node state. */
|
||||
#define CLUSTERMSG_FLAG0_PAUSED (1<<0) /* Master paused for manual failover. */
|
||||
#define CLUSTERMSG_FLAG0_FORCEACK (1<<1) /* Give ACK to AUTH_REQUEST even if
|
||||
master is up. */
|
||||
#define CLUSTERMSG_FLAG0_EXT_DATA (1<<2) /* Message contains extension data */
|
||||
|
||||
struct _clusterNode {
|
||||
mstime_t ctime; /* Node object creation time. */
|
||||
char name[CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
|
||||
char shard_id[CLUSTER_NAMELEN]; /* shard id, hex string, sha1-size */
|
||||
int flags; /* CLUSTER_NODE_... */
|
||||
uint64_t configEpoch; /* Last configEpoch observed for this node */
|
||||
unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
|
||||
uint16_t *slot_info_pairs; /* Slots info represented as (start/end) pair (consecutive index). */
|
||||
int slot_info_pairs_count; /* Used number of slots in slot_info_pairs */
|
||||
int numslots; /* Number of slots handled by this node */
|
||||
int numslaves; /* Number of slave nodes, if this is a master */
|
||||
clusterNode **slaves; /* pointers to slave nodes */
|
||||
clusterNode *slaveof; /* pointer to the master node. Note that it
|
||||
may be NULL even if the node is a slave
|
||||
if we don't have the master node in our
|
||||
tables. */
|
||||
unsigned long long last_in_ping_gossip; /* The number of the last carried in the ping gossip section */
|
||||
mstime_t ping_sent; /* Unix time we sent latest ping */
|
||||
mstime_t pong_received; /* Unix time we received the pong */
|
||||
mstime_t data_received; /* Unix time we received any data */
|
||||
mstime_t fail_time; /* Unix time when FAIL flag was set */
|
||||
mstime_t voted_time; /* Last time we voted for a slave of this master */
|
||||
mstime_t repl_offset_time; /* Unix time we received offset for this node */
|
||||
mstime_t orphaned_time; /* Starting time of orphaned master condition */
|
||||
long long repl_offset; /* Last known repl offset for this node. */
|
||||
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
|
||||
sds hostname; /* The known hostname for this node */
|
||||
sds human_nodename; /* The known human readable nodename for this node */
|
||||
int tcp_port; /* Latest known clients TCP port. */
|
||||
int tls_port; /* Latest known clients TLS port */
|
||||
int cport; /* Latest known cluster port of this node. */
|
||||
clusterLink *link; /* TCP/IP link established toward this node */
|
||||
clusterLink *inbound_link; /* TCP/IP link accepted from this node */
|
||||
list *fail_reports; /* List of nodes signaling this as failing */
|
||||
};
|
||||
|
||||
struct clusterState {
|
||||
clusterNode *myself; /* This node */
|
||||
uint64_t currentEpoch;
|
||||
int state; /* CLUSTER_OK, CLUSTER_FAIL, ... */
|
||||
int size; /* Num of master nodes with at least one slot */
|
||||
dict *nodes; /* Hash table of name -> clusterNode structures */
|
||||
dict *shards; /* Hash table of shard_id -> list (of nodes) structures */
|
||||
dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */
|
||||
clusterNode *migrating_slots_to[CLUSTER_SLOTS];
|
||||
clusterNode *importing_slots_from[CLUSTER_SLOTS];
|
||||
clusterNode *slots[CLUSTER_SLOTS];
|
||||
/* The following fields are used to take the slave state on elections. */
|
||||
mstime_t failover_auth_time; /* Time of previous or next election. */
|
||||
int failover_auth_count; /* Number of votes received so far. */
|
||||
int failover_auth_sent; /* True if we already asked for votes. */
|
||||
int failover_auth_rank; /* This slave rank for current auth request. */
|
||||
uint64_t failover_auth_epoch; /* Epoch of the current election. */
|
||||
int cant_failover_reason; /* Why a slave is currently not able to
|
||||
failover. See the CANT_FAILOVER_* macros. */
|
||||
/* Manual failover state in common. */
|
||||
mstime_t mf_end; /* Manual failover time limit (ms unixtime).
|
||||
It is zero if there is no MF in progress. */
|
||||
/* Manual failover state of master. */
|
||||
clusterNode *mf_slave; /* Slave performing the manual failover. */
|
||||
/* Manual failover state of slave. */
|
||||
long long mf_master_offset; /* Master offset the slave needs to start MF
|
||||
or -1 if still not received. */
|
||||
int mf_can_start; /* If non-zero signal that the manual failover
|
||||
can start requesting masters vote. */
|
||||
/* The following fields are used by masters to take state on elections. */
|
||||
uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */
|
||||
int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
|
||||
/* Stats */
|
||||
/* Messages received and sent by type. */
|
||||
long long stats_bus_messages_sent[CLUSTERMSG_TYPE_COUNT];
|
||||
long long stats_bus_messages_received[CLUSTERMSG_TYPE_COUNT];
|
||||
long long stats_pfail_nodes; /* Number of nodes in PFAIL status,
|
||||
excluding nodes without address. */
|
||||
unsigned long long stat_cluster_links_buffer_limit_exceeded; /* Total number of cluster links freed due to exceeding buffer limit */
|
||||
|
||||
/* Bit map for slots that are no longer claimed by the owner in cluster PING
|
||||
* messages. During slot migration, the owner will stop claiming the slot after
|
||||
* the ownership transfer. Set the bit corresponding to the slot when a node
|
||||
* stops claiming the slot. This prevents spreading incorrect information (that
|
||||
* source still owns the slot) using UPDATE messages. */
|
||||
unsigned char owner_not_claiming_slot[CLUSTER_SLOTS / 8];
|
||||
};
|
||||
|
||||
|
||||
#endif //CLUSTER_LEGACY_H
|
||||
+13
-349
@@ -1177,7 +1177,6 @@ commandHistory CLIENT_KILL_History[] = {
|
||||
{"3.2.0","Added `master` type in for `TYPE` option."},
|
||||
{"5.0.0","Replaced `slave` `TYPE` with `replica`. `slave` still supported for backward compatibility."},
|
||||
{"6.2.0","`LADDR` option."},
|
||||
{"7.4.0","`MAXAGE` option."},
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -1214,13 +1213,12 @@ struct COMMAND_ARG CLIENT_KILL_filter_new_format_Subargs[] = {
|
||||
{MAKE_ARG("addr",ARG_TYPE_STRING,-1,"ADDR",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"},
|
||||
{MAKE_ARG("laddr",ARG_TYPE_STRING,-1,"LADDR",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"},
|
||||
{MAKE_ARG("skipme",ARG_TYPE_ONEOF,-1,"SKIPME",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=CLIENT_KILL_filter_new_format_skipme_Subargs},
|
||||
{MAKE_ARG("maxage",ARG_TYPE_INTEGER,-1,"MAXAGE",NULL,"7.4.0",CMD_ARG_OPTIONAL,0,NULL)},
|
||||
};
|
||||
|
||||
/* CLIENT KILL filter argument table */
|
||||
struct COMMAND_ARG CLIENT_KILL_filter_Subargs[] = {
|
||||
{MAKE_ARG("old-format",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,"2.8.12"),.display_text="ip:port"},
|
||||
{MAKE_ARG("new-format",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,7,NULL),.subargs=CLIENT_KILL_filter_new_format_Subargs},
|
||||
{MAKE_ARG("new-format",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,6,NULL),.subargs=CLIENT_KILL_filter_new_format_Subargs},
|
||||
};
|
||||
|
||||
/* CLIENT KILL argument table */
|
||||
@@ -1545,7 +1543,7 @@ struct COMMAND_STRUCT CLIENT_Subcommands[] = {
|
||||
{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,clientCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,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,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_ID_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("info","Returns information about the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,0,CLIENT_INFO_Tips,1,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_INFO_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,6,CLIENT_KILL_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args},
|
||||
{MAKE_CMD("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,5,CLIENT_KILL_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args},
|
||||
{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,6,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,2),.args=CLIENT_LIST_Args},
|
||||
{MAKE_CMD("no-evict","Sets the client eviction mode of the connection.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,0,CLIENT_NO_EVICT_Tips,0,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_NO_EVICT_Keyspecs,0,NULL,1),.args=CLIENT_NO_EVICT_Args},
|
||||
{MAKE_CMD("no-touch","Controls whether commands sent by the client affect the LRU/LFU of accessed keys.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,0,CLIENT_NO_TOUCH_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_NO_TOUCH_Keyspecs,0,NULL,1),.args=CLIENT_NO_TOUCH_Args},
|
||||
@@ -2900,7 +2898,6 @@ struct COMMAND_ARG GEORADIUS_Args[] = {
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* GEORADIUSBYMEMBER history */
|
||||
commandHistory GEORADIUSBYMEMBER_History[] = {
|
||||
{"6.2.0","Added the `ANY` option for `COUNT`."},
|
||||
{"7.0.0","Added support for uppercase unit names."},
|
||||
};
|
||||
#endif
|
||||
@@ -2961,10 +2958,7 @@ struct COMMAND_ARG GEORADIUSBYMEMBER_Args[] = {
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* GEORADIUSBYMEMBER_RO history */
|
||||
commandHistory GEORADIUSBYMEMBER_RO_History[] = {
|
||||
{"6.2.0","Added the `ANY` option for `COUNT`."},
|
||||
{"7.0.0","Added support for uppercase unit names."},
|
||||
};
|
||||
#define GEORADIUSBYMEMBER_RO_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
@@ -3018,7 +3012,6 @@ struct COMMAND_ARG GEORADIUSBYMEMBER_RO_Args[] = {
|
||||
/* GEORADIUS_RO history */
|
||||
commandHistory GEORADIUS_RO_History[] = {
|
||||
{"6.2.0","Added the `ANY` option for `COUNT`."},
|
||||
{"7.0.0","Added support for uppercase unit names."},
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -3303,119 +3296,6 @@ struct COMMAND_ARG HEXISTS_Args[] = {
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/********** HEXPIRE ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HEXPIRE history */
|
||||
#define HEXPIRE_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HEXPIRE tips */
|
||||
#define HEXPIRE_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HEXPIRE key specs */
|
||||
keySpec HEXPIRE_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HEXPIRE condition argument table */
|
||||
struct COMMAND_ARG HEXPIRE_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)},
|
||||
{MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIRE fields argument table */
|
||||
struct COMMAND_ARG HEXPIRE_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIRE argument table */
|
||||
struct COMMAND_ARG HEXPIRE_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HEXPIRE_condition_Subargs},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HEXPIRE_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HEXPIREAT ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HEXPIREAT history */
|
||||
#define HEXPIREAT_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HEXPIREAT tips */
|
||||
#define HEXPIREAT_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HEXPIREAT key specs */
|
||||
keySpec HEXPIREAT_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HEXPIREAT condition argument table */
|
||||
struct COMMAND_ARG HEXPIREAT_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)},
|
||||
{MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIREAT fields argument table */
|
||||
struct COMMAND_ARG HEXPIREAT_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIREAT argument table */
|
||||
struct COMMAND_ARG HEXPIREAT_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HEXPIREAT_condition_Subargs},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HEXPIREAT_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HEXPIRETIME ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HEXPIRETIME history */
|
||||
#define HEXPIRETIME_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HEXPIRETIME tips */
|
||||
#define HEXPIRETIME_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HEXPIRETIME key specs */
|
||||
keySpec HEXPIRETIME_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HEXPIRETIME fields argument table */
|
||||
struct COMMAND_ARG HEXPIRETIME_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIRETIME argument table */
|
||||
struct COMMAND_ARG HEXPIRETIME_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HEXPIRETIME_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HGET ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
@@ -3625,181 +3505,6 @@ struct COMMAND_ARG HMSET_Args[] = {
|
||||
{MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=HMSET_data_Subargs},
|
||||
};
|
||||
|
||||
/********** HPERSIST ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HPERSIST history */
|
||||
#define HPERSIST_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HPERSIST tips */
|
||||
#define HPERSIST_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HPERSIST key specs */
|
||||
keySpec HPERSIST_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HPERSIST fields argument table */
|
||||
struct COMMAND_ARG HPERSIST_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPERSIST argument table */
|
||||
struct COMMAND_ARG HPERSIST_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPERSIST_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HPEXPIRE ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HPEXPIRE history */
|
||||
#define HPEXPIRE_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HPEXPIRE tips */
|
||||
#define HPEXPIRE_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HPEXPIRE key specs */
|
||||
keySpec HPEXPIRE_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HPEXPIRE condition argument table */
|
||||
struct COMMAND_ARG HPEXPIRE_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)},
|
||||
{MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIRE fields argument table */
|
||||
struct COMMAND_ARG HPEXPIRE_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIRE argument table */
|
||||
struct COMMAND_ARG HPEXPIRE_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HPEXPIRE_condition_Subargs},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPEXPIRE_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HPEXPIREAT ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HPEXPIREAT history */
|
||||
#define HPEXPIREAT_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HPEXPIREAT tips */
|
||||
#define HPEXPIREAT_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HPEXPIREAT key specs */
|
||||
keySpec HPEXPIREAT_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HPEXPIREAT condition argument table */
|
||||
struct COMMAND_ARG HPEXPIREAT_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)},
|
||||
{MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIREAT fields argument table */
|
||||
struct COMMAND_ARG HPEXPIREAT_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIREAT argument table */
|
||||
struct COMMAND_ARG HPEXPIREAT_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HPEXPIREAT_condition_Subargs},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPEXPIREAT_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HPEXPIRETIME ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HPEXPIRETIME history */
|
||||
#define HPEXPIRETIME_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HPEXPIRETIME tips */
|
||||
#define HPEXPIRETIME_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HPEXPIRETIME key specs */
|
||||
keySpec HPEXPIRETIME_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HPEXPIRETIME fields argument table */
|
||||
struct COMMAND_ARG HPEXPIRETIME_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIRETIME argument table */
|
||||
struct COMMAND_ARG HPEXPIRETIME_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPEXPIRETIME_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HPTTL ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HPTTL history */
|
||||
#define HPTTL_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HPTTL tips */
|
||||
#define HPTTL_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HPTTL key specs */
|
||||
keySpec HPTTL_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HPTTL fields argument table */
|
||||
struct COMMAND_ARG HPTTL_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPTTL argument table */
|
||||
struct COMMAND_ARG HPTTL_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPTTL_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HRANDFIELD ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
@@ -3860,7 +3565,6 @@ struct COMMAND_ARG HSCAN_Args[] = {
|
||||
{MAKE_ARG("cursor",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("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("novalues",ARG_TYPE_PURE_TOKEN,-1,"NOVALUES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
};
|
||||
|
||||
/********** HSET ********************/
|
||||
@@ -3947,37 +3651,6 @@ struct COMMAND_ARG HSTRLEN_Args[] = {
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/********** HTTL ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* HTTL history */
|
||||
#define HTTL_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* HTTL tips */
|
||||
#define HTTL_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* HTTL key specs */
|
||||
keySpec HTTL_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HTTL fields argument table */
|
||||
struct COMMAND_ARG HTTL_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HTTL argument table */
|
||||
struct COMMAND_ARG HTTL_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HTTL_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HVALS ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
@@ -8143,7 +7816,7 @@ struct COMMAND_ARG SINTERCARD_Args[] = {
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* SINTERSTORE key specs */
|
||||
keySpec SINTERSTORE_Keyspecs[2] = {
|
||||
{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={-1,1,0}}
|
||||
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={-1,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -9705,7 +9378,7 @@ struct COMMAND_ARG XGROUP_CREATE_Args[] = {
|
||||
{MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("id-selector",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XGROUP_CREATE_id_selector_Subargs},
|
||||
{MAKE_ARG("mkstream",ARG_TYPE_PURE_TOKEN,-1,"MKSTREAM",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
{MAKE_ARG("entriesread",ARG_TYPE_INTEGER,-1,"ENTRIESREAD",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL),.display_text="entries-read"},
|
||||
{MAKE_ARG("entries-read",ARG_TYPE_INTEGER,-1,"ENTRIESREAD",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
};
|
||||
|
||||
/********** XGROUP CREATECONSUMER ********************/
|
||||
@@ -9870,7 +9543,7 @@ struct COMMAND_STRUCT XGROUP_Subcommands[] = {
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* XINFO CONSUMERS history */
|
||||
commandHistory XINFO_CONSUMERS_History[] = {
|
||||
{"7.2.0","Added the `inactive` field, and changed the meaning of `idle`."},
|
||||
{"7.2.0","Added the `inactive` field."},
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -11013,25 +10686,22 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
||||
{MAKE_CMD("ttl","Returns the expiration time in seconds of a key.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,TTL_History,1,TTL_Tips,1,ttlCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,TTL_Keyspecs,1,NULL,1),.args=TTL_Args},
|
||||
{MAKE_CMD("type","Determines the type of value stored at a key.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,TYPE_History,0,TYPE_Tips,0,typeCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,TYPE_Keyspecs,1,NULL,1),.args=TYPE_Args},
|
||||
{MAKE_CMD("unlink","Asynchronously deletes one or more keys.","O(1) for each key removed regardless of its size. Then the command does O(N) work in a different thread in order to reclaim memory, where N is the number of allocations the deleted objects where composed of.","4.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,UNLINK_History,0,UNLINK_Tips,2,unlinkCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE,UNLINK_Keyspecs,1,NULL,1),.args=UNLINK_Args},
|
||||
{MAKE_CMD("wait","Blocks until the asynchronous replication of all preceding write commands sent by the connection is completed.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,WAIT_History,0,WAIT_Tips,2,waitCommand,3,CMD_BLOCKING,ACL_CATEGORY_CONNECTION,WAIT_Keyspecs,0,NULL,2),.args=WAIT_Args},
|
||||
{MAKE_CMD("waitaof","Blocks until all of the preceding write commands sent by the connection are written to the append-only file of the master and/or replicas.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,WAITAOF_History,0,WAITAOF_Tips,2,waitaofCommand,4,CMD_BLOCKING,ACL_CATEGORY_CONNECTION,WAITAOF_Keyspecs,0,NULL,3),.args=WAITAOF_Args},
|
||||
{MAKE_CMD("wait","Blocks until the asynchronous replication of all preceding write commands sent by the connection is completed.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,WAIT_History,0,WAIT_Tips,2,waitCommand,3,0,ACL_CATEGORY_CONNECTION,WAIT_Keyspecs,0,NULL,2),.args=WAIT_Args},
|
||||
{MAKE_CMD("waitaof","Blocks until all of the preceding write commands sent by the connection are written to the append-only file of the master and/or replicas.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,WAITAOF_History,0,WAITAOF_Tips,2,waitaofCommand,4,CMD_NOSCRIPT,ACL_CATEGORY_CONNECTION,WAITAOF_Keyspecs,0,NULL,3),.args=WAITAOF_Args},
|
||||
/* geo */
|
||||
{MAKE_CMD("geoadd","Adds one or more members to a geospatial index. The key is created if it doesn't exist.","O(log(N)) for each item added, where N is the number of elements in the sorted set.","3.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOADD_History,1,GEOADD_Tips,0,geoaddCommand,-5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_GEO,GEOADD_Keyspecs,1,NULL,4),.args=GEOADD_Args},
|
||||
{MAKE_CMD("geodist","Returns the distance between two members of a geospatial index.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEODIST_History,0,GEODIST_Tips,0,geodistCommand,-4,CMD_READONLY,ACL_CATEGORY_GEO,GEODIST_Keyspecs,1,NULL,4),.args=GEODIST_Args},
|
||||
{MAKE_CMD("geohash","Returns members from a geospatial index as geohash strings.","O(1) for each member requested.","3.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOHASH_History,0,GEOHASH_Tips,0,geohashCommand,-2,CMD_READONLY,ACL_CATEGORY_GEO,GEOHASH_Keyspecs,1,NULL,2),.args=GEOHASH_Args},
|
||||
{MAKE_CMD("geopos","Returns the longitude and latitude of members from a geospatial index.","O(1) for each member requested.","3.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOPOS_History,0,GEOPOS_Tips,0,geoposCommand,-2,CMD_READONLY,ACL_CATEGORY_GEO,GEOPOS_Keyspecs,1,NULL,2),.args=GEOPOS_Args},
|
||||
{MAKE_CMD("georadius","Queries a geospatial index for members within a distance from a coordinate, optionally stores the result.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.0",CMD_DOC_DEPRECATED,"`GEOSEARCH` and `GEOSEARCHSTORE` with the `BYRADIUS` argument","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUS_History,2,GEORADIUS_Tips,0,georadiusCommand,-6,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_GEO,GEORADIUS_Keyspecs,3,georadiusGetKeys,11),.args=GEORADIUS_Args},
|
||||
{MAKE_CMD("georadiusbymember","Queries a geospatial index for members within a distance from a member, optionally stores the result.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.0",CMD_DOC_DEPRECATED,"`GEOSEARCH` and `GEOSEARCHSTORE` with the `BYRADIUS` and `FROMMEMBER` arguments","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUSBYMEMBER_History,2,GEORADIUSBYMEMBER_Tips,0,georadiusbymemberCommand,-5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_GEO,GEORADIUSBYMEMBER_Keyspecs,3,georadiusGetKeys,10),.args=GEORADIUSBYMEMBER_Args},
|
||||
{MAKE_CMD("georadiusbymember_ro","Returns members from a geospatial index that are within a distance from a member.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.10",CMD_DOC_DEPRECATED,"`GEOSEARCH` with the `BYRADIUS` and `FROMMEMBER` arguments","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUSBYMEMBER_RO_History,2,GEORADIUSBYMEMBER_RO_Tips,0,georadiusbymemberroCommand,-5,CMD_READONLY,ACL_CATEGORY_GEO,GEORADIUSBYMEMBER_RO_Keyspecs,1,NULL,9),.args=GEORADIUSBYMEMBER_RO_Args},
|
||||
{MAKE_CMD("georadius_ro","Returns members from a geospatial index that are within a distance from a coordinate.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.10",CMD_DOC_DEPRECATED,"`GEOSEARCH` with the `BYRADIUS` argument","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUS_RO_History,2,GEORADIUS_RO_Tips,0,georadiusroCommand,-6,CMD_READONLY,ACL_CATEGORY_GEO,GEORADIUS_RO_Keyspecs,1,NULL,10),.args=GEORADIUS_RO_Args},
|
||||
{MAKE_CMD("georadiusbymember","Queries a geospatial index for members within a distance from a member, optionally stores the result.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.0",CMD_DOC_DEPRECATED,"`GEOSEARCH` and `GEOSEARCHSTORE` with the `BYRADIUS` and `FROMMEMBER` arguments","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUSBYMEMBER_History,1,GEORADIUSBYMEMBER_Tips,0,georadiusbymemberCommand,-5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_GEO,GEORADIUSBYMEMBER_Keyspecs,3,georadiusGetKeys,10),.args=GEORADIUSBYMEMBER_Args},
|
||||
{MAKE_CMD("georadiusbymember_ro","Returns members from a geospatial index that are within a distance from a member.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.10",CMD_DOC_DEPRECATED,"`GEOSEARCH` with the `BYRADIUS` and `FROMMEMBER` arguments","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUSBYMEMBER_RO_History,0,GEORADIUSBYMEMBER_RO_Tips,0,georadiusbymemberroCommand,-5,CMD_READONLY,ACL_CATEGORY_GEO,GEORADIUSBYMEMBER_RO_Keyspecs,1,NULL,9),.args=GEORADIUSBYMEMBER_RO_Args},
|
||||
{MAKE_CMD("georadius_ro","Returns members from a geospatial index that are within a distance from a coordinate.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.10",CMD_DOC_DEPRECATED,"`GEOSEARCH` with the `BYRADIUS` argument","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUS_RO_History,1,GEORADIUS_RO_Tips,0,georadiusroCommand,-6,CMD_READONLY,ACL_CATEGORY_GEO,GEORADIUS_RO_Keyspecs,1,NULL,10),.args=GEORADIUS_RO_Args},
|
||||
{MAKE_CMD("geosearch","Queries a geospatial index for members inside an area of a box or a circle.","O(N+log(M)) where N is the number of elements in the grid-aligned bounding box area around the shape provided as the filter and M is the number of items inside the shape","6.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOSEARCH_History,1,GEOSEARCH_Tips,0,geosearchCommand,-7,CMD_READONLY,ACL_CATEGORY_GEO,GEOSEARCH_Keyspecs,1,NULL,8),.args=GEOSEARCH_Args},
|
||||
{MAKE_CMD("geosearchstore","Queries a geospatial index for members inside an area of a box or a circle, optionally stores the result.","O(N+log(M)) where N is the number of elements in the grid-aligned bounding box area around the shape provided as the filter and M is the number of items inside the shape","6.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOSEARCHSTORE_History,1,GEOSEARCHSTORE_Tips,0,geosearchstoreCommand,-8,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_GEO,GEOSEARCHSTORE_Keyspecs,2,NULL,7),.args=GEOSEARCHSTORE_Args},
|
||||
/* 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_HASH,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_HASH,HEXISTS_Keyspecs,1,NULL,2),.args=HEXISTS_Args},
|
||||
{MAKE_CMD("hexpire","Set expiry for hash field using relative time to expire (seconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRE_History,0,HEXPIRE_Tips,0,hexpireCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HEXPIRE_Keyspecs,1,NULL,4),.args=HEXPIRE_Args},
|
||||
{MAKE_CMD("hexpireat","Set expiry for hash field using an absolute Unix timestamp (seconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIREAT_History,0,HEXPIREAT_Tips,0,hexpireatCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HEXPIREAT_Keyspecs,1,NULL,4),.args=HEXPIREAT_Args},
|
||||
{MAKE_CMD("hexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in seconds.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRETIME_History,0,HEXPIRETIME_Tips,0,hexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,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_HASH,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,HGETALL_Keyspecs,1,NULL,1),.args=HGETALL_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_HASH,HINCRBY_Keyspecs,1,NULL,3),.args=HINCRBY_Args},
|
||||
@@ -11040,17 +10710,11 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
||||
{MAKE_CMD("hlen","Returns the number of fields in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HLEN_History,0,HLEN_Tips,0,hlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HLEN_Keyspecs,1,NULL,1),.args=HLEN_Args},
|
||||
{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_HASH,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_DEPRECATED,"`HSET` with multiple field-value pairs","4.0.0","hash",COMMAND_GROUP_HASH,HMSET_History,0,HMSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HMSET_Keyspecs,1,NULL,2),.args=HMSET_Args},
|
||||
{MAKE_CMD("hpersist","Removes the expiration time for each specified field","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPERSIST_History,0,HPERSIST_Tips,0,hpersistCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HPERSIST_Keyspecs,1,NULL,2),.args=HPERSIST_Args},
|
||||
{MAKE_CMD("hpexpire","Set expiry for hash field using relative time to expire (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRE_History,0,HPEXPIRE_Tips,0,hpexpireCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRE_Keyspecs,1,NULL,4),.args=HPEXPIRE_Args},
|
||||
{MAKE_CMD("hpexpireat","Set expiry for hash field using an absolute Unix timestamp (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIREAT_History,0,HPEXPIREAT_Tips,0,hpexpireatCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIREAT_Keyspecs,1,NULL,4),.args=HPEXPIREAT_Args},
|
||||
{MAKE_CMD("hpexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in msec.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRETIME_History,0,HPEXPIRETIME_Tips,0,hpexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRETIME_Keyspecs,1,NULL,2),.args=HPEXPIRETIME_Args},
|
||||
{MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,0,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,2),.args=HPTTL_Args},
|
||||
{MAKE_CMD("hrandfield","Returns one or more random fields from a hash.","O(N) where N is the number of fields returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HRANDFIELD_History,0,HRANDFIELD_Tips,1,hrandfieldCommand,-2,CMD_READONLY,ACL_CATEGORY_HASH,HRANDFIELD_Keyspecs,1,NULL,2),.args=HRANDFIELD_Args},
|
||||
{MAKE_CMD("hscan","Iterates over fields and values of a hash.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSCAN_History,0,HSCAN_Tips,1,hscanCommand,-3,CMD_READONLY,ACL_CATEGORY_HASH,HSCAN_Keyspecs,1,NULL,5),.args=HSCAN_Args},
|
||||
{MAKE_CMD("hscan","Iterates over fields and values of a hash.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSCAN_History,0,HSCAN_Tips,1,hscanCommand,-3,CMD_READONLY,ACL_CATEGORY_HASH,HSCAN_Keyspecs,1,NULL,4),.args=HSCAN_Args},
|
||||
{MAKE_CMD("hset","Creates or modifies the value of a field in a hash.","O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSET_History,1,HSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSET_Keyspecs,1,NULL,2),.args=HSET_Args},
|
||||
{MAKE_CMD("hsetnx","Sets the value of a field in a hash only when the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETNX_History,0,HSETNX_Tips,0,hsetnxCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSETNX_Keyspecs,1,NULL,3),.args=HSETNX_Args},
|
||||
{MAKE_CMD("hstrlen","Returns the length of the value of a field.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSTRLEN_History,0,HSTRLEN_Tips,0,hstrlenCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HSTRLEN_Keyspecs,1,NULL,2),.args=HSTRLEN_Args},
|
||||
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,0,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args},
|
||||
{MAKE_CMD("hvals","Returns all values in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HVALS_History,0,HVALS_Tips,1,hvalsCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HVALS_Keyspecs,1,NULL,1),.args=HVALS_Args},
|
||||
/* hyperloglog */
|
||||
{MAKE_CMD("pfadd","Adds elements to a HyperLogLog key. Creates the key if it doesn't exist.","O(1) to add every element.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFADD_History,0,PFADD_Tips,0,pfaddCommand,-2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HYPERLOGLOG,PFADD_Keyspecs,1,NULL,2),.args=PFADD_Args},
|
||||
@@ -11197,7 +10861,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
||||
{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_STREAM,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_STREAM,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_STREAM,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_STREAM,XREAD_Keyspecs,1,xreadGetKeys,3),.args=XREAD_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|CMD_BLOCKING,ACL_CATEGORY_STREAM,XREAD_Keyspecs,1,xreadGetKeys,3),.args=XREAD_Args},
|
||||
{MAKE_CMD("xreadgroup","Returns new or historical messages from a stream for a consumer in a group. Blocks until a message is available otherwise.","For each stream mentioned: O(M) with M being the number of elements returned. If M is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1). On the other side when XREADGROUP blocks, XADD will pay the O(N) time in order to serve the N clients blocked on the stream getting new data.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREADGROUP_History,0,XREADGROUP_Tips,0,xreadCommand,-7,CMD_BLOCKING|CMD_WRITE,ACL_CATEGORY_STREAM,XREADGROUP_Keyspecs,1,xreadGetKeys,5),.args=XREADGROUP_Args},
|
||||
{MAKE_CMD("xrevrange","Returns the messages from a stream within a range of IDs in reverse order.","O(N) with N being the number of elements 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,XREVRANGE_History,1,XREVRANGE_Tips,0,xrevrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_STREAM,XREVRANGE_Keyspecs,1,NULL,4),.args=XREVRANGE_Args},
|
||||
{MAKE_CMD("xsetid","An internal command for replicating stream values.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XSETID_History,1,XSETID_Tips,0,xsetidCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STREAM,XSETID_Keyspecs,1,NULL,4),.args=XSETID_Args},
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
This directory contains JSON files, one for each of Redis commands.
|
||||
|
||||
Each JSON contains all the information about the command itself, but these JSON files are not to be used directly!
|
||||
Any third party who needs access to command information must get it from `COMMAND INFO` and `COMMAND DOCS`.
|
||||
The output can be extracted in a JSON format by using `redis-cli --json`, in the same manner as in `utils/generate-commands-json.py`.
|
||||
|
||||
The JSON files are used to generate commands.def (and https://github.com/redis/redis-doc/blob/master/commands.json) in Redis, and
|
||||
despite looking similar to the output of `COMMAND` there are some fields and flags that are implicitly populated, and that's the
|
||||
reason one shouldn't rely on the raw files.
|
||||
|
||||
The structure of each JSON is somewhat documented in https://redis.io/commands/command-docs/ and https://redis.io/commands/command/
|
||||
|
||||
The `reply_schema` section is a standard JSON Schema (see https://json-schema.org/) that describes the reply of each command.
|
||||
It is designed to someday be used to auto-generate code in client libraries, but is not yet mature and is not exposed externally.
|
||||
|
||||
@@ -27,10 +27,6 @@
|
||||
[
|
||||
"6.2.0",
|
||||
"`LADDR` option."
|
||||
],
|
||||
[
|
||||
"7.4.0",
|
||||
"`MAXAGE` option."
|
||||
]
|
||||
],
|
||||
"command_flags": [
|
||||
@@ -140,13 +136,6 @@
|
||||
"token": "NO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"token": "MAXAGE",
|
||||
"name": "maxage",
|
||||
"type": "integer",
|
||||
"optional": true,
|
||||
"since": "7.4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
[
|
||||
"6.2.0",
|
||||
"Added the `ANY` option for `COUNT`."
|
||||
],
|
||||
[
|
||||
"7.0.0",
|
||||
"Added support for uppercase unit names."
|
||||
]
|
||||
],
|
||||
"deprecated_since": "6.2.0",
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
"function": "georadiusbymemberCommand",
|
||||
"get_keys_function": "georadiusGetKeys",
|
||||
"history": [
|
||||
[
|
||||
"6.2.0",
|
||||
"Added the `ANY` option for `COUNT`."
|
||||
],
|
||||
[
|
||||
"7.0.0",
|
||||
"Added support for uppercase unit names."
|
||||
|
||||
@@ -6,16 +6,6 @@
|
||||
"since": "3.2.10",
|
||||
"arity": -5,
|
||||
"function": "georadiusbymemberroCommand",
|
||||
"history": [
|
||||
[
|
||||
"6.2.0",
|
||||
"Added the `ANY` option for `COUNT`."
|
||||
],
|
||||
[
|
||||
"7.0.0",
|
||||
"Added support for uppercase unit names."
|
||||
]
|
||||
],
|
||||
"deprecated_since": "6.2.0",
|
||||
"replaced_by": "`GEOSEARCH` with the `BYRADIUS` and `FROMMEMBER` arguments",
|
||||
"doc_flags": [
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
{
|
||||
"HEXPIRE": {
|
||||
"summary": "Set expiry for hash field using relative time to expire (seconds)",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -6,
|
||||
"function": "hexpireCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of results. Returns empty array if the key does not exist.",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The field does not exist.",
|
||||
"const": -2
|
||||
},
|
||||
{
|
||||
"description": "Specified NX | XX | GT | LT condition not met",
|
||||
"const": 0
|
||||
},
|
||||
{
|
||||
"description": "Expiration time was set or updated.",
|
||||
"const": 1
|
||||
},
|
||||
{
|
||||
"description": "Field deleted because the specified expiration time is in the past.",
|
||||
"const": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "seconds",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "condition",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "nx",
|
||||
"type": "pure-token",
|
||||
"token": "NX"
|
||||
},
|
||||
{
|
||||
"name": "xx",
|
||||
"type": "pure-token",
|
||||
"token": "XX"
|
||||
},
|
||||
{
|
||||
"name": "gt",
|
||||
"type": "pure-token",
|
||||
"token": "GT"
|
||||
},
|
||||
{
|
||||
"name": "lt",
|
||||
"type": "pure-token",
|
||||
"token": "LT"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
{
|
||||
"HEXPIREAT": {
|
||||
"summary": "Set expiry for hash field using an absolute Unix timestamp (seconds)",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -6,
|
||||
"function": "hexpireatCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of results. Returns empty array if the key does not exist.",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The field does not exist.",
|
||||
"const": -2
|
||||
},
|
||||
{
|
||||
"description": "Specified NX | XX | GT | LT condition not met",
|
||||
"const": 0
|
||||
},
|
||||
{
|
||||
"description": "Expiration time was set or updated.",
|
||||
"const": 1
|
||||
},
|
||||
{
|
||||
"description": "Field deleted because the specified expiration time is in the past.",
|
||||
"const": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "unix-time-seconds",
|
||||
"type": "unix-time"
|
||||
},
|
||||
{
|
||||
"name": "condition",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "nx",
|
||||
"type": "pure-token",
|
||||
"token": "NX"
|
||||
},
|
||||
{
|
||||
"name": "xx",
|
||||
"type": "pure-token",
|
||||
"token": "XX"
|
||||
},
|
||||
{
|
||||
"name": "gt",
|
||||
"type": "pure-token",
|
||||
"token": "GT"
|
||||
},
|
||||
{
|
||||
"name": "lt",
|
||||
"type": "pure-token",
|
||||
"token": "LT"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
{
|
||||
"HEXPIRETIME": {
|
||||
"summary": "Returns the expiration time of a hash field as a Unix timestamp, in seconds.",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -5,
|
||||
"function": "hexpiretimeCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of results. Returns empty array if the key does not exist.",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The field does not exist.",
|
||||
"const": -2
|
||||
},
|
||||
{
|
||||
"description": "The field exists but has no associated expire.",
|
||||
"const": -1
|
||||
},
|
||||
{
|
||||
"description": "Expiration Unix timestamp in seconds.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"HPERSIST": {
|
||||
"summary": "Removes the expiration time for each specified field",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -5,
|
||||
"function": "hpersistCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of results. Returns empty array if the key does not exist.",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The field does not exist.",
|
||||
"const": -2
|
||||
},
|
||||
{
|
||||
"description": "The field exists but has no associated expire.",
|
||||
"const": -1
|
||||
},
|
||||
{
|
||||
"description": "Expiration time was removed",
|
||||
"const": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
{
|
||||
"HPEXPIRE": {
|
||||
"summary": "Set expiry for hash field using relative time to expire (milliseconds)",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -6,
|
||||
"function": "hpexpireCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of results. Returns empty array if the key does not exist.",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The field does not exist.",
|
||||
"const": -2
|
||||
},
|
||||
{
|
||||
"description": "Specified NX | XX | GT | LT condition not met",
|
||||
"const": 0
|
||||
},
|
||||
{
|
||||
"description": "Expiration time was set or updated.",
|
||||
"const": 1
|
||||
},
|
||||
{
|
||||
"description": "Field deleted because the specified expiration time is in the past.",
|
||||
"const": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "milliseconds",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "condition",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "nx",
|
||||
"type": "pure-token",
|
||||
"token": "NX"
|
||||
},
|
||||
{
|
||||
"name": "xx",
|
||||
"type": "pure-token",
|
||||
"token": "XX"
|
||||
},
|
||||
{
|
||||
"name": "gt",
|
||||
"type": "pure-token",
|
||||
"token": "GT"
|
||||
},
|
||||
{
|
||||
"name": "lt",
|
||||
"type": "pure-token",
|
||||
"token": "LT"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
{
|
||||
"HPEXPIREAT": {
|
||||
"summary": "Set expiry for hash field using an absolute Unix timestamp (milliseconds)",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -6,
|
||||
"function": "hpexpireatCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of results. Returns empty array if the key does not exist.",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The field does not exist.",
|
||||
"const": -2
|
||||
},
|
||||
{
|
||||
"description": "Specified NX | XX | GT | LT condition not met",
|
||||
"const": 0
|
||||
},
|
||||
{
|
||||
"description": "Expiration time was set or updated.",
|
||||
"const": 1
|
||||
},
|
||||
{
|
||||
"description": "Field deleted because the specified expiration time is in the past.",
|
||||
"const": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "unix-time-milliseconds",
|
||||
"type": "unix-time"
|
||||
},
|
||||
{
|
||||
"name": "condition",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "nx",
|
||||
"type": "pure-token",
|
||||
"token": "NX"
|
||||
},
|
||||
{
|
||||
"name": "xx",
|
||||
"type": "pure-token",
|
||||
"token": "XX"
|
||||
},
|
||||
{
|
||||
"name": "gt",
|
||||
"type": "pure-token",
|
||||
"token": "GT"
|
||||
},
|
||||
{
|
||||
"name": "lt",
|
||||
"type": "pure-token",
|
||||
"token": "LT"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
{
|
||||
"HPEXPIRETIME": {
|
||||
"summary": "Returns the expiration time of a hash field as a Unix timestamp, in msec.",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -5,
|
||||
"function": "hpexpiretimeCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of results. Returns empty array if the key does not exist.",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The field does not exist.",
|
||||
"const": -2
|
||||
},
|
||||
{
|
||||
"description": "The field exists but has no associated expire.",
|
||||
"const": -1
|
||||
},
|
||||
{
|
||||
"description": "Expiration Unix timestamp in milliseconds.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
{
|
||||
"HPTTL": {
|
||||
"summary": "Returns the TTL in milliseconds of a hash field.",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -5,
|
||||
"function": "hpttlCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of results. Returns empty array if the key does not exist.",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The field does not exist.",
|
||||
"const": -2
|
||||
},
|
||||
{
|
||||
"description": "The field exists but has no associated expire.",
|
||||
"const": -1
|
||||
},
|
||||
{
|
||||
"description": "TTL in milliseconds.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -56,12 +56,6 @@
|
||||
"name": "count",
|
||||
"type": "integer",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"token": "NOVALUES",
|
||||
"name": "novalues",
|
||||
"type": "pure-token",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
@@ -75,7 +69,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "list of key/value pairs from the hash where each even element is the key, and each odd element is the value, or when novalues option is on, a list of keys from the hash",
|
||||
"description": "list of key/value pairs from the hash where each even element is the key, and each odd element is the value",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
{
|
||||
"HTTL": {
|
||||
"summary": "Returns the TTL in seconds of a hash field.",
|
||||
"complexity": "O(N) where N is the number of specified fields",
|
||||
"group": "hash",
|
||||
"since": "7.4.0",
|
||||
"arity": -5,
|
||||
"function": "httlCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"HASH"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of results. Returns empty array if the key does not exist.",
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"maxItems": 4294967295,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The field does not exist.",
|
||||
"const": -2
|
||||
},
|
||||
{
|
||||
"description": "The field exists but has no associated expire.",
|
||||
"const": -1
|
||||
},
|
||||
{
|
||||
"description": "TTL in seconds.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -47,18 +47,9 @@
|
||||
"functions.caches": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overhead.db.hashtable.lut": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overhead.db.hashtable.rehashing": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overhead.total": {
|
||||
"type": "integer"
|
||||
},
|
||||
"db.dict.rehashing.count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"keys.count": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -83,9 +74,6 @@
|
||||
"allocator.resident": {
|
||||
"type": "integer"
|
||||
},
|
||||
"allocator.muzzy": {
|
||||
"type": "integer"
|
||||
},
|
||||
"allocator-fragmentation.ratio": {
|
||||
"type": "number"
|
||||
},
|
||||
@@ -112,7 +100,7 @@
|
||||
}
|
||||
},
|
||||
"patternProperties": {
|
||||
"^db\\.\\d+$": {
|
||||
"^db.": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"overhead.hashtable.main": {
|
||||
@@ -120,6 +108,9 @@
|
||||
},
|
||||
"overhead.hashtable.expires": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overhead.hashtable.slot-to-keys": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"OW",
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"arity": 3,
|
||||
"function": "waitCommand",
|
||||
"command_flags": [
|
||||
"BLOCKING"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"arity": 4,
|
||||
"function": "waitaofCommand",
|
||||
"command_flags": [
|
||||
"BLOCKING"
|
||||
"NOSCRIPT"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -72,9 +72,8 @@
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "entriesread",
|
||||
"display": "entries-read",
|
||||
"token": "ENTRIESREAD",
|
||||
"name": "entries-read",
|
||||
"type": "integer",
|
||||
"optional": true
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"history": [
|
||||
[
|
||||
"7.2.0",
|
||||
"Added the `inactive` field, and changed the meaning of `idle`."
|
||||
"Added the `inactive` field."
|
||||
]
|
||||
],
|
||||
"command_flags": [
|
||||
|
||||
@@ -292,8 +292,7 @@
|
||||
},
|
||||
"seen-time": {
|
||||
"description": "timestamp of the last interaction attempt of the consumer",
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"type": "integer"
|
||||
},
|
||||
"pel-count": {
|
||||
"description": "number of unacknowledged entries that belong to the consumer",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"get_keys_function": "xreadGetKeys",
|
||||
"command_flags": [
|
||||
"BLOCKING",
|
||||
"READONLY"
|
||||
"READONLY",
|
||||
"BLOCKING"
|
||||
],
|
||||
"acl_categories": [
|
||||
"STREAM"
|
||||
|
||||
+38
-38
@@ -1,12 +1,31 @@
|
||||
/* Configuration file parsing and CONFIG GET/SET commands implementation.
|
||||
*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -1101,22 +1120,12 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
if (fp == NULL && errno != ENOENT) return NULL;
|
||||
|
||||
struct redis_stat sb;
|
||||
if (fp && redis_fstat(fileno(fp),&sb) == -1) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
if (fp && redis_fstat(fileno(fp),&sb) == -1) return NULL;
|
||||
|
||||
int linenum = -1;
|
||||
struct rewriteConfigState *state = rewriteConfigCreateState();
|
||||
|
||||
if (fp == NULL) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (sb.st_size == 0) {
|
||||
fclose(fp);
|
||||
return state;
|
||||
}
|
||||
if (fp == NULL || sb.st_size == 0) return state;
|
||||
|
||||
/* Load the file content */
|
||||
sds config = sdsnewlen(SDS_NOINIT,sb.st_size);
|
||||
@@ -2378,7 +2387,7 @@ static int isValidShutdownOnSigFlags(int val, const char **err) {
|
||||
static int isValidAnnouncedNodename(char *val,const char **err) {
|
||||
if (!(isValidAuxString(val,sdslen(val)))) {
|
||||
*err = "Announced human node name contained invalid character";
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -2460,12 +2469,6 @@ static int updatePort(const char **err) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int updateDefragConfiguration(const char **err) {
|
||||
UNUSED(err);
|
||||
server.active_defrag_configuration_changed = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int updateJemallocBgThread(const char **err) {
|
||||
UNUSED(err);
|
||||
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
|
||||
@@ -2526,9 +2529,9 @@ static int updateAofAutoGCEnabled(const char **err) {
|
||||
static int updateSighandlerEnabled(const char **err) {
|
||||
UNUSED(err);
|
||||
if (server.crashlog_enabled)
|
||||
setupSigSegvHandler();
|
||||
setupSignalHandlers();
|
||||
else
|
||||
removeSigSegvHandlers();
|
||||
removeSignalHandlers();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -3094,7 +3097,6 @@ standardConfig static_configs[] = {
|
||||
createBoolConfig("latency-tracking", NULL, MODIFIABLE_CONFIG, server.latency_tracking_enabled, 1, NULL, NULL),
|
||||
createBoolConfig("aof-disable-auto-gc", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, server.aof_disable_auto_gc, 0, NULL, updateAofAutoGCEnabled),
|
||||
createBoolConfig("replica-ignore-disk-write-errors", NULL, MODIFIABLE_CONFIG, server.repl_ignore_disk_write_error, 0, NULL, NULL),
|
||||
createBoolConfig("hide-user-data-from-log", NULL, MODIFIABLE_CONFIG, server.hide_user_data_from_log, 0, NULL, NULL),
|
||||
createBoolConfig("lua-enable-deprecated-api", NULL, IMMUTABLE_CONFIG | HIDDEN_CONFIG, server.lua_enable_deprecated_api, 0, NULL, NULL),
|
||||
|
||||
/* String Configs */
|
||||
@@ -3111,10 +3113,10 @@ standardConfig static_configs[] = {
|
||||
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG | PROTECTED_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),
|
||||
createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_filename, "appendonly.aof", isValidAOFfilename, NULL),
|
||||
createStringConfig("appenddirname", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_dirname, "appendonlydir", isValidAOFdirname, NULL),
|
||||
createStringConfig("server-cpulist", "server_cpulist", IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.server_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("bio-cpulist", "bio_cpulist", IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("aof-rewrite-cpulist", "aof_rewrite_cpulist", IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("bgsave-cpulist", "bgsave_cpulist", IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("server_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.server_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.ignore_warnings, "", NULL, NULL),
|
||||
createStringConfig("proc-title-template", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate),
|
||||
createStringConfig("bind-source-addr", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bind_source_addr, NULL, NULL, NULL),
|
||||
@@ -3155,15 +3157,15 @@ standardConfig static_configs[] = {
|
||||
createIntConfig("list-max-listpack-size", "list-max-ziplist-size", MODIFIABLE_CONFIG, INT_MIN, INT_MAX, server.list_max_listpack_size, -2, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("tcp-keepalive", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tcpkeepalive, 300, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("cluster-migration-barrier", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_migration_barrier, 1, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("active-defrag-cycle-min", NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_min, 1, INTEGER_CONFIG, NULL, updateDefragConfiguration), /* Default: 1% CPU min (at lower threshold) */
|
||||
createIntConfig("active-defrag-cycle-max", NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_max, 25, INTEGER_CONFIG, NULL, updateDefragConfiguration), /* Default: 25% CPU max (at upper threshold) */
|
||||
createIntConfig("active-defrag-cycle-min", NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_min, 1, INTEGER_CONFIG, NULL, NULL), /* Default: 1% CPU min (at lower threshold) */
|
||||
createIntConfig("active-defrag-cycle-max", NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_max, 25, INTEGER_CONFIG, NULL, NULL), /* Default: 25% CPU max (at upper threshold) */
|
||||
createIntConfig("active-defrag-threshold-lower", NULL, MODIFIABLE_CONFIG, 0, 1000, server.active_defrag_threshold_lower, 10, INTEGER_CONFIG, NULL, NULL), /* Default: don't defrag when fragmentation is below 10% */
|
||||
createIntConfig("active-defrag-threshold-upper", NULL, MODIFIABLE_CONFIG, 0, 1000, server.active_defrag_threshold_upper, 100, INTEGER_CONFIG, NULL, updateDefragConfiguration), /* Default: maximum defrag force at 100% fragmentation */
|
||||
createIntConfig("active-defrag-threshold-upper", NULL, MODIFIABLE_CONFIG, 0, 1000, server.active_defrag_threshold_upper, 100, INTEGER_CONFIG, NULL, NULL), /* Default: maximum defrag force at 100% fragmentation */
|
||||
createIntConfig("lfu-log-factor", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_log_factor, 10, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("lfu-decay-time", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_decay_time, 1, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("replica-priority", "slave-priority", MODIFIABLE_CONFIG, 0, INT_MAX, server.slave_priority, 100, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("repl-diskless-sync-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, 64, server.maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("maxmemory-eviction-tenacity", NULL, MODIFIABLE_CONFIG, 0, 100, server.maxmemory_eviction_tenacity, 10, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */
|
||||
createIntConfig("replica-announce-port", "slave-announce-port", MODIFIABLE_CONFIG, 0, 65535, server.slave_announce_port, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
@@ -3189,8 +3191,6 @@ standardConfig static_configs[] = {
|
||||
createUIntConfig("maxclients", NULL, MODIFIABLE_CONFIG, 1, UINT_MAX, server.maxclients, 10000, INTEGER_CONFIG, NULL, updateMaxclients),
|
||||
createUIntConfig("unixsocketperm", NULL, IMMUTABLE_CONFIG, 0, 0777, server.unixsocketperm, 0, OCTAL_CONFIG, NULL, NULL),
|
||||
createUIntConfig("socket-mark-id", NULL, IMMUTABLE_CONFIG, 0, UINT_MAX, server.socket_mark_id, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createUIntConfig("max-new-connections-per-cycle", NULL, MODIFIABLE_CONFIG, 1, 1000, server.max_new_conns_per_cycle, 10, INTEGER_CONFIG, NULL, NULL),
|
||||
createUIntConfig("max-new-tls-connections-per-cycle", NULL, MODIFIABLE_CONFIG, 1, 1000, server.max_new_tls_conns_per_cycle, 1, INTEGER_CONFIG, NULL, NULL),
|
||||
#ifdef LOG_REQ_RES
|
||||
createUIntConfig("client-default-resp", NULL, IMMUTABLE_CONFIG | HIDDEN_CONFIG, 2, 3, server.client_default_resp, 2, INTEGER_CONFIG, NULL, NULL),
|
||||
#endif
|
||||
@@ -3244,10 +3244,10 @@ standardConfig static_configs[] = {
|
||||
createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-cert-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-key-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-key-file-pass", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-client-cert-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-client-key-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-client-key-file-pass", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-client-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-dh-params-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-ca-cert-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, applyTlsCfg),
|
||||
createStringConfig("tls-ca-cert-dir", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, applyTlsCfg),
|
||||
|
||||
+25
-6
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __CONFIG_H
|
||||
@@ -75,9 +96,7 @@
|
||||
#endif
|
||||
|
||||
/* Test for accept4() */
|
||||
#if defined(__linux__) || defined(OpenBSD5_7) || \
|
||||
(__FreeBSD__ >= 10 || __FreeBSD_version >= 1000000) || \
|
||||
(defined(NetBSD8_0) || __NetBSD_Version__ >= 800000000)
|
||||
#ifdef __linux__
|
||||
#define HAVE_ACCEPT4 1
|
||||
#endif
|
||||
|
||||
|
||||
+25
-3
@@ -1,10 +1,31 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019-Present, Redis Ltd.
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __REDIS_CONNECTION_H
|
||||
@@ -19,6 +40,7 @@
|
||||
|
||||
#define CONN_INFO_LEN 32
|
||||
#define CONN_ADDR_STR_LEN 128 /* Similar to INET6_ADDRSTRLEN, hoping to handle other protocols. */
|
||||
#define MAX_ACCEPTS_PER_CALL 1000
|
||||
|
||||
struct aeEventLoop;
|
||||
typedef struct connection connection;
|
||||
|
||||
+24
-3
@@ -1,10 +1,31 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019-Present, Redis Ltd.
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __REDIS_CONNHELPERS_H
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
* Copyright 2001-2010 Georges Menie (www.menie.org)
|
||||
* Copyright 2010-current Redis Ltd.
|
||||
* Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style)
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
* The array indexes are slot numbers, so that given a desired slot, this string is guaranteed
|
||||
* to make redis cluster route a request to the shard holding this slot
|
||||
*/
|
||||
typedef char crc16_alphastring[4];
|
||||
|
||||
const crc16_alphastring crc16_slot_table[] = {
|
||||
const char *crc16_slot_table[] = {
|
||||
"06S", "Qi", "5L5", "4Iu", "4gY", "460", "1Y7", "1LV", "0QG", "ru", "7Ok", "4ji", "4DE", "65n", "2JH", "I8", "F9", "SX", "7nF", "4KD",
|
||||
"4eh", "6PK", "2ke", "1Ng", "0Sv", "4L", "491", "4hX", "4Ft", "5C4", "2Hy", "09R", "021", "0cX", "4Xv", "6mU", "6Cy", "42R", "0Mt", "nF",
|
||||
"cv", "1Pe", "5kK", "6NI", "74L", "4UF", "0nh", "MZ", "2TJ", "0ai", "4ZG", "6od", "6AH", "40c", "0OE", "lw", "aG", "0Bu", "5iz", "6Lx",
|
||||
|
||||
+128
-478
@@ -1,11 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2020, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2020, Redis Labs, Inc
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -16,8 +36,6 @@
|
||||
#include "quicklist.h"
|
||||
#include "fpconv_dtoa.h"
|
||||
#include "cluster.h"
|
||||
#include "threads_mngr.h"
|
||||
#include "script.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <signal.h>
|
||||
@@ -48,16 +66,12 @@ typedef ucontext_t sigcontext_t;
|
||||
/* Globals */
|
||||
static int bug_report_start = 0; /* True if bug report header was already logged. */
|
||||
static pthread_mutex_t bug_report_start_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
/* Mutex for a case when two threads crash at the same time. */
|
||||
static pthread_mutex_t signal_handler_lock;
|
||||
static pthread_mutexattr_t signal_handler_lock_attr;
|
||||
static volatile int signal_handler_lock_initialized = 0;
|
||||
|
||||
/* Forward declarations */
|
||||
int bugReportStart(void);
|
||||
void bugReportStart(void);
|
||||
void printCrashReport(void);
|
||||
void bugReportEnd(int killViaSignal, int sig);
|
||||
void logStackTrace(void *eip, int uplevel, int current_thread);
|
||||
void sigalrmSignalHandler(int sig, siginfo_t *info, void *secret);
|
||||
void logStackTrace(void *eip, int uplevel);
|
||||
|
||||
/* ================================= Debugging ============================== */
|
||||
|
||||
@@ -203,22 +217,17 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
|
||||
}
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
hashTypeIterator *hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi, 0) != C_ERR) {
|
||||
while (hashTypeNext(hi) != C_ERR) {
|
||||
unsigned char eledigest[20];
|
||||
sds sdsele;
|
||||
|
||||
/* field */
|
||||
memset(eledigest,0,20);
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
/* val */
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
/* hash-field expiration (HFE) */
|
||||
if (hi->expire_time != EB_EXPIRE_TIME_INVALID)
|
||||
xorDigest(eledigest,"!!hexpire!!",11);
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
hashTypeReleaseIterator(hi);
|
||||
@@ -267,6 +276,7 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
|
||||
* a different digest. */
|
||||
void computeDatasetDigest(unsigned char *final) {
|
||||
unsigned char digest[20];
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
uint32_t aux;
|
||||
@@ -275,16 +285,17 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
if (kvstoreSize(db->keys) == 0)
|
||||
continue;
|
||||
kvstoreIterator *kvs_it = kvstoreIteratorInit(db->keys);
|
||||
|
||||
/* hash the DB id, so the same dataset moved in a different DB will lead to a different digest */
|
||||
if (dictSize(db->dict) == 0) continue;
|
||||
di = dictGetSafeIterator(db->dict);
|
||||
|
||||
/* hash the DB id, so the same dataset moved in a different
|
||||
* DB will lead to a different digest */
|
||||
aux = htonl(j);
|
||||
mixDigest(final,&aux,sizeof(aux));
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
while((de = kvstoreIteratorNext(kvs_it)) != NULL) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key;
|
||||
robj *keyobj, *o;
|
||||
|
||||
@@ -301,7 +312,7 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
xorDigest(final,digest,20);
|
||||
decrRefCount(keyobj);
|
||||
}
|
||||
kvstoreIteratorRelease(kvs_it);
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,9 +464,9 @@ void debugCommand(client *c) {
|
||||
"SEGFAULT",
|
||||
" Crash the server with sigsegv.",
|
||||
"SET-ACTIVE-EXPIRE <0|1>",
|
||||
" Setting it to 0 disables expiring keys (and hash-fields) in background ",
|
||||
" when they are not accessed (otherwise the Redis behavior). Setting it",
|
||||
" to 1 reenables back the default.",
|
||||
" Setting it to 0 disables expiring keys in background when they are not",
|
||||
" accessed (otherwise the Redis behavior). Setting it to 1 reenables back the",
|
||||
" default.",
|
||||
"QUICKLIST-PACKED-THRESHOLD <size>",
|
||||
" Sets the threshold for elements to be inserted as plain vs packed nodes",
|
||||
" Default value is 1GB, allows values up to 4GB. Setting to 0 restores to default.",
|
||||
@@ -482,13 +493,11 @@ void debugCommand(client *c) {
|
||||
" In case RESET is provided the peak reset time will be restored to the default value",
|
||||
"REPLYBUFFER RESIZING <0|1>",
|
||||
" Enable or disable the reply buffer resize cron job",
|
||||
"DICT-RESIZING <0|1>",
|
||||
" Enable or disable the main dict and expire dict resizing.",
|
||||
"SCRIPT <LIST|<sha>>",
|
||||
" Output SHA and content of all scripts or of a specific script with its SHA.",
|
||||
"CLUSTERLINK KILL <to|from|all> <node-id>",
|
||||
" Kills the link based on the direction to/from (both) with the provided node." ,
|
||||
NULL
|
||||
};
|
||||
addExtendedReplyHelp(c, help, clusterDebugCommandExtendedHelp());
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
||||
/* Compiler gives warnings about writing to a random address
|
||||
* e.g "*((char*)-1) = 'x';". As a workaround, we map a read-only area
|
||||
@@ -596,7 +605,7 @@ NULL
|
||||
robj *val;
|
||||
char *strenc;
|
||||
|
||||
if ((de = dbFind(c->db, c->argv[2]->ptr)) == NULL) {
|
||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||
addReplyErrorObject(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
@@ -648,7 +657,7 @@ NULL
|
||||
robj *val;
|
||||
sds key;
|
||||
|
||||
if ((de = dbFind(c->db, c->argv[2]->ptr)) == NULL) {
|
||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||
addReplyErrorObject(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
@@ -674,14 +683,10 @@ NULL
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr))
|
||||
== NULL) return;
|
||||
|
||||
if (o->encoding != OBJ_ENCODING_LISTPACK && o->encoding != OBJ_ENCODING_LISTPACK_EX) {
|
||||
if (o->encoding != OBJ_ENCODING_LISTPACK) {
|
||||
addReplyError(c,"Not a listpack encoded object.");
|
||||
} else {
|
||||
if (o->encoding == OBJ_ENCODING_LISTPACK)
|
||||
lpRepr(o->ptr);
|
||||
else if (o->encoding == OBJ_ENCODING_LISTPACK_EX)
|
||||
lpRepr(((listpackEx*)o->ptr)->lp);
|
||||
|
||||
lpRepr(o->ptr);
|
||||
addReplyStatus(c,"Listpack structure printed on stdout");
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"quicklist") && (c->argc == 3 || c->argc == 4)) {
|
||||
@@ -708,12 +713,7 @@ NULL
|
||||
if (getPositiveLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (server.loading || server.async_loading) {
|
||||
addReplyErrorObject(c, shared.loadingerr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dbExpand(c->db, keys, 1) == C_ERR) {
|
||||
if (dictTryExpand(c->db->dict, keys) != DICT_OK) {
|
||||
addReplyError(c, "OOM in dictTryExpand");
|
||||
return;
|
||||
}
|
||||
@@ -761,7 +761,7 @@ NULL
|
||||
/* We don't use lookupKey because a debug command should
|
||||
* work on logically expired keys */
|
||||
dictEntry *de;
|
||||
robj *o = ((de = dbFind(c->db, c->argv[j]->ptr)) == NULL) ? NULL : dictGetVal(de);
|
||||
robj *o = ((de = dictFind(c->db->dict,c->argv[j]->ptr)) == NULL) ? NULL : dictGetVal(de);
|
||||
if (o) xorObjectDigest(c->db,c->argv[j],digest,o);
|
||||
|
||||
sds d = sdsempty();
|
||||
@@ -849,7 +849,7 @@ NULL
|
||||
{
|
||||
int memerr;
|
||||
unsigned long long sz = memtoull((const char *)c->argv[2]->ptr, &memerr);
|
||||
if (memerr || !quicklistSetPackedThreshold(sz)) {
|
||||
if (memerr || !quicklistisSetPackedThreshold(sz)) {
|
||||
addReplyError(c, "argument must be a memory value bigger than 1 and smaller than 4gb");
|
||||
} else {
|
||||
addReply(c,shared.ok);
|
||||
@@ -905,11 +905,11 @@ NULL
|
||||
full = 1;
|
||||
|
||||
stats = sdscatprintf(stats,"[Dictionary HT]\n");
|
||||
kvstoreGetStats(server.db[dbid].keys, buf, sizeof(buf), full);
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].dict,full);
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
stats = sdscatprintf(stats,"[Expires HT]\n");
|
||||
kvstoreGetStats(server.db[dbid].expires, buf, sizeof(buf), full);
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].expires,full);
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
addReplyVerbatim(c,stats,sdslen(stats),"txt");
|
||||
@@ -1015,33 +1015,34 @@ NULL
|
||||
return;
|
||||
}
|
||||
addReply(c, shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr, "dict-resizing") && c->argc == 3) {
|
||||
server.dict_resizing = atoi(c->argv[2]->ptr);
|
||||
addReply(c, shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"script") && c->argc == 3) {
|
||||
if (!strcasecmp(c->argv[2]->ptr,"list")) {
|
||||
dictIterator *di = dictGetIterator(evalScriptsDict());
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
luaScript *script = dictGetVal(de);
|
||||
sds *sha = dictGetKey(de);
|
||||
serverLog(LL_WARNING, "SCRIPT SHA: %s\n%s", (char*)sha, (char*)script->body->ptr);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else if (sdslen(c->argv[2]->ptr) == 40) {
|
||||
dictEntry *de;
|
||||
if ((de = dictFind(evalScriptsDict(), c->argv[2]->ptr)) == NULL) {
|
||||
addReplyErrorObject(c, shared.noscripterr);
|
||||
return;
|
||||
}
|
||||
luaScript *script = dictGetVal(de);
|
||||
serverLog(LL_WARNING, "SCRIPT SHA: %s\n%s", (char*)c->argv[2]->ptr, (char*)script->body->ptr);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
} else if(!strcasecmp(c->argv[1]->ptr,"CLUSTERLINK") &&
|
||||
!strcasecmp(c->argv[2]->ptr,"KILL") &&
|
||||
c->argc == 5) {
|
||||
if (!server.cluster_enabled) {
|
||||
addReplyError(c, "Debug option only available for cluster mode enabled setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the node. */
|
||||
clusterNode *n = clusterLookupNode(c->argv[4]->ptr, sdslen(c->argv[4]->ptr));
|
||||
if (!n) {
|
||||
addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[4]->ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Terminate the link based on the direction or all. */
|
||||
if (!strcasecmp(c->argv[3]->ptr,"from")) {
|
||||
freeClusterLink(n->inbound_link);
|
||||
} else if (!strcasecmp(c->argv[3]->ptr,"to")) {
|
||||
freeClusterLink(n->link);
|
||||
} else if (!strcasecmp(c->argv[3]->ptr,"all")) {
|
||||
freeClusterLink(n->link);
|
||||
freeClusterLink(n->inbound_link);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown direction %s", (char*) c->argv[3]->ptr);
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
} else if(!handleDebugClusterCommand(c)) {
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
@@ -1049,31 +1050,23 @@ NULL
|
||||
|
||||
/* =========================== Crash handling ============================== */
|
||||
|
||||
__attribute__ ((noinline))
|
||||
void _serverAssert(const char *estr, const char *file, int line) {
|
||||
int new_report = bugReportStart();
|
||||
serverLog(LL_WARNING,"=== %sASSERTION FAILED ===", new_report ? "" : "RECURSIVE ");
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,"=== ASSERTION FAILED ===");
|
||||
serverLog(LL_WARNING,"==> %s:%d '%s' is not true",file,line,estr);
|
||||
|
||||
if (server.crashlog_enabled) {
|
||||
#ifdef HAVE_BACKTRACE
|
||||
logStackTrace(NULL, 1, 0);
|
||||
logStackTrace(NULL, 1);
|
||||
#endif
|
||||
/* If this was a recursive assertion, it what most likely generated
|
||||
* from printCrashReport. */
|
||||
if (new_report) printCrashReport();
|
||||
printCrashReport();
|
||||
}
|
||||
|
||||
// remove the signal handler so on abort() we will output the crash report.
|
||||
removeSigSegvHandlers();
|
||||
removeSignalHandlers();
|
||||
bugReportEnd(0, 0);
|
||||
}
|
||||
|
||||
/* Returns the amount of client's command arguments we allow logging */
|
||||
int clientArgsToLog(const client *c) {
|
||||
return server.hide_user_data_from_log ? 1 : c->argc;
|
||||
}
|
||||
|
||||
void _serverAssertPrintClientInfo(const client *c) {
|
||||
int j;
|
||||
char conninfo[CONN_INFO_LEN];
|
||||
@@ -1084,10 +1077,6 @@ void _serverAssertPrintClientInfo(const client *c) {
|
||||
serverLog(LL_WARNING,"client->conn = %s", connGetInfo(c->conn, conninfo, sizeof(conninfo)));
|
||||
serverLog(LL_WARNING,"client->argc = %d", c->argc);
|
||||
for (j=0; j < c->argc; j++) {
|
||||
if (j >= clientArgsToLog(c)) {
|
||||
serverLog(LL_WARNING,"client->argv[%d] = *redacted*",j);
|
||||
continue;
|
||||
}
|
||||
char buf[128];
|
||||
char *arg;
|
||||
|
||||
@@ -1127,7 +1116,7 @@ void serverLogObjectDebugInfo(const robj *o) {
|
||||
} else if (o->type == OBJ_SET) {
|
||||
serverLog(LL_WARNING,"Set size: %d", (int) setTypeSize(o));
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
serverLog(LL_WARNING,"Hash size: %d", (int) hashTypeLength(o, 0));
|
||||
serverLog(LL_WARNING,"Hash size: %d", (int) hashTypeLength(o));
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
serverLog(LL_WARNING,"Sorted set size: %d", (int) zsetLength(o));
|
||||
if (o->encoding == OBJ_ENCODING_SKIPLIST)
|
||||
@@ -1150,7 +1139,6 @@ void _serverAssertWithInfo(const client *c, const robj *o, const char *estr, con
|
||||
_serverAssert(estr,file,line);
|
||||
}
|
||||
|
||||
__attribute__ ((noinline))
|
||||
void _serverPanic(const char *file, int line, const char *msg, ...) {
|
||||
va_list ap;
|
||||
va_start(ap,msg);
|
||||
@@ -1158,37 +1146,31 @@ void _serverPanic(const char *file, int line, const char *msg, ...) {
|
||||
vsnprintf(fmtmsg,sizeof(fmtmsg),msg,ap);
|
||||
va_end(ap);
|
||||
|
||||
int new_report = bugReportStart();
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,"------------------------------------------------");
|
||||
serverLog(LL_WARNING,"!!! Software Failure. Press left mouse button to continue");
|
||||
serverLog(LL_WARNING,"Guru Meditation: %s #%s:%d",fmtmsg,file,line);
|
||||
|
||||
if (server.crashlog_enabled) {
|
||||
#ifdef HAVE_BACKTRACE
|
||||
logStackTrace(NULL, 1, 0);
|
||||
logStackTrace(NULL, 1);
|
||||
#endif
|
||||
/* If this was a recursive panic, it what most likely generated
|
||||
* from printCrashReport. */
|
||||
if (new_report) printCrashReport();
|
||||
printCrashReport();
|
||||
}
|
||||
|
||||
// remove the signal handler so on abort() we will output the crash report.
|
||||
removeSigSegvHandlers();
|
||||
removeSignalHandlers();
|
||||
bugReportEnd(0, 0);
|
||||
}
|
||||
|
||||
/* Start a bug report, returning 1 if this is the first time this function was called, 0 otherwise. */
|
||||
int bugReportStart(void) {
|
||||
void bugReportStart(void) {
|
||||
pthread_mutex_lock(&bug_report_start_mutex);
|
||||
if (bug_report_start == 0) {
|
||||
serverLogRaw(LL_WARNING|LL_RAW,
|
||||
"\n\n=== REDIS BUG REPORT START: Cut & paste starting from here ===\n");
|
||||
bug_report_start = 1;
|
||||
pthread_mutex_unlock(&bug_report_start_mutex);
|
||||
return 1;
|
||||
}
|
||||
pthread_mutex_unlock(&bug_report_start_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
@@ -1286,10 +1268,6 @@ static void* getAndSetMcontextEip(ucontext_t *uc, void *eip) {
|
||||
|
||||
REDIS_NO_SANITIZE("address")
|
||||
void logStackContent(void **sp) {
|
||||
if (server.hide_user_data_from_log) {
|
||||
serverLog(LL_NOTICE,"hide-user-data-from-log is on, skip logging stack content to avoid spilling PII.");
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 15; i >= 0; i--) {
|
||||
unsigned long addr = (unsigned long) sp+i;
|
||||
@@ -1837,132 +1815,24 @@ void closeDirectLogFiledes(int fd) {
|
||||
if (!log_to_stdout) close(fd);
|
||||
}
|
||||
|
||||
#if defined(HAVE_BACKTRACE) && defined(__linux__)
|
||||
static int stacktrace_pipe[2] = {0};
|
||||
static void setupStacktracePipe(void) {
|
||||
if (-1 == anetPipe(stacktrace_pipe, O_CLOEXEC | O_NONBLOCK, O_CLOEXEC | O_NONBLOCK)) {
|
||||
serverLog(LL_WARNING, "setupStacktracePipe failed: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void setupStacktracePipe(void) {/* we don't need a pipe to write the stacktraces */}
|
||||
#endif
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#define BACKTRACE_MAX_SIZE 100
|
||||
|
||||
#ifdef __linux__
|
||||
#if !defined(_GNU_SOURCE)
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <sys/prctl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#define TIDS_MAX_SIZE 50
|
||||
static size_t get_ready_to_signal_threads_tids(int sig_num, pid_t tids[TIDS_MAX_SIZE]);
|
||||
|
||||
typedef struct {
|
||||
char thread_name[16];
|
||||
int trace_size;
|
||||
pid_t tid;
|
||||
void *trace[BACKTRACE_MAX_SIZE];
|
||||
} stacktrace_data;
|
||||
|
||||
__attribute__ ((noinline)) static void collect_stacktrace_data(void) {
|
||||
stacktrace_data trace_data = {{0}};
|
||||
|
||||
/* Get the stack trace first! */
|
||||
trace_data.trace_size = backtrace(trace_data.trace, BACKTRACE_MAX_SIZE);
|
||||
|
||||
/* get the thread name */
|
||||
prctl(PR_GET_NAME, trace_data.thread_name);
|
||||
|
||||
/* get the thread id */
|
||||
trace_data.tid = syscall(SYS_gettid);
|
||||
|
||||
/* Send the output to the main process*/
|
||||
if (write(stacktrace_pipe[1], &trace_data, sizeof(trace_data)) == -1) {/* Avoid warning. */};
|
||||
}
|
||||
|
||||
__attribute__ ((noinline))
|
||||
static void writeStacktraces(int fd, int uplevel) {
|
||||
/* get the list of all the process's threads that don't block or ignore the THREADS_SIGNAL */
|
||||
pid_t tids[TIDS_MAX_SIZE];
|
||||
size_t len_tids = get_ready_to_signal_threads_tids(THREADS_SIGNAL, tids);
|
||||
if (!len_tids) {
|
||||
serverLogRawFromHandler(LL_WARNING, "writeStacktraces(): Failed to get the process's threads.");
|
||||
}
|
||||
|
||||
char buff[PIPE_BUF];
|
||||
/* Clear the stacktraces pipe */
|
||||
while (read(stacktrace_pipe[0], &buff, sizeof(buff)) > 0) {}
|
||||
|
||||
/* ThreadsManager_runOnThreads returns 0 if it is already running */
|
||||
if (!ThreadsManager_runOnThreads(tids, len_tids, collect_stacktrace_data)) return;
|
||||
|
||||
size_t collected = 0;
|
||||
|
||||
pid_t calling_tid = syscall(SYS_gettid);
|
||||
|
||||
/* Read the stacktrace_pipe until it's empty */
|
||||
stacktrace_data curr_stacktrace_data = {{0}};
|
||||
while (read(stacktrace_pipe[0], &curr_stacktrace_data, sizeof(curr_stacktrace_data)) > 0) {
|
||||
/* stacktrace header includes the tid and the thread's name */
|
||||
snprintf_async_signal_safe(buff, sizeof(buff), "\n%d %s", curr_stacktrace_data.tid, curr_stacktrace_data.thread_name);
|
||||
if (write(fd,buff,strlen(buff)) == -1) {/* Avoid warning. */};
|
||||
|
||||
/* skip kernel call to the signal handler, the signal handler and the callback addresses */
|
||||
int curr_uplevel = 3;
|
||||
|
||||
if (curr_stacktrace_data.tid == calling_tid) {
|
||||
/* skip signal syscall and ThreadsManager_runOnThreads */
|
||||
curr_uplevel += uplevel + 2;
|
||||
/* Add an indication to header of the thread that is handling the log file */
|
||||
if (write(fd," *\n",strlen(" *\n")) == -1) {/* Avoid warning. */};
|
||||
} else {
|
||||
/* just add a new line */
|
||||
if (write(fd,"\n",strlen("\n")) == -1) {/* Avoid warning. */};
|
||||
}
|
||||
|
||||
/* add the stacktrace */
|
||||
backtrace_symbols_fd(curr_stacktrace_data.trace+curr_uplevel, curr_stacktrace_data.trace_size-curr_uplevel, fd);
|
||||
|
||||
++collected;
|
||||
}
|
||||
|
||||
snprintf_async_signal_safe(buff, sizeof(buff), "\n%lu/%lu expected stacktraces.\n", (long unsigned)(collected), (long unsigned)len_tids);
|
||||
if (write(fd,buff,strlen(buff)) == -1) {/* Avoid warning. */};
|
||||
|
||||
}
|
||||
|
||||
#endif /* __linux__ */
|
||||
__attribute__ ((noinline))
|
||||
static void writeCurrentThreadsStackTrace(int fd, int uplevel) {
|
||||
void *trace[BACKTRACE_MAX_SIZE];
|
||||
|
||||
int trace_size = backtrace(trace, BACKTRACE_MAX_SIZE);
|
||||
|
||||
char *msg = "\nBacktrace:\n";
|
||||
if (write(fd,msg,strlen(msg)) == -1) {/* Avoid warning. */};
|
||||
backtrace_symbols_fd(trace+uplevel, trace_size-uplevel, fd);
|
||||
}
|
||||
|
||||
/* Logs the stack trace using the backtrace() call. This function is designed
|
||||
* to be called from signal handlers safely.
|
||||
* The eip argument is optional (can take NULL).
|
||||
* The uplevel argument indicates how many of the calling functions to skip.
|
||||
* Functions that are taken in consideration in "uplevel" should be declared with
|
||||
* __attribute__ ((noinline)) to make sure the compiler won't inline them.
|
||||
*/
|
||||
__attribute__ ((noinline))
|
||||
void logStackTrace(void *eip, int uplevel, int current_thread) {
|
||||
int fd = openDirectLogFiledes();
|
||||
void logStackTrace(void *eip, int uplevel) {
|
||||
void *trace[100];
|
||||
int trace_size = 0, fd = openDirectLogFiledes();
|
||||
char *msg;
|
||||
uplevel++; /* skip this function */
|
||||
|
||||
if (fd == -1) return; /* If we can't log there is anything to do. */
|
||||
|
||||
/* Get the stack trace first! */
|
||||
trace_size = backtrace(trace, 100);
|
||||
|
||||
msg = "\n------ STACK TRACE ------\n";
|
||||
if (write(fd,msg,strlen(msg)) == -1) {/* Avoid warning. */};
|
||||
|
||||
@@ -1974,21 +1844,9 @@ void logStackTrace(void *eip, int uplevel, int current_thread) {
|
||||
}
|
||||
|
||||
/* Write symbols to log file */
|
||||
++uplevel;
|
||||
#ifdef __linux__
|
||||
if (current_thread) {
|
||||
writeCurrentThreadsStackTrace(fd, uplevel);
|
||||
} else {
|
||||
writeStacktraces(fd, uplevel);
|
||||
}
|
||||
#else
|
||||
/* Outside of linux, we only support writing the current thread. */
|
||||
UNUSED(current_thread);
|
||||
writeCurrentThreadsStackTrace(fd, uplevel);
|
||||
#endif
|
||||
msg = "\n------ STACK TRACE DONE ------\n";
|
||||
msg = "\nBacktrace:\n";
|
||||
if (write(fd,msg,strlen(msg)) == -1) {/* Avoid warning. */};
|
||||
|
||||
backtrace_symbols_fd(trace+uplevel, trace_size-uplevel, fd);
|
||||
|
||||
/* Cleanup */
|
||||
closeDirectLogFiledes(fd);
|
||||
@@ -1997,17 +1855,11 @@ void logStackTrace(void *eip, int uplevel, int current_thread) {
|
||||
#endif /* HAVE_BACKTRACE */
|
||||
|
||||
sds genClusterDebugString(sds infostring) {
|
||||
sds cluster_info = genClusterInfoString();
|
||||
sds cluster_nodes = clusterGenNodesDescription(NULL, 0, 0);
|
||||
|
||||
infostring = sdscatprintf(infostring, "\r\n# Cluster info\r\n");
|
||||
infostring = sdscatsds(infostring, cluster_info);
|
||||
infostring = sdscatsds(infostring, genClusterInfoString());
|
||||
infostring = sdscatprintf(infostring, "\n------ CLUSTER NODES OUTPUT ------\n");
|
||||
infostring = sdscatsds(infostring, cluster_nodes);
|
||||
|
||||
sdsfree(cluster_info);
|
||||
sdsfree(cluster_nodes);
|
||||
|
||||
infostring = sdscatsds(infostring, clusterGenNodesDescription(NULL, 0, 0));
|
||||
|
||||
return infostring;
|
||||
}
|
||||
|
||||
@@ -2065,13 +1917,9 @@ void logCurrentClient(client *cc, const char *title) {
|
||||
sdsfree(client);
|
||||
serverLog(LL_WARNING|LL_RAW,"argc: '%d'\n", cc->argc);
|
||||
for (j = 0; j < cc->argc; j++) {
|
||||
if (j >= clientArgsToLog(cc)) {
|
||||
serverLog(LL_WARNING|LL_RAW,"argv[%d]: *redacted*\n",j);
|
||||
continue;
|
||||
}
|
||||
robj *decoded;
|
||||
decoded = getDecodedObject(cc->argv[j]);
|
||||
sds repr = sdscatrepr(sdsempty(),decoded->ptr, min(sdslen(decoded->ptr), 1024));
|
||||
sds repr = sdscatrepr(sdsempty(),decoded->ptr, min(sdslen(decoded->ptr), 128));
|
||||
serverLog(LL_WARNING|LL_RAW,"argv[%d]: '%s'\n", j, (char*)repr);
|
||||
if (!strcasecmp(decoded->ptr, "auth") || !strcasecmp(decoded->ptr, "auth2")) {
|
||||
sdsfree(repr);
|
||||
@@ -2088,7 +1936,7 @@ void logCurrentClient(client *cc, const char *title) {
|
||||
dictEntry *de;
|
||||
|
||||
key = getDecodedObject(cc->argv[1]);
|
||||
de = dbFind(cc->db, key->ptr);
|
||||
de = dictFind(cc->db->dict, key->ptr);
|
||||
if (de) {
|
||||
val = dictGetVal(de);
|
||||
serverLog(LL_WARNING,"key '%s' found in DB containing the following object:", (char*)key->ptr);
|
||||
@@ -2113,7 +1961,7 @@ int memtest_test_linux_anonymous_maps(void) {
|
||||
int regions = 0, j;
|
||||
|
||||
int fd = openDirectLogFiledes();
|
||||
if (fd == -1) return 0;
|
||||
if (!fd) return 0;
|
||||
|
||||
fp = fopen("/proc/self/maps","r");
|
||||
if (!fp) {
|
||||
@@ -2268,19 +2116,9 @@ void invalidFunctionWasCalled(void) {}
|
||||
|
||||
typedef void (*invalidFunctionWasCalledType)(void);
|
||||
|
||||
__attribute__ ((noinline))
|
||||
static void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
UNUSED(secret);
|
||||
UNUSED(info);
|
||||
int print_full_crash_info = 1;
|
||||
/* Check if it is safe to enter the signal handler. second thread crashing at the same time will deadlock. */
|
||||
if(pthread_mutex_lock(&signal_handler_lock) == EDEADLK) {
|
||||
/* If this thread already owns the lock (meaning we crashed during handling a signal) switch
|
||||
* to printing the minimal information about the crash. */
|
||||
serverLogRawFromHandler(LL_WARNING,
|
||||
"Crashed running signal handler. Providing reduced version of recursive crash report.");
|
||||
print_full_crash_info = 0;
|
||||
}
|
||||
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,
|
||||
@@ -2313,9 +2151,7 @@ static void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
getAndSetMcontextEip(uc, ptr);
|
||||
}
|
||||
|
||||
/* When printing the reduced crash info, just print the current thread
|
||||
* to avoid race conditions with the multi-threaded stack collector. */
|
||||
logStackTrace(eip, 1, !print_full_crash_info);
|
||||
logStackTrace(eip, 1);
|
||||
|
||||
if (eip == info->si_addr) {
|
||||
/* Restore old eip */
|
||||
@@ -2325,7 +2161,7 @@ static void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
logRegisters(uc);
|
||||
#endif
|
||||
|
||||
if (print_full_crash_info) printCrashReport();
|
||||
printCrashReport();
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
if (eip != NULL)
|
||||
@@ -2335,60 +2171,6 @@ static void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
bugReportEnd(1, sig);
|
||||
}
|
||||
|
||||
void setupDebugSigHandlers(void) {
|
||||
setupStacktracePipe();
|
||||
|
||||
setupSigSegvHandler();
|
||||
|
||||
struct sigaction act;
|
||||
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
act.sa_sigaction = sigalrmSignalHandler;
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
}
|
||||
|
||||
void setupSigSegvHandler(void) {
|
||||
/* Initialize the signal handler lock.
|
||||
Attempting to initialize an already initialized mutex or mutexattr results in undefined behavior. */
|
||||
if (!signal_handler_lock_initialized) {
|
||||
/* Set signal handler with error checking attribute. re-lock within the same thread will error. */
|
||||
pthread_mutexattr_init(&signal_handler_lock_attr);
|
||||
pthread_mutexattr_settype(&signal_handler_lock_attr, PTHREAD_MUTEX_ERRORCHECK);
|
||||
pthread_mutex_init(&signal_handler_lock, &signal_handler_lock_attr);
|
||||
signal_handler_lock_initialized = 1;
|
||||
}
|
||||
|
||||
struct sigaction act;
|
||||
|
||||
sigemptyset(&act.sa_mask);
|
||||
/* SA_NODEFER to disables adding the signal to the signal mask of the
|
||||
* calling process on entry to the signal handler unless it is included in the sa_mask field. */
|
||||
/* SA_SIGINFO flag is set to raise the function defined in sa_sigaction.
|
||||
* Otherwise, sa_handler is used. */
|
||||
act.sa_flags = SA_NODEFER | SA_SIGINFO;
|
||||
act.sa_sigaction = sigsegvHandler;
|
||||
if(server.crashlog_enabled) {
|
||||
sigaction(SIGSEGV, &act, NULL);
|
||||
sigaction(SIGBUS, &act, NULL);
|
||||
sigaction(SIGFPE, &act, NULL);
|
||||
sigaction(SIGILL, &act, NULL);
|
||||
sigaction(SIGABRT, &act, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void removeSigSegvHandlers(void) {
|
||||
struct sigaction act;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_NODEFER | SA_RESETHAND;
|
||||
act.sa_handler = SIG_DFL;
|
||||
sigaction(SIGSEGV, &act, NULL);
|
||||
sigaction(SIGBUS, &act, NULL);
|
||||
sigaction(SIGFPE, &act, NULL);
|
||||
sigaction(SIGILL, &act, NULL);
|
||||
sigaction(SIGABRT, &act, NULL);
|
||||
}
|
||||
|
||||
void printCrashReport(void) {
|
||||
/* Log INFO and CLIENT LIST */
|
||||
logServerInfo();
|
||||
@@ -2411,7 +2193,7 @@ void printCrashReport(void) {
|
||||
void bugReportEnd(int killViaSignal, int sig) {
|
||||
struct sigaction act;
|
||||
|
||||
serverLogRawFromHandler(LL_WARNING|LL_RAW,
|
||||
serverLogRaw(LL_WARNING|LL_RAW,
|
||||
"\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"
|
||||
" Please report the crash by opening an issue on github:\n\n"
|
||||
" http://github.com/redis/redis/issues\n\n"
|
||||
@@ -2424,7 +2206,7 @@ void bugReportEnd(int killViaSignal, int sig) {
|
||||
if (server.daemonize && server.supervised == 0 && server.pidfile) unlink(server.pidfile);
|
||||
|
||||
if (!killViaSignal) {
|
||||
/* To avoid issues with valgrind, we may wanna exit rather than generate a signal */
|
||||
/* To avoid issues with valgrind, we may wanna exit rahter than generate a signal */
|
||||
if (server.use_exit_on_panic) {
|
||||
/* Using _exit to bypass false leak reports by gcc ASAN */
|
||||
fflush(stdout);
|
||||
@@ -2436,7 +2218,7 @@ void bugReportEnd(int killViaSignal, int sig) {
|
||||
/* Make sure we exit with the right signal at the end. So for instance
|
||||
* the core will be dumped if enabled. */
|
||||
sigemptyset (&act.sa_mask);
|
||||
act.sa_flags = 0;
|
||||
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND;
|
||||
act.sa_handler = SIG_DFL;
|
||||
sigaction (sig, &act, NULL);
|
||||
kill(getpid(),sig);
|
||||
@@ -2469,27 +2251,22 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len) {
|
||||
/* =========================== Software Watchdog ============================ */
|
||||
#include <sys/time.h>
|
||||
|
||||
void sigalrmSignalHandler(int sig, siginfo_t *info, void *secret) {
|
||||
void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) {
|
||||
#ifdef HAVE_BACKTRACE
|
||||
ucontext_t *uc = (ucontext_t*) secret;
|
||||
#else
|
||||
(void)secret;
|
||||
#endif
|
||||
UNUSED(info);
|
||||
UNUSED(sig);
|
||||
|
||||
/* SIGALRM can be sent explicitly to the process calling kill() to get the stacktraces,
|
||||
or every watchdog_period interval. In the last case, si_pid is not set */
|
||||
if(info->si_pid == 0) {
|
||||
serverLogRawFromHandler(LL_WARNING,"\n--- WATCHDOG TIMER EXPIRED ---");
|
||||
} else {
|
||||
serverLogRawFromHandler(LL_WARNING, "\nReceived SIGALRM");
|
||||
}
|
||||
serverLogFromHandler(LL_WARNING,"\n--- WATCHDOG TIMER EXPIRED ---");
|
||||
#ifdef HAVE_BACKTRACE
|
||||
logStackTrace(getAndSetMcontextEip(uc, NULL), 1, 0);
|
||||
logStackTrace(getAndSetMcontextEip(uc, NULL), 1);
|
||||
#else
|
||||
serverLogRawFromHandler(LL_WARNING,"Sorry: no support for backtrace().");
|
||||
serverLogFromHandler(LL_WARNING,"Sorry: no support for backtrace().");
|
||||
#endif
|
||||
serverLogRawFromHandler(LL_WARNING,"--------\n");
|
||||
serverLogFromHandler(LL_WARNING,"--------\n");
|
||||
}
|
||||
|
||||
/* Schedule a SIGALRM delivery after the specified period in milliseconds.
|
||||
@@ -2507,10 +2284,25 @@ void watchdogScheduleSignal(int period) {
|
||||
setitimer(ITIMER_REAL, &it, NULL);
|
||||
}
|
||||
void applyWatchdogPeriod(void) {
|
||||
struct sigaction act;
|
||||
|
||||
/* Disable watchdog when period is 0 */
|
||||
if (server.watchdog_period == 0) {
|
||||
watchdogScheduleSignal(0); /* Stop the current timer. */
|
||||
|
||||
/* Set the signal handler to SIG_IGN, this will also remove pending
|
||||
* signals from the queue. */
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = 0;
|
||||
act.sa_handler = SIG_IGN;
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
} else {
|
||||
/* Setup the signal handler. */
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
act.sa_sigaction = watchdogSignalHandler;
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
|
||||
/* If the configured period is smaller than twice the timer period, it is
|
||||
* too short for the software watchdog to work reliably. Fix it now
|
||||
* if needed. */
|
||||
@@ -2528,145 +2320,3 @@ void debugDelay(int usec) {
|
||||
if (usec < 0) usec = (rand() % -usec) == 0 ? 1: 0;
|
||||
if (usec) usleep(usec);
|
||||
}
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#ifdef __linux__
|
||||
|
||||
/* =========================== Stacktrace Utils ============================ */
|
||||
|
||||
|
||||
|
||||
/** If it doesn't block and doesn't ignore, return 1 (the thread will handle the signal)
|
||||
* If thread tid blocks or ignores sig_num returns 0 (thread is not ready to catch the signal).
|
||||
* also returns 0 if something is wrong and prints a warning message to the log file **/
|
||||
static int is_thread_ready_to_signal(const char *proc_pid_task_path, const char *tid, int sig_num) {
|
||||
/* Open the threads status file path /proc/<pid>>/task/<tid>/status */
|
||||
char path_buff[PATH_MAX];
|
||||
snprintf_async_signal_safe(path_buff, PATH_MAX, "%s/%s/status", proc_pid_task_path, tid);
|
||||
|
||||
int thread_status_file = open(path_buff, O_RDONLY);
|
||||
char buff[PATH_MAX];
|
||||
if (thread_status_file == -1) {
|
||||
serverLogFromHandler(LL_WARNING, "tid:%s: failed to open %s file", tid, path_buff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ret = 1;
|
||||
size_t field_name_len = strlen("SigBlk:\t"); /* SigIgn has the same length */
|
||||
char *line = NULL;
|
||||
size_t fields_count = 2;
|
||||
while ((line = fgets_async_signal_safe(buff, PATH_MAX, thread_status_file)) && fields_count) {
|
||||
/* iterate the file until we reach SigBlk or SigIgn field line */
|
||||
if (!strncmp(buff, "SigBlk:\t", field_name_len) || !strncmp(buff, "SigIgn:\t", field_name_len)) {
|
||||
line = buff + field_name_len;
|
||||
unsigned long sig_mask;
|
||||
if (-1 == string2ul_base16_async_signal_safe(line, sizeof(buff), &sig_mask)) {
|
||||
serverLogRawFromHandler(LL_WARNING, "Can't convert signal mask to an unsigned long due to an overflow");
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* The bit position in a signal mask aligns with the signal number. Since signal numbers start from 1
|
||||
we need to adjust the signal number by subtracting 1 to align it correctly with the zero-based indexing used */
|
||||
if (sig_mask & (1L << (sig_num - 1))) { /* if the signal is blocked/ignored return 0 */
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
--fields_count;
|
||||
}
|
||||
}
|
||||
|
||||
close(thread_status_file);
|
||||
|
||||
/* if we reached EOF, it means we haven't found SigBlk or/and SigIgn, something is wrong */
|
||||
if (line == NULL) {
|
||||
ret = 0;
|
||||
serverLogFromHandler(LL_WARNING, "tid:%s: failed to find SigBlk or/and SigIgn field(s) in %s/%s/status file", tid, proc_pid_task_path, tid);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** We are using syscall(SYS_getdents64) to read directories, which unlike opendir(), is considered
|
||||
* async-signal-safe. This function wrapper getdents64() in glibc is supported as of glibc 2.30.
|
||||
* To support earlier versions of glibc, we use syscall(SYS_getdents64), which requires defining
|
||||
* linux_dirent64 ourselves. This structure is very old and stable: It will not change unless the kernel
|
||||
* chooses to break compatibility with all existing binaries. Highly Unlikely.
|
||||
*/
|
||||
struct linux_dirent64 {
|
||||
unsigned long long d_ino;
|
||||
long long d_off;
|
||||
unsigned short d_reclen; /* Length of this linux_dirent */
|
||||
unsigned char d_type;
|
||||
char d_name[256]; /* Filename (null-terminated) */
|
||||
};
|
||||
|
||||
/** Returns the number of the process's threads that can receive signal sig_num.
|
||||
* Writes into tids the tids of these threads.
|
||||
* If it fails, returns 0.
|
||||
*/
|
||||
static size_t get_ready_to_signal_threads_tids(int sig_num, pid_t tids[TIDS_MAX_SIZE]) {
|
||||
/* Open /proc/<pid>/task file. */
|
||||
char path_buff[PATH_MAX];
|
||||
snprintf_async_signal_safe(path_buff, PATH_MAX, "/proc/%d/task", getpid());
|
||||
|
||||
int dir;
|
||||
if (-1 == (dir = open(path_buff, O_RDONLY | O_DIRECTORY))) return 0;
|
||||
|
||||
size_t tids_count = 0;
|
||||
pid_t calling_tid = syscall(SYS_gettid);
|
||||
int current_thread_index = -1;
|
||||
long nread;
|
||||
char buff[PATH_MAX];
|
||||
|
||||
/* readdir() is not async-signal-safe (AS-safe).
|
||||
Hence, we read the file using SYS_getdents64, which is considered AS-sync*/
|
||||
while ((nread = syscall(SYS_getdents64, dir, buff, PATH_MAX))) {
|
||||
if (nread == -1) {
|
||||
close(dir);
|
||||
serverLogRawFromHandler(LL_WARNING, "get_ready_to_signal_threads_tids(): Failed to read the process's task directory");
|
||||
return 0;
|
||||
}
|
||||
/* Each thread is represented by a directory */
|
||||
for (long pos = 0; pos < nread;) {
|
||||
struct linux_dirent64 *entry = (struct linux_dirent64 *)(buff + pos);
|
||||
pos += entry->d_reclen;
|
||||
/* Skip irrelevant directories. */
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
||||
|
||||
/* the thread's directory name is equivalent to its tid. */
|
||||
long tid;
|
||||
string2l(entry->d_name, strlen(entry->d_name), &tid);
|
||||
|
||||
if(!is_thread_ready_to_signal(path_buff, entry->d_name, sig_num)) continue;
|
||||
|
||||
if(tid == calling_tid) {
|
||||
current_thread_index = tids_count;
|
||||
}
|
||||
|
||||
/* save the thread id */
|
||||
tids[tids_count++] = tid;
|
||||
|
||||
/* Stop if we reached the maximum threads number. */
|
||||
if(tids_count == TIDS_MAX_SIZE) {
|
||||
serverLogRawFromHandler(LL_WARNING, "get_ready_to_signal_threads_tids(): Reached the limit of the tids buffer.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(tids_count == TIDS_MAX_SIZE) break;
|
||||
}
|
||||
|
||||
/* Swap the last tid with the the current thread id */
|
||||
if(current_thread_index != -1) {
|
||||
pid_t last_tid = tids[tids_count - 1];
|
||||
|
||||
tids[tids_count - 1] = calling_tid;
|
||||
tids[current_thread_index] = last_tid;
|
||||
}
|
||||
|
||||
close(dir);
|
||||
|
||||
return tids_count;
|
||||
}
|
||||
#endif /* __linux__ */
|
||||
#endif /* HAVE_BACKTRACE */
|
||||
|
||||
+24
-3
@@ -2,11 +2,32 @@
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2016-Present, Redis Ltd.
|
||||
* Copyright (c) 2016, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _REDIS_DEBUGMACRO_H_
|
||||
|
||||
+116
-310
@@ -5,28 +5,42 @@
|
||||
* We do that by scanning the keyspace and for each pointer we have, we can try to
|
||||
* ask the allocator if moving it to a new address will help reduce fragmentation.
|
||||
*
|
||||
* Copyright (c) 2020-Present, Redis Ltd.
|
||||
* Copyright (c) 2020, Redis Labs, Inc
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef HAVE_DEFRAG
|
||||
|
||||
typedef struct defragCtx {
|
||||
void *privdata;
|
||||
int slot;
|
||||
} defragCtx;
|
||||
|
||||
typedef struct defragPubSubCtx {
|
||||
kvstore *pubsub_channels;
|
||||
dict *(*clientPubSubChannels)(client*);
|
||||
} defragPubSubCtx;
|
||||
|
||||
/* this method was added to jemalloc in order to help us understand which
|
||||
* pointers are worthwhile moving and which aren't */
|
||||
int je_get_defrag_hint(void* ptr);
|
||||
@@ -46,7 +60,7 @@ void* activeDefragAlloc(void *ptr) {
|
||||
/* move this allocation to a new allocation.
|
||||
* make sure not to use the thread cache. so that we don't get back the same
|
||||
* pointers we try to free */
|
||||
size = zmalloc_usable_size(ptr);
|
||||
size = zmalloc_size(ptr);
|
||||
newptr = zmalloc_no_tcache(size);
|
||||
memcpy(newptr, ptr, size);
|
||||
zfree_no_tcache(ptr);
|
||||
@@ -70,32 +84,14 @@ sds activeDefragSds(sds sdsptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Defrag helper for hfield strings
|
||||
/* Defrag helper for robj and/or string objects
|
||||
*
|
||||
* returns NULL in case the allocation wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
hfield activeDefragHfield(hfield hf) {
|
||||
void *ptr = hfieldGetAllocPtr(hf);
|
||||
void *newptr = activeDefragAlloc(ptr);
|
||||
if (newptr) {
|
||||
size_t offset = hf - (char*)ptr;
|
||||
hf = (char*)newptr + offset;
|
||||
return hf;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Defrag helper for robj and/or string objects with expected refcount.
|
||||
*
|
||||
* Like activeDefragStringOb, but it requires the caller to pass in the expected
|
||||
* reference count. In some cases, the caller needs to update a robj whose
|
||||
* reference count is not 1, in these cases, the caller must explicitly pass
|
||||
* in the reference count, otherwise defragmentation will not be performed.
|
||||
* Note that the caller is responsible for updating any other references to the robj. */
|
||||
robj *activeDefragStringObEx(robj* ob, int expected_refcount) {
|
||||
robj *activeDefragStringOb(robj* ob) {
|
||||
robj *ret = NULL;
|
||||
if (ob->refcount!=expected_refcount)
|
||||
if (ob->refcount!=1)
|
||||
return NULL;
|
||||
|
||||
/* try to defrag robj (only if not an EMBSTR type (handled below). */
|
||||
@@ -126,15 +122,6 @@ robj *activeDefragStringObEx(robj* ob, int expected_refcount) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Defrag helper for robj and/or string objects
|
||||
*
|
||||
* returns NULL in case the allocation wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
robj *activeDefragStringOb(robj* ob) {
|
||||
return activeDefragStringObEx(ob, 1);
|
||||
}
|
||||
|
||||
/* Defrag helper for lua scripts
|
||||
*
|
||||
* returns NULL in case the allocation wasn't moved.
|
||||
@@ -156,20 +143,11 @@ luaScript *activeDefragLuaScript(luaScript *script) {
|
||||
}
|
||||
|
||||
/* Defrag helper for dict main allocations (dict struct, and hash tables).
|
||||
* Receives a pointer to the dict* and return a new dict* when the dict
|
||||
* struct itself was moved.
|
||||
*
|
||||
* Returns NULL in case the allocation wasn't moved.
|
||||
* When it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
dict *dictDefragTables(dict *d) {
|
||||
dict *ret = NULL;
|
||||
* receives a pointer to the dict* and implicitly updates it when the dict
|
||||
* struct itself was moved. Returns a stat of how many pointers were moved. */
|
||||
void dictDefragTables(dict* d) {
|
||||
dictEntry **newtable;
|
||||
/* handle the dict struct */
|
||||
if ((ret = activeDefragAlloc(d)))
|
||||
d = ret;
|
||||
/* handle the first hash table */
|
||||
if (!d->ht_table[0]) return ret; /* created but unused */
|
||||
newtable = activeDefragAlloc(d->ht_table[0]);
|
||||
if (newtable)
|
||||
d->ht_table[0] = newtable;
|
||||
@@ -179,7 +157,6 @@ dict *dictDefragTables(dict *d) {
|
||||
if (newtable)
|
||||
d->ht_table[1] = newtable;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Internal function used by zslDefrag */
|
||||
@@ -266,41 +243,6 @@ void activeDefragSdsDictCallback(void *privdata, const dictEntry *de) {
|
||||
UNUSED(de);
|
||||
}
|
||||
|
||||
void activeDefragLuaScriptDictCallback(void *privdata, const dictEntry *de) {
|
||||
UNUSED(privdata);
|
||||
|
||||
/* If this luaScript is in the LRU list, unconditionally update the node's
|
||||
* value pointer to the current dict key (regardless of reallocation). */
|
||||
luaScript *script = dictGetVal(de);
|
||||
if (script->node)
|
||||
script->node->value = dictGetKey(de);
|
||||
}
|
||||
|
||||
void activeDefragHfieldDictCallback(void *privdata, const dictEntry *de) {
|
||||
dict *d = privdata;
|
||||
hfield newhf, hf = dictGetKey(de);
|
||||
|
||||
if (hfieldGetExpireTime(hf) == EB_EXPIRE_TIME_INVALID) {
|
||||
/* If the hfield does not have TTL, we directly defrag it. */
|
||||
newhf = activeDefragHfield(hf);
|
||||
} else {
|
||||
/* Update its reference in the ebucket while defragging it. */
|
||||
ebuckets *eb = hashTypeGetDictMetaHFE(d);
|
||||
newhf = ebDefragItem(eb, &hashFieldExpireBucketsType, hf, (ebDefragFunction *)activeDefragHfield);
|
||||
}
|
||||
if (newhf) {
|
||||
/* We can't search in dict for that key after we've released
|
||||
* the pointer it holds, since it won't be able to do the string
|
||||
* compare, but we can find the entry using key hash and pointer. */
|
||||
dictUseStoredKeyApi(d, 1);
|
||||
uint64_t hash = dictGetHash(d, newhf);
|
||||
dictUseStoredKeyApi(d, 0);
|
||||
dictEntry *de = dictFindEntryByPtrAndHash(d, hf, hash);
|
||||
serverAssert(de);
|
||||
dictSetKey(d, de, newhf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Defrag a dict with sds key and optional value (either ptr, sds or robj string) */
|
||||
void activeDefragSdsDict(dict* d, int val_type) {
|
||||
unsigned long cursor = 0;
|
||||
@@ -313,28 +255,12 @@ void activeDefragSdsDict(dict* d, int val_type) {
|
||||
val_type == DEFRAG_SDS_DICT_VAL_LUA_SCRIPT ? (dictDefragAllocFunction *)activeDefragLuaScript :
|
||||
NULL)
|
||||
};
|
||||
dictScanFunction *fn = (val_type == DEFRAG_SDS_DICT_VAL_LUA_SCRIPT ?
|
||||
activeDefragLuaScriptDictCallback : activeDefragSdsDictCallback);
|
||||
do {
|
||||
cursor = dictScanDefrag(d, cursor, fn,
|
||||
cursor = dictScanDefrag(d, cursor, activeDefragSdsDictCallback,
|
||||
&defragfns, NULL);
|
||||
} while (cursor != 0);
|
||||
}
|
||||
|
||||
/* Defrag a dict with hfield key and sds value. */
|
||||
void activeDefragHfieldDict(dict *d) {
|
||||
unsigned long cursor = 0;
|
||||
dictDefragFunctions defragfns = {
|
||||
.defragAlloc = activeDefragAlloc,
|
||||
.defragKey = NULL, /* Will be defragmented in activeDefragHfieldDictCallback. */
|
||||
.defragVal = (dictDefragAllocFunction *)activeDefragSds
|
||||
};
|
||||
do {
|
||||
cursor = dictScanDefrag(d, cursor, activeDefragHfieldDictCallback,
|
||||
&defragfns, d);
|
||||
} while (cursor != 0);
|
||||
}
|
||||
|
||||
/* Defrag a list of ptr, sds or robj string values */
|
||||
void activeDefragList(list *l, int val_type) {
|
||||
listNode *ln, *newln;
|
||||
@@ -406,7 +332,8 @@ long scanLaterList(robj *ob, unsigned long *cursor, long long endtime) {
|
||||
quicklistNode *node;
|
||||
long iterations = 0;
|
||||
int bookmark_failed = 0;
|
||||
serverAssert(ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST);
|
||||
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST)
|
||||
return 0;
|
||||
|
||||
if (*cursor == 0) {
|
||||
/* if cursor is 0, we start new iteration */
|
||||
@@ -455,7 +382,8 @@ void scanLaterZsetCallback(void *privdata, const dictEntry *_de) {
|
||||
}
|
||||
|
||||
void scanLaterZset(robj *ob, unsigned long *cursor) {
|
||||
serverAssert(ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST);
|
||||
if (ob->type != OBJ_ZSET || ob->encoding != OBJ_ENCODING_SKIPLIST)
|
||||
return;
|
||||
zset *zs = (zset*)ob->ptr;
|
||||
dict *d = zs->dict;
|
||||
scanLaterZsetData data = {zs};
|
||||
@@ -471,7 +399,8 @@ void scanCallbackCountScanned(void *privdata, const dictEntry *de) {
|
||||
}
|
||||
|
||||
void scanLaterSet(robj *ob, unsigned long *cursor) {
|
||||
serverAssert(ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HT);
|
||||
if (ob->type != OBJ_SET || ob->encoding != OBJ_ENCODING_HT)
|
||||
return;
|
||||
dict *d = ob->ptr;
|
||||
dictDefragFunctions defragfns = {
|
||||
.defragAlloc = activeDefragAlloc,
|
||||
@@ -481,14 +410,15 @@ void scanLaterSet(robj *ob, unsigned long *cursor) {
|
||||
}
|
||||
|
||||
void scanLaterHash(robj *ob, unsigned long *cursor) {
|
||||
serverAssert(ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HT);
|
||||
if (ob->type != OBJ_HASH || ob->encoding != OBJ_ENCODING_HT)
|
||||
return;
|
||||
dict *d = ob->ptr;
|
||||
dictDefragFunctions defragfns = {
|
||||
.defragAlloc = activeDefragAlloc,
|
||||
.defragKey = NULL, /* Will be defragmented in activeDefragHfieldDictCallback. */
|
||||
.defragKey = (dictDefragAllocFunction *)activeDefragSds,
|
||||
.defragVal = (dictDefragAllocFunction *)activeDefragSds
|
||||
};
|
||||
*cursor = dictScanDefrag(d, *cursor, activeDefragHfieldDictCallback, &defragfns, d);
|
||||
*cursor = dictScanDefrag(d, *cursor, scanCallbackCountScanned, &defragfns, NULL);
|
||||
}
|
||||
|
||||
void defragQuicklist(redisDb *db, dictEntry *kde) {
|
||||
@@ -527,9 +457,11 @@ void defragZsetSkiplist(redisDb *db, dictEntry *kde) {
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
/* defrag the dict struct and tables */
|
||||
if ((newdict = dictDefragTables(zs->dict)))
|
||||
/* handle the dict struct */
|
||||
if ((newdict = activeDefragAlloc(zs->dict)))
|
||||
zs->dict = newdict;
|
||||
/* defrag the dict tables */
|
||||
dictDefragTables(zs->dict);
|
||||
}
|
||||
|
||||
void defragHash(redisDb *db, dictEntry *kde) {
|
||||
@@ -540,10 +472,12 @@ void defragHash(redisDb *db, dictEntry *kde) {
|
||||
if (dictSize(d) > server.active_defrag_max_scan_fields)
|
||||
defragLater(db, kde);
|
||||
else
|
||||
activeDefragHfieldDict(d);
|
||||
/* defrag the dict struct and tables */
|
||||
if ((newd = dictDefragTables(ob->ptr)))
|
||||
activeDefragSdsDict(d, DEFRAG_SDS_DICT_VAL_IS_SDS);
|
||||
/* handle the dict struct */
|
||||
if ((newd = activeDefragAlloc(ob->ptr)))
|
||||
ob->ptr = newd;
|
||||
/* defrag the dict tables */
|
||||
dictDefragTables(ob->ptr);
|
||||
}
|
||||
|
||||
void defragSet(redisDb *db, dictEntry *kde) {
|
||||
@@ -555,9 +489,11 @@ void defragSet(redisDb *db, dictEntry *kde) {
|
||||
defragLater(db, kde);
|
||||
else
|
||||
activeDefragSdsDict(d, DEFRAG_SDS_DICT_NO_VAL);
|
||||
/* defrag the dict struct and tables */
|
||||
if ((newd = dictDefragTables(ob->ptr)))
|
||||
/* handle the dict struct */
|
||||
if ((newd = activeDefragAlloc(ob->ptr)))
|
||||
ob->ptr = newd;
|
||||
/* defrag the dict tables */
|
||||
dictDefragTables(ob->ptr);
|
||||
}
|
||||
|
||||
/* Defrag callback for radix tree iterator, called for each node,
|
||||
@@ -576,7 +512,10 @@ int scanLaterStreamListpacks(robj *ob, unsigned long *cursor, long long endtime)
|
||||
static unsigned char last[sizeof(streamID)];
|
||||
raxIterator ri;
|
||||
long iterations = 0;
|
||||
serverAssert(ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM);
|
||||
if (ob->type != OBJ_STREAM || ob->encoding != OBJ_ENCODING_STREAM) {
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream *s = ob->ptr;
|
||||
raxStart(&ri,s->rax);
|
||||
@@ -730,43 +669,32 @@ void defragModule(redisDb *db, dictEntry *kde) {
|
||||
}
|
||||
|
||||
/* for each key we scan in the main dict, this function will attempt to defrag
|
||||
* all the various pointers it has. */
|
||||
void defragKey(defragCtx *ctx, dictEntry *de) {
|
||||
* all the various pointers it has. Returns a stat of how many pointers were
|
||||
* moved. */
|
||||
void defragKey(redisDb *db, dictEntry *de) {
|
||||
sds keysds = dictGetKey(de);
|
||||
robj *newob, *ob = dictGetVal(de);
|
||||
robj *newob, *ob;
|
||||
unsigned char *newzl;
|
||||
sds newsds;
|
||||
redisDb *db = ctx->privdata;
|
||||
int slot = ctx->slot;
|
||||
|
||||
/* Try to defrag the key name. */
|
||||
newsds = activeDefragSds(keysds);
|
||||
if (newsds) {
|
||||
kvstoreDictSetKey(db->keys, slot, de, newsds);
|
||||
if (kvstoreDictSize(db->expires, slot)) {
|
||||
dictSetKey(db->dict, de, newsds);
|
||||
if (dictSize(db->expires)) {
|
||||
/* We can't search in db->expires for that key after we've released
|
||||
* the pointer it holds, since it won't be able to do the string
|
||||
* compare, but we can find the entry using key hash and pointer. */
|
||||
uint64_t hash = kvstoreGetHash(db->expires, newsds);
|
||||
dictEntry *expire_de = kvstoreDictFindEntryByPtrAndHash(db->expires, slot, keysds, hash);
|
||||
if (expire_de) kvstoreDictSetKey(db->expires, slot, expire_de, newsds);
|
||||
uint64_t hash = dictGetHash(db->dict, newsds);
|
||||
dictEntry *expire_de = dictFindEntryByPtrAndHash(db->expires, keysds, hash);
|
||||
if (expire_de) dictSetKey(db->expires, expire_de, newsds);
|
||||
}
|
||||
|
||||
/* Update the key's reference in the dict's metadata or the listpackEx. */
|
||||
if (unlikely(ob->type == OBJ_HASH))
|
||||
hashTypeUpdateKeyRef(ob, newsds);
|
||||
}
|
||||
|
||||
/* Try to defrag robj and / or string value. */
|
||||
if (unlikely(ob->type == OBJ_HASH && hashTypeGetMinExpire(ob, 0) != EB_EXPIRE_TIME_INVALID)) {
|
||||
/* Update its reference in the ebucket while defragging it. */
|
||||
newob = ebDefragItem(&db->hexpires, &hashExpireBucketsType, ob,
|
||||
(ebDefragFunction *)activeDefragStringOb);
|
||||
} else {
|
||||
/* If the dict doesn't have metadata, we directly defrag it. */
|
||||
newob = activeDefragStringOb(ob);
|
||||
}
|
||||
if (newob) {
|
||||
kvstoreDictSetVal(db->keys, slot, de, newob);
|
||||
ob = dictGetVal(de);
|
||||
if ((newob = activeDefragStringOb(ob))) {
|
||||
dictSetVal(db->dict, de, newob);
|
||||
ob = newob;
|
||||
}
|
||||
|
||||
@@ -806,12 +734,6 @@ void defragKey(defragCtx *ctx, dictEntry *de) {
|
||||
if (ob->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
if ((newzl = activeDefragAlloc(ob->ptr)))
|
||||
ob->ptr = newzl;
|
||||
} else if (ob->encoding == OBJ_ENCODING_LISTPACK_EX) {
|
||||
listpackEx *newlpt, *lpt = (listpackEx*)ob->ptr;
|
||||
if ((newlpt = activeDefragAlloc(lpt)))
|
||||
ob->ptr = lpt = newlpt;
|
||||
if ((newzl = activeDefragAlloc(lpt->lp)))
|
||||
lpt->lp = newzl;
|
||||
} else if (ob->encoding == OBJ_ENCODING_HT) {
|
||||
defragHash(db, de);
|
||||
} else {
|
||||
@@ -829,7 +751,7 @@ void defragKey(defragCtx *ctx, dictEntry *de) {
|
||||
/* Defrag scan callback for the main db dictionary. */
|
||||
void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
long long hits_before = server.stat_active_defrag_hits;
|
||||
defragKey((defragCtx*)privdata, (dictEntry*)de);
|
||||
defragKey((redisDb*)privdata, (dictEntry*)de);
|
||||
if (server.stat_active_defrag_hits != hits_before)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
else
|
||||
@@ -844,70 +766,20 @@ void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
* or not, a false detection can cause the defragmenter to waste a lot of CPU
|
||||
* without the possibility of getting any results. */
|
||||
float getAllocatorFragmentation(size_t *out_frag_bytes) {
|
||||
size_t resident, active, allocated, frag_smallbins_bytes;
|
||||
zmalloc_get_allocator_info(1, &allocated, &active, &resident, NULL, NULL, &frag_smallbins_bytes);
|
||||
|
||||
if (server.lua_arena != UINT_MAX) {
|
||||
size_t lua_resident, lua_active, lua_allocated, lua_frag_smallbins_bytes;
|
||||
zmalloc_get_allocator_info_by_arena(server.lua_arena, 0, &lua_allocated, &lua_active, &lua_resident, &lua_frag_smallbins_bytes);
|
||||
resident -= lua_resident;
|
||||
active -= lua_active;
|
||||
allocated -= lua_allocated;
|
||||
frag_smallbins_bytes -= lua_frag_smallbins_bytes;
|
||||
}
|
||||
|
||||
/* Calculate the fragmentation ratio as the proportion of wasted memory in small
|
||||
* bins (which are defraggable) relative to the total allocated memory (including large bins).
|
||||
* This is because otherwise, if most of the memory usage is large bins, we may show high percentage,
|
||||
* despite the fact it's not a lot of memory for the user. */
|
||||
float frag_pct = (float)frag_smallbins_bytes / allocated * 100;
|
||||
size_t resident, active, allocated;
|
||||
zmalloc_get_allocator_info(&allocated, &active, &resident);
|
||||
float frag_pct = ((float)active / allocated)*100 - 100;
|
||||
size_t frag_bytes = active - allocated;
|
||||
float rss_pct = ((float)resident / allocated)*100 - 100;
|
||||
size_t rss_bytes = resident - allocated;
|
||||
if(out_frag_bytes)
|
||||
*out_frag_bytes = frag_smallbins_bytes;
|
||||
*out_frag_bytes = frag_bytes;
|
||||
serverLog(LL_DEBUG,
|
||||
"allocated=%zu, active=%zu, resident=%zu, frag=%.2f%% (%.2f%% rss), frag_bytes=%zu (%zu rss)",
|
||||
allocated, active, resident, frag_pct, rss_pct, frag_smallbins_bytes, rss_bytes);
|
||||
"allocated=%zu, active=%zu, resident=%zu, frag=%.0f%% (%.0f%% rss), frag_bytes=%zu (%zu rss)",
|
||||
allocated, active, resident, frag_pct, rss_pct, frag_bytes, rss_bytes);
|
||||
return frag_pct;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for the pubsub dictionary. */
|
||||
void defragPubsubScanCallback(void *privdata, const dictEntry *de) {
|
||||
defragCtx *ctx = privdata;
|
||||
defragPubSubCtx *pubsub_ctx = ctx->privdata;
|
||||
kvstore *pubsub_channels = pubsub_ctx->pubsub_channels;
|
||||
robj *newchannel, *channel = dictGetKey(de);
|
||||
dict *newclients, *clients = dictGetVal(de);
|
||||
|
||||
/* Try to defrag the channel name. */
|
||||
serverAssert(channel->refcount == (int)dictSize(clients) + 1);
|
||||
newchannel = activeDefragStringObEx(channel, dictSize(clients) + 1);
|
||||
if (newchannel) {
|
||||
kvstoreDictSetKey(pubsub_channels, ctx->slot, (dictEntry*)de, newchannel);
|
||||
|
||||
/* The channel name is shared by the client's pubsub(shard) and server's
|
||||
* pubsub(shard), after defraging the channel name, we need to update
|
||||
* the reference in the clients' dictionary. */
|
||||
dictIterator *di = dictGetIterator(clients);
|
||||
dictEntry *clientde;
|
||||
while((clientde = dictNext(di)) != NULL) {
|
||||
client *c = dictGetKey(clientde);
|
||||
dict *client_channels = pubsub_ctx->clientPubSubChannels(c);
|
||||
uint64_t hash = dictGetHash(client_channels, newchannel);
|
||||
dictEntry *pubsub_channel = dictFindByHashAndPtr(client_channels, channel, hash);
|
||||
serverAssert(pubsub_channel);
|
||||
dictSetKey(pubsub_ctx->clientPubSubChannels(c), pubsub_channel, newchannel);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* Try to defrag the dictionary of clients that is stored as the value part. */
|
||||
if ((newclients = dictDefragTables(clients)))
|
||||
kvstoreDictSetVal(pubsub_channels, ctx->slot, (dictEntry*)de, newclients);
|
||||
|
||||
server.stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
/* We may need to defrag other globals, one small allocation can hold a full allocator run.
|
||||
* so although small, it is still important to defrag these */
|
||||
void defragOtherGlobals(void) {
|
||||
@@ -917,8 +789,6 @@ void defragOtherGlobals(void) {
|
||||
* that remain static for a long time */
|
||||
activeDefragSdsDict(evalScriptsDict(), DEFRAG_SDS_DICT_VAL_LUA_SCRIPT);
|
||||
moduleDefragGlobals();
|
||||
kvstoreDictLUTDefrag(server.pubsub_channels, dictDefragTables);
|
||||
kvstoreDictLUTDefrag(server.pubsubshard_channels, dictDefragTables);
|
||||
}
|
||||
|
||||
/* returns 0 more work may or may not be needed (see non-zero cursor),
|
||||
@@ -926,22 +796,22 @@ void defragOtherGlobals(void) {
|
||||
int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime, int dbid) {
|
||||
if (de) {
|
||||
robj *ob = dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
if (ob->type == OBJ_LIST) {
|
||||
return scanLaterList(ob, cursor, endtime);
|
||||
} else if (ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HT) {
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
scanLaterZset(ob, cursor);
|
||||
} else if (ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HT) {
|
||||
} else if (ob->type == OBJ_HASH) {
|
||||
scanLaterHash(ob, cursor);
|
||||
} else if (ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM) {
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
return scanLaterStreamListpacks(ob, cursor, endtime);
|
||||
} else if (ob->type == OBJ_MODULE) {
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj, dictGetKey(de));
|
||||
return moduleLateDefrag(&keyobj, ob, cursor, endtime, dbid);
|
||||
} else {
|
||||
*cursor = 0; /* object type/encoding may have changed since we schedule it for later */
|
||||
*cursor = 0; /* object type may have changed since we schedule it for later */
|
||||
}
|
||||
} else {
|
||||
*cursor = 0; /* object may have been deleted already */
|
||||
@@ -954,7 +824,7 @@ static sds defrag_later_current_key = NULL;
|
||||
static unsigned long defrag_later_cursor = 0;
|
||||
|
||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||
int defragLaterStep(redisDb *db, int slot, long long endtime) {
|
||||
int defragLaterStep(redisDb *db, long long endtime) {
|
||||
unsigned int iterations = 0;
|
||||
unsigned long long prev_defragged = server.stat_active_defrag_hits;
|
||||
unsigned long long prev_scanned = server.stat_active_defrag_scanned;
|
||||
@@ -984,7 +854,7 @@ int defragLaterStep(redisDb *db, int slot, long long endtime) {
|
||||
}
|
||||
|
||||
/* each time we enter this function we need to fetch the key from the dict again (if it still exists) */
|
||||
dictEntry *de = kvstoreDictFind(db->keys, slot, defrag_later_current_key);
|
||||
dictEntry *de = dictFind(db->dict, defrag_later_current_key);
|
||||
key_defragged = server.stat_active_defrag_hits;
|
||||
do {
|
||||
int quit = 0;
|
||||
@@ -1029,8 +899,7 @@ void computeDefragCycles(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Calculate the adaptive aggressiveness of the defrag based on the current
|
||||
* fragmentation and configurations. */
|
||||
/* Calculate the adaptive aggressiveness of the defrag */
|
||||
int cpu_pct = INTERPOLATE(frag_pct,
|
||||
server.active_defrag_threshold_lower,
|
||||
server.active_defrag_threshold_upper,
|
||||
@@ -1039,15 +908,10 @@ void computeDefragCycles(void) {
|
||||
cpu_pct = LIMIT(cpu_pct,
|
||||
server.active_defrag_cycle_min,
|
||||
server.active_defrag_cycle_max);
|
||||
|
||||
/* Normally we allow increasing the aggressiveness during a scan, but don't
|
||||
* reduce it, since we should not lower the aggressiveness when fragmentation
|
||||
* drops. But when a configuration is made, we should reconsider it. */
|
||||
if (cpu_pct > server.active_defrag_running ||
|
||||
server.active_defrag_configuration_changed)
|
||||
{
|
||||
/* We allow increasing the aggressiveness during a scan, but don't
|
||||
* reduce it. */
|
||||
if (cpu_pct > server.active_defrag_running) {
|
||||
server.active_defrag_running = cpu_pct;
|
||||
server.active_defrag_configuration_changed = 0;
|
||||
serverLog(LL_VERBOSE,
|
||||
"Starting active defrag, frag=%.0f%%, frag_bytes=%zu, cpu=%d%%",
|
||||
frag_pct, frag_bytes, cpu_pct);
|
||||
@@ -1058,11 +922,9 @@ void computeDefragCycles(void) {
|
||||
* This works in a similar way to activeExpireCycle, in the sense that
|
||||
* we do incremental work across calls. */
|
||||
void activeDefragCycle(void) {
|
||||
static int slot = -1;
|
||||
static int current_db = -1;
|
||||
static int defrag_later_item_in_progress = 0;
|
||||
static int defrag_stage = 0;
|
||||
static unsigned long defrag_cursor = 0;
|
||||
static unsigned long cursor = 0;
|
||||
static unsigned long expires_cursor = 0;
|
||||
static redisDb *db = NULL;
|
||||
static long long start_scan, start_stat;
|
||||
unsigned int iterations = 0;
|
||||
@@ -1070,23 +932,19 @@ void activeDefragCycle(void) {
|
||||
unsigned long long prev_scanned = server.stat_active_defrag_scanned;
|
||||
long long start, timelimit, endtime;
|
||||
mstime_t latency;
|
||||
int all_stages_finished = 0;
|
||||
int quit = 0;
|
||||
|
||||
if (!server.active_defrag_enabled) {
|
||||
if (server.active_defrag_running) {
|
||||
/* if active defrag was disabled mid-run, start from fresh next time. */
|
||||
server.active_defrag_running = 0;
|
||||
server.active_defrag_configuration_changed = 0;
|
||||
if (db)
|
||||
listEmpty(db->defrag_later);
|
||||
defrag_later_current_key = NULL;
|
||||
defrag_later_cursor = 0;
|
||||
current_db = -1;
|
||||
defrag_stage = 0;
|
||||
defrag_cursor = 0;
|
||||
slot = -1;
|
||||
defrag_later_item_in_progress = 0;
|
||||
cursor = 0;
|
||||
expires_cursor = 0;
|
||||
db = NULL;
|
||||
goto update_metrics;
|
||||
}
|
||||
@@ -1101,14 +959,6 @@ void activeDefragCycle(void) {
|
||||
run_with_period(1000) {
|
||||
computeDefragCycles();
|
||||
}
|
||||
|
||||
/* Normally it is checked once a second, but when there is a configuration
|
||||
* change, we want to check it as soon as possible. */
|
||||
if (server.active_defrag_configuration_changed) {
|
||||
computeDefragCycles();
|
||||
server.active_defrag_configuration_changed = 0;
|
||||
}
|
||||
|
||||
if (!server.active_defrag_running)
|
||||
return;
|
||||
|
||||
@@ -1122,9 +972,9 @@ void activeDefragCycle(void) {
|
||||
dictDefragFunctions defragfns = {.defragAlloc = activeDefragAlloc};
|
||||
do {
|
||||
/* if we're not continuing a scan from the last call or loop, start a new one */
|
||||
if (!defrag_stage && !defrag_cursor && (slot < 0)) {
|
||||
if (!cursor && !expires_cursor) {
|
||||
/* finish any leftovers from previous db before moving to the next one */
|
||||
if (db && defragLaterStep(db, slot, endtime)) {
|
||||
if (db && defragLaterStep(db, endtime)) {
|
||||
quit = 1; /* time is up, we didn't finish all the work */
|
||||
break; /* this will exit the function and we'll continue on the next cycle */
|
||||
}
|
||||
@@ -1143,10 +993,7 @@ void activeDefragCycle(void) {
|
||||
|
||||
start_scan = now;
|
||||
current_db = -1;
|
||||
defrag_stage = 0;
|
||||
defrag_cursor = 0;
|
||||
slot = -1;
|
||||
defrag_later_item_in_progress = 0;
|
||||
cursor = 0;
|
||||
db = NULL;
|
||||
server.active_defrag_running = 0;
|
||||
|
||||
@@ -1162,79 +1009,38 @@ void activeDefragCycle(void) {
|
||||
}
|
||||
|
||||
db = &server.db[current_db];
|
||||
kvstoreDictLUTDefrag(db->keys, dictDefragTables);
|
||||
kvstoreDictLUTDefrag(db->expires, dictDefragTables);
|
||||
defrag_stage = 0;
|
||||
defrag_cursor = 0;
|
||||
slot = -1;
|
||||
defrag_later_item_in_progress = 0;
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
/* This array of structures holds the parameters for all defragmentation stages. */
|
||||
typedef struct defragStage {
|
||||
kvstore *kvs;
|
||||
dictScanFunction *scanfn;
|
||||
void *privdata;
|
||||
} defragStage;
|
||||
defragStage defrag_stages[] = {
|
||||
{db->keys, defragScanCallback, db},
|
||||
{db->expires, scanCallbackCountScanned, NULL},
|
||||
{server.pubsub_channels, defragPubsubScanCallback,
|
||||
&(defragPubSubCtx){server.pubsub_channels, getClientPubSubChannels}},
|
||||
{server.pubsubshard_channels, defragPubsubScanCallback,
|
||||
&(defragPubSubCtx){server.pubsubshard_channels, getClientPubSubShardChannels}},
|
||||
};
|
||||
do {
|
||||
int num_stages = sizeof(defrag_stages) / sizeof(defrag_stages[0]);
|
||||
serverAssert(defrag_stage < num_stages);
|
||||
defragStage *current_stage = &defrag_stages[defrag_stage];
|
||||
|
||||
/* before scanning the next bucket, see if we have big keys left from the previous bucket to scan */
|
||||
if (defragLaterStep(db, slot, endtime)) {
|
||||
if (defragLaterStep(db, endtime)) {
|
||||
quit = 1; /* time is up, we didn't finish all the work */
|
||||
break; /* this will exit the function and we'll continue on the next cycle */
|
||||
}
|
||||
|
||||
if (!defrag_later_item_in_progress) {
|
||||
/* Continue defragmentation from the previous stage.
|
||||
* If slot is -1, it means this stage starts from the first non-empty slot. */
|
||||
if (slot == -1) slot = kvstoreGetFirstNonEmptyDictIndex(current_stage->kvs);
|
||||
defrag_cursor = kvstoreDictScanDefrag(current_stage->kvs, slot, defrag_cursor,
|
||||
current_stage->scanfn, &defragfns, &(defragCtx){current_stage->privdata, slot});
|
||||
}
|
||||
/* Scan the keyspace dict unless we're scanning the expire dict. */
|
||||
if (!expires_cursor)
|
||||
cursor = dictScanDefrag(db->dict, cursor, defragScanCallback,
|
||||
&defragfns, db);
|
||||
|
||||
if (!defrag_cursor) {
|
||||
/* Move to the next slot only if regular and large item scanning has been completed. */
|
||||
if (listLength(db->defrag_later) > 0) {
|
||||
defrag_later_item_in_progress = 1;
|
||||
continue;
|
||||
}
|
||||
/* When done scanning the keyspace dict, we scan the expire dict. */
|
||||
if (!cursor)
|
||||
expires_cursor = dictScanDefrag(db->expires, expires_cursor,
|
||||
scanCallbackCountScanned,
|
||||
&defragfns, NULL);
|
||||
|
||||
/* Move to the next slot in the current stage. If we've reached the end, move to the next stage. */
|
||||
if ((slot = kvstoreGetNextNonEmptyDictIndex(current_stage->kvs, slot)) == -1)
|
||||
defrag_stage++;
|
||||
defrag_later_item_in_progress = 0;
|
||||
}
|
||||
|
||||
/* Check if all defragmentation stages have been processed.
|
||||
* If so, mark as finished and reset the stage counter to move on to next database. */
|
||||
if (defrag_stage == num_stages) {
|
||||
all_stages_finished = 1;
|
||||
defrag_stage = 0;
|
||||
}
|
||||
|
||||
/* Once in 16 scan iterations, 512 pointer reallocations. or 64 keys
|
||||
* (if we have a lot of pointers in one hash bucket or rehashing),
|
||||
* check if we reached the time limit.
|
||||
* But regardless, don't start a new db in this loop, this is because after
|
||||
* the last db we call defragOtherGlobals, which must be done in one cycle */
|
||||
if (all_stages_finished ||
|
||||
if (!(cursor || expires_cursor) ||
|
||||
++iterations > 16 ||
|
||||
server.stat_active_defrag_hits - prev_defragged > 512 ||
|
||||
server.stat_active_defrag_scanned - prev_scanned > 64)
|
||||
{
|
||||
/* Quit if all stages were finished or timeout. */
|
||||
if (all_stages_finished || ustime() > endtime) {
|
||||
if (!(cursor || expires_cursor) || ustime() > endtime) {
|
||||
quit = 1;
|
||||
break;
|
||||
}
|
||||
@@ -1242,7 +1048,7 @@ void activeDefragCycle(void) {
|
||||
prev_defragged = server.stat_active_defrag_hits;
|
||||
prev_scanned = server.stat_active_defrag_scanned;
|
||||
}
|
||||
} while(!all_stages_finished && !quit);
|
||||
} while((cursor || expires_cursor) && !quit);
|
||||
} while(!quit);
|
||||
|
||||
latencyEndMonitor(latency);
|
||||
|
||||
+248
-594
File diff suppressed because it is too large
Load Diff
+47
-84
@@ -5,11 +5,32 @@
|
||||
* tables of power of two in size are used, collisions are handled by
|
||||
* chaining. See the source code for more information... :)
|
||||
*
|
||||
* Copyright (c) 2006-Present, Redis Ltd.
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __DICT_H
|
||||
@@ -23,36 +44,18 @@
|
||||
#define DICT_OK 0
|
||||
#define DICT_ERR 1
|
||||
|
||||
/* Hash table parameters */
|
||||
#define HASHTABLE_MIN_FILL 8 /* Minimal hash table fill 12.5%(100/8) */
|
||||
|
||||
typedef struct dictEntry dictEntry; /* opaque */
|
||||
|
||||
typedef struct dict dict;
|
||||
|
||||
typedef struct dictType {
|
||||
/* Callbacks */
|
||||
uint64_t (*hashFunction)(const void *key);
|
||||
void *(*keyDup)(dict *d, const void *key);
|
||||
void *(*valDup)(dict *d, const void *obj);
|
||||
int (*keyCompare)(dict *d, const void *key1, const void *key2);
|
||||
void (*keyDestructor)(dict *d, void *key);
|
||||
void (*valDestructor)(dict *d, void *obj);
|
||||
int (*resizeAllowed)(size_t moreMem, double usedRatio);
|
||||
/* Invoked at the start of dict initialization/rehashing (old and new ht are already created) */
|
||||
void (*rehashingStarted)(dict *d);
|
||||
/* Invoked at the end of dict initialization/rehashing of all the entries from old to new ht. Both ht still exists
|
||||
* and are cleaned up after this callback. */
|
||||
void (*rehashingCompleted)(dict *d);
|
||||
/* Invoked when the size of the dictionary changes.
|
||||
* The `delta` parameter can be positive (size increase) or negative (size decrease). */
|
||||
void (*bucketChanged)(dict *d, long long delta);
|
||||
/* Allow a dict to carry extra caller-defined metadata. The
|
||||
* extra memory is initialized to 0 when a dict is allocated. */
|
||||
size_t (*dictMetadataBytes)(dict *d);
|
||||
|
||||
/* Data */
|
||||
void *userdata;
|
||||
|
||||
int (*expandAllowed)(size_t moreMem, double usedRatio);
|
||||
/* Flags */
|
||||
/* The 'no_value' flag, if set, indicates that values are not used, i.e. the
|
||||
* dict is a set. When this flag is set, it's not possible to access the
|
||||
@@ -65,32 +68,14 @@ typedef struct dictType {
|
||||
unsigned int keys_are_odd:1;
|
||||
/* TODO: Add a 'keys_are_even' flag and use a similar optimization if that
|
||||
* flag is set. */
|
||||
/* Sometimes we want the ability to store a key in a given way inside the hash
|
||||
* function, and lookup it in some other way without resorting to any kind of
|
||||
* conversion. For instance the key may be stored as a structure also
|
||||
* representing other things, but the lookup happens via just a pointer to a
|
||||
* null terminated string. Optionally providing additional hash/cmp functions,
|
||||
* dict supports such usage. In that case we'll have a hashFunction() that will
|
||||
* expect a null terminated C string, and a storedHashFunction() that will
|
||||
* instead expect the structure. Similarly, the two comparison functions will
|
||||
* work differently. The keyCompare() will treat the first argument as a pointer
|
||||
* to a C string and the other as a structure (this way we can directly lookup
|
||||
* the structure key using the C string). While the storedKeyCompare() will
|
||||
* check if two pointers to the key in structure form are the same.
|
||||
*
|
||||
* However, functions of dict that gets key as argument (void *key) don't get
|
||||
* any indication whether it is a lookup or stored key. To indicate that
|
||||
* you intend to use key of type stored-key, and, consequently, use
|
||||
* dedicated compare and hash functions of stored-key, is by calling
|
||||
* dictUseStoredKeyApi(1) before using any of the dict functions that gets
|
||||
* key as a parameter and then call again dictUseStoredKeyApi(0) once done.
|
||||
*
|
||||
* Set to NULL both functions, if you don't want to support this feature. */
|
||||
uint64_t (*storedHashFunction)(const void *key);
|
||||
int (*storedKeyCompare)(dict *d, const void *key1, const void *key2);
|
||||
|
||||
/* Optional callback called when the dict is destroyed. */
|
||||
void (*onDictRelease)(dict *d);
|
||||
/* Allow each dict and dictEntry to carry extra caller-defined metadata. The
|
||||
* extra memory is initialized to 0 when allocated. */
|
||||
size_t (*dictEntryMetadataBytes)(dict *d);
|
||||
size_t (*dictMetadataBytes)(void);
|
||||
/* Optional callback called after an entry has been reallocated (due to
|
||||
* active defrag). Only called if the entry has metadata. */
|
||||
void (*afterReplaceEntry)(dict *d, dictEntry *entry);
|
||||
} dictType;
|
||||
|
||||
#define DICTHT_SIZE(exp) ((exp) == -1 ? 0 : (unsigned long)1<<(exp))
|
||||
@@ -105,12 +90,12 @@ struct dict {
|
||||
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
|
||||
|
||||
/* Keep small vars at end for optimal (minimal) struct padding */
|
||||
unsigned pauserehash : 15; /* If >0 rehashing is paused */
|
||||
|
||||
unsigned useStoredKeyApi : 1; /* See comment of storedHashFunction above */
|
||||
int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
|
||||
signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) */
|
||||
int16_t pauseAutoResize; /* If >0 automatic resizing is disallowed (<0 indicates coding error) */
|
||||
void *metadata[];
|
||||
|
||||
void *metadata[]; /* An arbitrary number of bytes (starting at a
|
||||
* pointer-aligned address) of size as defined
|
||||
* by dictType's dictEntryBytes. */
|
||||
};
|
||||
|
||||
/* If safe is set to 1 this is a safe iterator, that means, you can call
|
||||
@@ -126,16 +111,6 @@ typedef struct dictIterator {
|
||||
unsigned long long fingerprint;
|
||||
} dictIterator;
|
||||
|
||||
typedef struct dictStats {
|
||||
int htidx;
|
||||
unsigned long buckets;
|
||||
unsigned long maxChainLen;
|
||||
unsigned long totalChainLen;
|
||||
unsigned long htSize;
|
||||
unsigned long htUsed;
|
||||
unsigned long *clvector;
|
||||
} dictStats;
|
||||
|
||||
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
typedef void *(dictDefragAllocFunction)(void *ptr);
|
||||
typedef struct {
|
||||
@@ -163,20 +138,17 @@ typedef struct {
|
||||
(d)->type->keyCompare((d), key1, key2) : \
|
||||
(key1) == (key2))
|
||||
|
||||
#define dictMetadata(d) (&(d)->metadata)
|
||||
#define dictMetadataSize(d) ((d)->type->dictMetadataBytes \
|
||||
? (d)->type->dictMetadataBytes(d) : 0)
|
||||
#define dictEntryMetadataSize(d) ((d)->type->dictEntryMetadataBytes \
|
||||
? (d)->type->dictEntryMetadataBytes(d) : 0)
|
||||
#define dictMetadataSize(d) ((d)->type->dictMetadataBytes \
|
||||
? (d)->type->dictMetadataBytes() : 0)
|
||||
|
||||
#define dictBuckets(d) (DICTHT_SIZE((d)->ht_size_exp[0])+DICTHT_SIZE((d)->ht_size_exp[1]))
|
||||
#define dictHashKey(d, key) ((d)->type->hashFunction(key))
|
||||
#define dictSlots(d) (DICTHT_SIZE((d)->ht_size_exp[0])+DICTHT_SIZE((d)->ht_size_exp[1]))
|
||||
#define dictSize(d) ((d)->ht_used[0]+(d)->ht_used[1])
|
||||
#define dictIsEmpty(d) ((d)->ht_used[0] == 0 && (d)->ht_used[1] == 0)
|
||||
#define dictIsRehashing(d) ((d)->rehashidx != -1)
|
||||
#define dictPauseRehashing(d) ((d)->pauserehash++)
|
||||
#define dictResumeRehashing(d) ((d)->pauserehash--)
|
||||
#define dictIsRehashingPaused(d) ((d)->pauserehash > 0)
|
||||
#define dictPauseAutoResize(d) ((d)->pauseAutoResize++)
|
||||
#define dictResumeAutoResize(d) ((d)->pauseAutoResize--)
|
||||
#define dictUseStoredKeyApi(d, flag) ((d)->useStoredKeyApi = (flag))
|
||||
|
||||
/* If our unsigned long type can store a 64 bit number, use a 64 bit PRNG. */
|
||||
#if ULONG_MAX >= 0xffffffffffffffff
|
||||
@@ -193,10 +165,9 @@ typedef enum {
|
||||
|
||||
/* API */
|
||||
dict *dictCreate(dictType *type);
|
||||
void dictTypeAddMeta(dict **d, dictType *typeWithMeta);
|
||||
int dictExpand(dict *d, unsigned long size);
|
||||
int dictTryExpand(dict *d, unsigned long size);
|
||||
int dictShrink(dict *d, unsigned long size);
|
||||
void *dictMetadata(dict *d);
|
||||
int dictAdd(dict *d, void *key, void *val);
|
||||
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing);
|
||||
void *dictFindPositionForInsert(dict *d, const void *key, dictEntry **existing);
|
||||
@@ -209,11 +180,9 @@ void dictFreeUnlinkedEntry(dict *d, dictEntry *he);
|
||||
dictEntry *dictTwoPhaseUnlinkFind(dict *d, const void *key, dictEntry ***plink, int *table_index);
|
||||
void dictTwoPhaseUnlinkFree(dict *d, dictEntry *he, dictEntry **plink, int table_index);
|
||||
void dictRelease(dict *d);
|
||||
dictEntry *dictFindByHashAndPtr(dict *d, const void *oldptr, const uint64_t hash);
|
||||
dictEntry * dictFind(dict *d, const void *key);
|
||||
void *dictFetchValue(dict *d, const void *key);
|
||||
int dictShrinkIfNeeded(dict *d);
|
||||
int dictExpandIfNeeded(dict *d);
|
||||
int dictResize(dict *d);
|
||||
void dictSetKey(dict *d, dictEntry* de, void *key);
|
||||
void dictSetVal(dict *d, dictEntry *de, void *val);
|
||||
void dictSetSignedIntegerVal(dictEntry *de, int64_t val);
|
||||
@@ -247,19 +216,13 @@ uint64_t dictGenCaseHashFunction(const unsigned char *buf, size_t len);
|
||||
void dictEmpty(dict *d, void(callback)(dict*));
|
||||
void dictSetResizeEnabled(dictResizeEnable enable);
|
||||
int dictRehash(dict *d, int n);
|
||||
int dictRehashMicroseconds(dict *d, uint64_t us);
|
||||
int dictRehashMilliseconds(dict *d, int ms);
|
||||
void dictSetHashFunctionSeed(uint8_t *seed);
|
||||
uint8_t *dictGetHashFunctionSeed(void);
|
||||
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, void *privdata);
|
||||
unsigned long dictScanDefrag(dict *d, unsigned long v, dictScanFunction *fn, dictDefragFunctions *defragfns, void *privdata);
|
||||
uint64_t dictGetHash(dict *d, const void *key);
|
||||
dictEntry *dictFindEntryByPtrAndHash(dict *d, const void *oldptr, uint64_t hash);
|
||||
void dictRehashingInfo(dict *d, unsigned long long *from_size, unsigned long long *to_size);
|
||||
|
||||
size_t dictGetStatsMsg(char *buf, size_t bufsize, dictStats *stats, int full);
|
||||
dictStats* dictGetStatsHt(dict *d, int htidx, int full);
|
||||
void dictCombineStats(dictStats *from, dictStats *into);
|
||||
void dictFreeStats(dictStats *stats);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int dictTest(int argc, char *argv[], int flags);
|
||||
|
||||
-2440
File diff suppressed because it is too large
Load Diff
-306
@@ -1,306 +0,0 @@
|
||||
/*
|
||||
* Copyright Redis Ltd. 2024 - present
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0 (RSALv2)
|
||||
* or the Server Side Public License v1 (SSPLv1).
|
||||
*
|
||||
*
|
||||
* WHAT IS EBUCKETS?
|
||||
* -----------------
|
||||
* ebuckets is being used to store items that are set with expiration-time. It
|
||||
* supports the basic API of add, remove and active expiration. The implementation
|
||||
* of it is based on rax-tree, or plain linked-list when small. The expiration time
|
||||
* of the items are used as the key to traverse rax-tree.
|
||||
*
|
||||
* Instead of holding a distinct item in each leaf of the rax-tree we can aggregate
|
||||
* items into small segments and hold it in each leaf. This way we can avoid
|
||||
* frequent modification of the rax-tree, since many of the modifications
|
||||
* will be done only at the segment level. It will also save memory because
|
||||
* rax-tree can be costly, around 40 bytes per leaf (with rax-key limited to 6
|
||||
* bytes). Whereas each additional item in the segment will cost the size of the
|
||||
* 'next' pointer in a list (8 bytes) and few more bytes for maintenance of the
|
||||
* segment.
|
||||
*
|
||||
* EBUCKETS STRUCTURE
|
||||
* ------------------
|
||||
* The ebuckets data structure is organized in a hierarchical manner as follows:
|
||||
*
|
||||
* 1. ebuckets: This is the top-level data structure. It can be either a rax tree
|
||||
* or a plain linked list. It contains one or more buckets, each representing
|
||||
* an interval in time.
|
||||
*
|
||||
* 2. bucket: Each bucket represents an interval in time and contains one or more
|
||||
* segments. The key in the rax-tree for each bucket represents low
|
||||
* bound expiration-time for the items within this bucket. The key of the
|
||||
* following bucket represents the upper bound expiration-time.
|
||||
*
|
||||
* 3. segment: Each segment within a bucket can hold up to `EB_SEG_MAX_ITEMS`
|
||||
* items as a linked list. If there are more, the segment will try to
|
||||
* split the bucket. To avoid wasting memory, it is a singly linked list (only
|
||||
* next-item pointer). It is a cyclic linked-list to allow efficient removal of
|
||||
* items from the middle of the segment without traversing the rax tree.
|
||||
*
|
||||
* 4. item: Each item that is stored in ebuckets should embed the ExpireMeta
|
||||
* struct and supply getter function (see EbucketsType.getExpireMeta). This
|
||||
* struct holds the expire-time of the item and few more fields that are used
|
||||
* to maintain the segments data-structure.
|
||||
*
|
||||
* SPLITTING BUCKET
|
||||
* ----------------
|
||||
* Each segment can hold up-to `EB_SEG_MAX_ITEMS` items. On insertion of new
|
||||
* item, it will try to split the segment. Here is an example For adding item
|
||||
* with expiration of 42 to a segment that already reached its maximum capacity
|
||||
* which will cause to split of the segment and in turn split of the bucket as
|
||||
* well to a finer grained ranges:
|
||||
*
|
||||
* BUCKETS BUCKETS
|
||||
* [ 00-10 ] -> size(Seg0) = 11 ==> [ 00-10 ] -> size(Seg0) = 11
|
||||
* [ 11-76 ] -> size(Seg1) = 16 [ 11-36 ] -> size(Seg1) = 9
|
||||
* [ 37-76 ] -> size(Seg2) = 7
|
||||
*
|
||||
* EXTENDING BUCKET
|
||||
* ----------------
|
||||
* In the example above, the reason it wasn't split evenly is that Seg1 must have
|
||||
* been holding items with same TTL and they must reside together in the same
|
||||
* bucket after the split. Which brings us to another important point. If there
|
||||
* is a segment that reached its maximum capacity and all the items have same
|
||||
* expiration-time key, then we cannot split the bucket but aggregate all the
|
||||
* items, with same expiration time key, by allocating an extended-segment and
|
||||
* chain it to the first segment in visited bucket. In that sense, extended
|
||||
* segments will only hold items with same expiration-time key.
|
||||
*
|
||||
* BUCKETS BUCKETS
|
||||
* [ 00-10 ] -> size(Seg0)=11 ==> [ 00-10 ] -> size(Seg0)=11
|
||||
* [ 11-12 ] -> size(Seg1)=16 [ 11-12 ] -> size(Seg1)=1 -> size(Seg2)=16
|
||||
*
|
||||
* LIMITING RAX TREE DEPTH
|
||||
* -----------------------
|
||||
* The rax tree is basically a B-tree and its depth is bounded by the sizeof of
|
||||
* the key. Holding 6 bytes for expiration-time key is more than enough to represent
|
||||
* unix-time in msec, and in turn the depth of the tree is limited to 6 levels.
|
||||
* At a first glance it might look sufficient but we need take into consideration
|
||||
* the heavyweight maintenance and traversal of each node in the B-tree.
|
||||
*
|
||||
* And so, we can further prune the tree such that holding keys with msec precision
|
||||
* in the tree doesn't bring with it much value. The active-expiration operation can
|
||||
* live with deletion of expired items, say, older than 1 sec, which means the size
|
||||
* of time-expiration keys to the rax tree become no more than ~4.5 bytes and we
|
||||
* also get rid of the "noisy" bits which most probably will cause to yet another
|
||||
* branching and modification of the rax tree in case of items with time-expiration
|
||||
* difference of less than 1 second. The lazy expiration will still be precise and
|
||||
* without compromise on accuracy because the exact expiration-time is kept
|
||||
* attached as well to each item, in `ExpireMeta`, and each traversal of item with
|
||||
* expiration will behave as expected down to the msec. Take care to configure
|
||||
* `EB_BUCKET_KEY_PRECISION` according to your needs.
|
||||
*
|
||||
* EBUCKET KEY
|
||||
* -----------
|
||||
* Taking into account configured value of `EB_BUCKET_KEY_PRECISION`, two items
|
||||
* with expiration-time t1 and t2 will be considered to have the same key in the
|
||||
* rax-tree/buckets if and only if:
|
||||
*
|
||||
* EB_BUCKET_KEY(t1) == EB_BUCKET_KEY(t2)
|
||||
*
|
||||
* EBUCKETS CREATION
|
||||
* -----------------
|
||||
* To avoid the cost of allocating rax data-structure for only few elements,
|
||||
* ebuckets will start as a simple linked-list and only when it reaches some
|
||||
* threshold, it will be converted to rax.
|
||||
*
|
||||
* TODO
|
||||
* ----
|
||||
* - ebRemove() optimize to merge small segments into one segment.
|
||||
* - ebAdd() Fix pathological case of cascade addition of items into rax such
|
||||
* that their values are smaller/bigger than visited extended-segment which ends
|
||||
* up with multiple segments with a single item in each segment.
|
||||
*/
|
||||
|
||||
#ifndef __EBUCKETS_H
|
||||
#define __EBUCKETS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include "rax.h"
|
||||
|
||||
/*
|
||||
* EB_BUCKET_KEY_PRECISION - Defines the number of bits to ignore from the
|
||||
* expiration-time when mapping to buckets. The higher the value, the more items
|
||||
* with similar expiration-time will be aggregated into the same bucket. The lower
|
||||
* the value, the more "accurate" the active expiration of buckets will be.
|
||||
*
|
||||
* Note that the accurate time expiration of each item is preserved anyway and
|
||||
* enforced by lazy expiration. It only impacts the active expiration that will
|
||||
* be able to work on buckets older than (1<<EB_BUCKET_KEY_PRECISION) msec ago.
|
||||
* For example if EB_BUCKET_KEY_PRECISION is 10, then active expiration
|
||||
* will work only on buckets that already got expired at least 1sec ago.
|
||||
*
|
||||
* The idea of it is to trim the rax tree depth, avoid having too many branches,
|
||||
* and reduce frequent modifications of the tree to the minimum.
|
||||
*/
|
||||
#define EB_BUCKET_KEY_PRECISION 0 /* TBD: modify to 10 */
|
||||
|
||||
/* From expiration time to bucket-key */
|
||||
#define EB_BUCKET_KEY(exptime) ((exptime) >> EB_BUCKET_KEY_PRECISION)
|
||||
|
||||
|
||||
#define EB_EXPIRE_TIME_MAX ((uint64_t)0x0000FFFFFFFFFFFF) /* Maximum expire-time. */
|
||||
#define EB_EXPIRE_TIME_INVALID (EB_EXPIRE_TIME_MAX+1) /* assumed bigger than max */
|
||||
|
||||
/* Handler to ebuckets DS. Pointer to a list, rax or NULL (empty DS). See also ebIsList(). */
|
||||
typedef void *ebuckets;
|
||||
|
||||
/* Users of ebuckets will store `eItem` which is just a void pointer to their
|
||||
* element. In addition, eItem should embed the ExpireMeta struct and supply
|
||||
* getter function (see EbucketsType.getExpireMeta).
|
||||
*/
|
||||
typedef void *eItem;
|
||||
|
||||
/* This struct Should be embedded inside `eItem` and must be aligned in memory. */
|
||||
typedef struct ExpireMeta {
|
||||
/* 48bits of unix-time in msec. This value is sufficient to represent, in
|
||||
* unix-time, until the date of 02 August, 10889
|
||||
*/
|
||||
uint32_t expireTimeLo; /* Low bits of expireTime. */
|
||||
uint16_t expireTimeHi; /* High bits of expireTime. */
|
||||
|
||||
unsigned int lastInSegment : 1; /* Last item in segment. If set, then 'next' will
|
||||
point to the NextSegHdr, unless lastItemBucket=1
|
||||
then it will point to segment header of the
|
||||
current segment. */
|
||||
unsigned int firstItemBucket : 1; /* First item in bucket. This flag assist
|
||||
to manipulate segments directly without
|
||||
the need to traverse from start the
|
||||
rax tree */
|
||||
unsigned int lastItemBucket : 1; /* Last item in bucket. This flag assist
|
||||
to manipulate segments directly without
|
||||
the need to traverse from start the
|
||||
rax tree */
|
||||
unsigned int numItems : 5; /* Only first item in segment will maintain
|
||||
this value. */
|
||||
|
||||
unsigned int trash : 1; /* This flag indicates whether the ExpireMeta
|
||||
associated with the item is leftover.
|
||||
There is always a potential to reuse the
|
||||
item after removal/deletion. Note that,
|
||||
the user can still safely O(1) TTL lookup
|
||||
a given item and verify whether attached
|
||||
TTL is valid or leftover. See function
|
||||
ebGetExpireTime(). */
|
||||
|
||||
unsigned int userData : 3; /* ebuckets can be used to store in same
|
||||
instance few different types of items,
|
||||
such as, listpack and hash. This field
|
||||
is reserved to store such identification
|
||||
associated with the item and can help
|
||||
to distinct on delete or expire callback.
|
||||
It is not used by ebuckets internally and
|
||||
should be maintained by the user */
|
||||
|
||||
unsigned int reserved : 4;
|
||||
|
||||
void *next; /* - If not last item in segment then next
|
||||
points to next eItem (lastInSegment=0).
|
||||
- If last in segment but not last in
|
||||
bucket (lastItemBucket=0) then it
|
||||
points to next segment header.
|
||||
- If last in bucket then it points to
|
||||
current segment header (Can be either
|
||||
of type FirstSegHdr or NextSegHdr). */
|
||||
} ExpireMeta;
|
||||
|
||||
/* Each instance of ebuckets need to have corresponding EbucketsType that holds
|
||||
* the necessary callbacks and configuration to operate correctly on the type
|
||||
* of items that are stored in it. Conceptually it should have hold reference
|
||||
* from ebuckets instance to this type, but to save memory we will pass it as
|
||||
* an argument to each API call. */
|
||||
typedef struct EbucketsType {
|
||||
/* getter to extract the ExpireMeta from the item */
|
||||
ExpireMeta* (*getExpireMeta)(const eItem item);
|
||||
|
||||
/* Called during ebDestroy(). Set to NULL if not needed. */
|
||||
void (*onDeleteItem)(eItem item, void *ctx);
|
||||
|
||||
/* Is addresses of items are odd in memory. It is taken into consideration
|
||||
* and used by ebuckets to know how to distinct between ebuckets pointer to
|
||||
* rax versus a pointer to item which is head of list. */
|
||||
unsigned int itemsAddrAreOdd;
|
||||
} EbucketsType;
|
||||
|
||||
/* Returned value by `onExpireItem` callback to indicate the action to be taken by
|
||||
* ebExpire(). */
|
||||
typedef enum ExpireAction {
|
||||
ACT_REMOVE_EXP_ITEM=0, /* Remove the item from ebuckets. */
|
||||
ACT_UPDATE_EXP_ITEM, /* Re-insert the item with updated expiration-time.
|
||||
Before returning this value, the cb need to
|
||||
update expiration time of the item by assisting
|
||||
function ebSetMetaExpTime(). The item will be
|
||||
kept aside and will be added again to ebuckets
|
||||
at the end of ebExpire() */
|
||||
ACT_STOP_ACTIVE_EXP /* Stop active-expiration. It will assume that
|
||||
provided 'item' wasn't deleted by the callback. */
|
||||
} ExpireAction;
|
||||
|
||||
/* ExpireInfo is used to pass input and output parameters to ebExpire(). */
|
||||
typedef struct ExpireInfo {
|
||||
/* onExpireItem - Called during active-expiration by ebExpire() */
|
||||
ExpireAction (*onExpireItem)(eItem item, void *ctx);
|
||||
|
||||
uint64_t maxToExpire; /* [INPUT ] Limit of number expired items to scan */
|
||||
void *ctx; /* [INPUT ] context to pass to onExpireItem */
|
||||
uint64_t now; /* [INPUT ] Current time in msec. */
|
||||
uint64_t itemsExpired; /* [OUTPUT] Returns the number of expired or updated items. */
|
||||
uint64_t nextExpireTime; /* [OUTPUT] Next expiration time. Returns
|
||||
EB_EXPIRE_TIME_INVALID if none left. */
|
||||
} ExpireInfo;
|
||||
|
||||
/* ebuckets API */
|
||||
|
||||
static inline ebuckets ebCreate(void) { return NULL; } /* Empty ebuckets */
|
||||
|
||||
void ebDestroy(ebuckets *eb, EbucketsType *type, void *deletedItemsCbCtx);
|
||||
|
||||
void ebExpire(ebuckets *eb, EbucketsType *type, ExpireInfo *info);
|
||||
|
||||
uint64_t ebExpireDryRun(ebuckets eb, EbucketsType *type, uint64_t now);
|
||||
|
||||
static inline int ebIsEmpty(ebuckets eb) { return eb == NULL; }
|
||||
|
||||
uint64_t ebGetNextTimeToExpire(ebuckets eb, EbucketsType *type);
|
||||
|
||||
uint64_t ebGetMaxExpireTime(ebuckets eb, EbucketsType *type, int accurate);
|
||||
|
||||
uint64_t ebGetTotalItems(ebuckets eb, EbucketsType *type);
|
||||
|
||||
/* Item related API */
|
||||
|
||||
int ebRemove(ebuckets *eb, EbucketsType *type, eItem item);
|
||||
|
||||
int ebAdd(ebuckets *eb, EbucketsType *type, eItem item, uint64_t expireTime);
|
||||
|
||||
uint64_t ebGetExpireTime(EbucketsType *type, eItem item);
|
||||
|
||||
typedef eItem (ebDefragFunction)(const eItem item);
|
||||
eItem ebDefragItem(ebuckets *eb, EbucketsType *type, eItem item, ebDefragFunction *fn);
|
||||
|
||||
static inline uint64_t ebGetMetaExpTime(ExpireMeta *expMeta) {
|
||||
return (((uint64_t)(expMeta)->expireTimeHi << 32) | (expMeta)->expireTimeLo);
|
||||
}
|
||||
|
||||
static inline void ebSetMetaExpTime(ExpireMeta *expMeta, uint64_t t) {
|
||||
expMeta->expireTimeLo = (uint32_t)(t&0xFFFFFFFF);
|
||||
expMeta->expireTimeHi = (uint16_t)((t) >> 32);
|
||||
}
|
||||
|
||||
/* Debug API */
|
||||
|
||||
void ebValidate(ebuckets eb, EbucketsType *type);
|
||||
|
||||
void ebPrint(ebuckets eb, EbucketsType *type);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int ebucketsTest(int argc, char *argv[], int flags);
|
||||
#endif
|
||||
|
||||
#endif /* __EBUCKETS_H */
|
||||
+24
-3
@@ -13,11 +13,32 @@
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2011-Present, Redis Ltd.
|
||||
* Copyright (c) 2011-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
+24
-3
@@ -2,11 +2,32 @@
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2011-Present, Redis Ltd.
|
||||
* Copyright (c) 2011-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __ENDIANCONV_H
|
||||
|
||||
+40
-129
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2011-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -17,20 +38,14 @@
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#if defined(USE_JEMALLOC)
|
||||
#include <lstate.h>
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
|
||||
static int gc_count = 0; /* Counter for the number of GC requests, reset after each GC execution */
|
||||
|
||||
void ldbInit(void);
|
||||
void ldbDisable(client *c);
|
||||
void ldbEnable(client *c);
|
||||
void evalGenericCommandWithDebugging(client *c, int evalsha);
|
||||
sds ldbCatStackValue(sds s, lua_State *lua, int idx);
|
||||
listNode *luaScriptsLRUAdd(client *c, sds sha, int evalsha);
|
||||
|
||||
static void dictLuaScriptDestructor(dict *d, void *val) {
|
||||
UNUSED(d);
|
||||
@@ -43,7 +58,7 @@ static uint64_t dictStrCaseHash(const void *key) {
|
||||
return dictGenCaseHashFunction((unsigned char*)key, strlen((char*)key));
|
||||
}
|
||||
|
||||
/* lctx.lua_scripts sha (as sds string) -> scripts (as luaScript) cache. */
|
||||
/* server.lua_scripts sha (as sds string) -> scripts (as luaScript) cache. */
|
||||
dictType shaScriptObjectDictType = {
|
||||
dictStrCaseHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
@@ -59,7 +74,6 @@ struct luaCtx {
|
||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||
client *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||
list *lua_scripts_lru_list; /* A list of SHA1, first in first out LRU eviction. */
|
||||
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
||||
} lctx;
|
||||
|
||||
@@ -167,23 +181,18 @@ int luaRedisReplicateCommandsCommand(lua_State *lua) {
|
||||
*
|
||||
* However it is simpler to just call scriptingReset() that does just that. */
|
||||
void scriptingInit(int setup) {
|
||||
lua_State *lua = lua_open();
|
||||
|
||||
if (setup) {
|
||||
lctx.lua_client = NULL;
|
||||
server.script_disable_deny_script = 0;
|
||||
ldbInit();
|
||||
}
|
||||
|
||||
lua_State *lua = createLuaState();
|
||||
if (lua == NULL) {
|
||||
serverLog(LL_WARNING, "Failed creating the lua VM.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Initialize a dictionary we use to map SHAs to scripts.
|
||||
* Initialize a list we use for lua script evictions, it shares the
|
||||
* sha with the dictionary, so free fn is not set. */
|
||||
* This is useful for replication, as we need to replicate EVALSHA
|
||||
* as EVAL, so we need to remember the associated script. */
|
||||
lctx.lua_scripts = dictCreate(&shaScriptObjectDictType);
|
||||
lctx.lua_scripts_lru_list = listCreate();
|
||||
lctx.lua_scripts_mem = 0;
|
||||
|
||||
luaRegisterRedisAPI(lua);
|
||||
@@ -257,32 +266,16 @@ void scriptingInit(int setup) {
|
||||
lctx.lua = lua;
|
||||
}
|
||||
|
||||
/* Free lua_scripts dict and close lua interpreter. */
|
||||
void freeLuaScriptsSync(dict *lua_scripts, list *lua_scripts_lru_list, lua_State *lua) {
|
||||
dictRelease(lua_scripts);
|
||||
listRelease(lua_scripts_lru_list);
|
||||
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* When lua is closed, destroy the previously used private tcache. */
|
||||
void *ud = (global_State*)G(lua)->ud;
|
||||
unsigned int lua_tcache = (unsigned int)(uintptr_t)ud;
|
||||
#endif
|
||||
|
||||
lua_gc(lua, LUA_GCCOLLECT, 0);
|
||||
lua_close(lua);
|
||||
|
||||
#if defined(USE_JEMALLOC)
|
||||
je_mallctl("tcache.destroy", NULL, NULL, (void *)&lua_tcache, sizeof(unsigned int));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Release resources related to Lua scripting.
|
||||
* This function is used in order to reset the scripting environment. */
|
||||
void scriptingRelease(int async) {
|
||||
if (async)
|
||||
freeLuaScriptsAsync(lctx.lua_scripts, lctx.lua_scripts_lru_list, lctx.lua);
|
||||
freeLuaScriptsAsync(lctx.lua_scripts);
|
||||
else
|
||||
freeLuaScriptsSync(lctx.lua_scripts, lctx.lua_scripts_lru_list, lctx.lua);
|
||||
dictRelease(lctx.lua_scripts);
|
||||
lctx.lua_scripts_mem = 0;
|
||||
lua_gc(lctx.lua, LUA_GCCOLLECT, 0);
|
||||
lua_close(lctx.lua);
|
||||
}
|
||||
|
||||
void scriptingReset(int async) {
|
||||
@@ -428,11 +421,8 @@ uint64_t evalGetCommandFlags(client *c, uint64_t cmd_flags) {
|
||||
* exists, and in such a case, it behaves like in the success case.
|
||||
*
|
||||
* If 'c' is not NULL, on error the client is informed with an appropriate
|
||||
* error describing the nature of the problem and the Lua interpreter error.
|
||||
*
|
||||
* 'evalsha' indicating whether the lua function is created from the EVAL context
|
||||
* or from the SCRIPT LOAD. */
|
||||
sds luaCreateFunction(client *c, robj *body, int evalsha) {
|
||||
* error describing the nature of the problem and the Lua interpreter error. */
|
||||
sds luaCreateFunction(client *c, robj *body) {
|
||||
char funcname[43];
|
||||
dictEntry *de;
|
||||
uint64_t script_flags;
|
||||
@@ -449,9 +439,7 @@ sds luaCreateFunction(client *c, robj *body, int evalsha) {
|
||||
ssize_t shebang_len = 0;
|
||||
sds err = NULL;
|
||||
if (evalExtractShebangFlags(body->ptr, &script_flags, &shebang_len, &err) == C_ERR) {
|
||||
if (c != NULL) {
|
||||
addReplyErrorSds(c, err);
|
||||
}
|
||||
addReplyErrorSds(c, err);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -463,7 +451,6 @@ sds luaCreateFunction(client *c, robj *body, int evalsha) {
|
||||
lua_tostring(lctx.lua,-1));
|
||||
}
|
||||
lua_pop(lctx.lua,1);
|
||||
luaGC(lctx.lua, &gc_count);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -478,76 +465,13 @@ sds luaCreateFunction(client *c, robj *body, int evalsha) {
|
||||
l->body = body;
|
||||
l->flags = script_flags;
|
||||
sds sha = sdsnewlen(funcname+2,40);
|
||||
l->node = luaScriptsLRUAdd(c, sha, evalsha);
|
||||
int retval = dictAdd(lctx.lua_scripts,sha,l);
|
||||
serverAssertWithInfo(c ? c : lctx.lua_client,NULL,retval == DICT_OK);
|
||||
lctx.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body);
|
||||
incrRefCount(body);
|
||||
|
||||
/* Perform GC after creating the script and adding it to the LRU list,
|
||||
* as script may be evicted during addition. */
|
||||
luaGC(lctx.lua, &gc_count);
|
||||
|
||||
return sha;
|
||||
}
|
||||
|
||||
/* Delete a Lua function with the specified sha.
|
||||
*
|
||||
* This will delete the lua function from the lua interpreter and delete
|
||||
* the lua function from server. */
|
||||
void luaDeleteFunction(client *c, sds sha) {
|
||||
/* Delete the script from lua interpreter. */
|
||||
char funcname[43];
|
||||
funcname[0] = 'f';
|
||||
funcname[1] = '_';
|
||||
memcpy(funcname+2, sha, 40);
|
||||
funcname[42] = '\0';
|
||||
lua_pushnil(lctx.lua);
|
||||
lua_setfield(lctx.lua, LUA_REGISTRYINDEX, funcname);
|
||||
|
||||
/* Delete the script from server. */
|
||||
dictEntry *de = dictUnlink(lctx.lua_scripts, sha);
|
||||
serverAssertWithInfo(c ? c : lctx.lua_client, NULL, de);
|
||||
luaScript *l = dictGetVal(de);
|
||||
/* We only delete `EVAL` scripts, which must exist in the LRU list. */
|
||||
serverAssert(l->node);
|
||||
listDelNode(lctx.lua_scripts_lru_list, l->node);
|
||||
lctx.lua_scripts_mem -= sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(l->body);
|
||||
dictFreeUnlinkedEntry(lctx.lua_scripts, de);
|
||||
}
|
||||
|
||||
/* Users who abuse EVAL will generate a new lua script on each call, which can
|
||||
* consume large amounts of memory over time. Since EVAL is mostly the one that
|
||||
* abuses the lua cache, and these won't have pipeline issues (scripts won't
|
||||
* disappear when EVALSHA needs it and cause failure), we implement script eviction
|
||||
* only for these (not for one loaded with SCRIPT LOAD). Considering that we don't
|
||||
* have many scripts, then unlike keys, we don't need to worry about the memory
|
||||
* usage of keeping a true sorted LRU linked list.
|
||||
*
|
||||
* 'evalsha' indicating whether the lua function is added from the EVAL context
|
||||
* or from the SCRIPT LOAD.
|
||||
*
|
||||
* Returns the corresponding node added, which is used to save it in luaScript
|
||||
* and use it for quick removal and re-insertion into an LRU list each time the
|
||||
* script is used. */
|
||||
#define LRU_LIST_LENGTH 500
|
||||
listNode *luaScriptsLRUAdd(client *c, sds sha, int evalsha) {
|
||||
/* Script eviction only applies to EVAL, not SCRIPT LOAD. */
|
||||
if (evalsha) return NULL;
|
||||
|
||||
/* Evict oldest. */
|
||||
while (listLength(lctx.lua_scripts_lru_list) >= LRU_LIST_LENGTH) {
|
||||
listNode *ln = listFirst(lctx.lua_scripts_lru_list);
|
||||
sds oldest = listNodeValue(ln);
|
||||
luaDeleteFunction(c, oldest);
|
||||
server.stat_evictedscripts++;
|
||||
}
|
||||
|
||||
/* Add current. */
|
||||
listAddNodeTail(lctx.lua_scripts_lru_list, sha);
|
||||
return listLast(lctx.lua_scripts_lru_list);
|
||||
}
|
||||
|
||||
void evalGenericCommand(client *c, int evalsha) {
|
||||
lua_State *lua = lctx.lua;
|
||||
char funcname[43];
|
||||
@@ -586,7 +510,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
addReplyErrorObject(c, shared.noscripterr);
|
||||
return;
|
||||
}
|
||||
if (luaCreateFunction(c, c->argv[1], evalsha) == NULL) {
|
||||
if (luaCreateFunction(c,c->argv[1]) == NULL) {
|
||||
lua_pop(lua,1); /* remove the error handler from the stack. */
|
||||
/* The error is sent to the client by luaCreateFunction()
|
||||
* itself when it returns NULL. */
|
||||
@@ -612,21 +536,9 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
rctx.flags |= SCRIPT_EVAL_MODE; /* mark the current run as EVAL (as opposed to FCALL) so we'll
|
||||
get appropriate error messages and logs */
|
||||
|
||||
if (l->node) {
|
||||
/* Quick removal and re-insertion after the script is called to
|
||||
* maintain the LRU list. */
|
||||
listUnlinkNode(lctx.lua_scripts_lru_list, l->node);
|
||||
listLinkNodeTail(lctx.lua_scripts_lru_list, l->node);
|
||||
}
|
||||
|
||||
luaCallFunction(&rctx, lua, c->argv+3, numkeys, c->argv+3+numkeys, c->argc-3-numkeys, ldb.active);
|
||||
lua_pop(lua,1); /* Remove the error handler. */
|
||||
scriptResetRun(&rctx);
|
||||
luaGC(lua, &gc_count);
|
||||
|
||||
/* We can no longer touch 'l' here, as it may have been reallocated by activedefrag
|
||||
* during AOF loading of long-running scripts. This issue is not with newly generated
|
||||
* AOF files, in which scripts propagate effects rather than scripts. */
|
||||
}
|
||||
|
||||
void evalCommand(client *c) {
|
||||
@@ -712,7 +624,7 @@ NULL
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"load")) {
|
||||
sds sha = luaCreateFunction(c, c->argv[2], 1);
|
||||
sds sha = luaCreateFunction(c,c->argv[2]);
|
||||
if (sha == NULL) return; /* The error was sent by luaCreateFunction(). */
|
||||
addReplyBulkCBuffer(c,sha,40);
|
||||
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
|
||||
@@ -752,8 +664,7 @@ dict* evalScriptsDict(void) {
|
||||
unsigned long evalScriptsMemory(void) {
|
||||
return lctx.lua_scripts_mem +
|
||||
dictMemUsage(lctx.lua_scripts) +
|
||||
dictSize(lctx.lua_scripts) * sizeof(luaScript) +
|
||||
listLength(lctx.lua_scripts_lru_list) * sizeof(listNode);
|
||||
dictSize(lctx.lua_scripts) * sizeof(luaScript);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
|
||||
+62
-66
@@ -2,11 +2,32 @@
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2016, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -37,7 +58,6 @@ struct evictionPoolEntry {
|
||||
sds key; /* Key name. */
|
||||
sds cached; /* Cached SDS object for key name. */
|
||||
int dbid; /* Key DB number. */
|
||||
int slot; /* Slot. */
|
||||
};
|
||||
|
||||
static struct evictionPoolEntry *EvictionPoolLRU;
|
||||
@@ -122,16 +142,16 @@ void evictionPoolAlloc(void) {
|
||||
* We insert keys on place in ascending order, so keys with the smaller
|
||||
* idle time are on the left, and keys with the higher idle time on the
|
||||
* right. */
|
||||
int evictionPoolPopulate(redisDb *db, kvstore *samplekvs, struct evictionPoolEntry *pool) {
|
||||
|
||||
void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
|
||||
int j, k, count;
|
||||
dictEntry *samples[server.maxmemory_samples];
|
||||
|
||||
int slot = kvstoreGetFairRandomDictIndex(samplekvs);
|
||||
count = kvstoreDictGetSomeKeys(samplekvs,slot,samples,server.maxmemory_samples);
|
||||
count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
|
||||
for (j = 0; j < count; j++) {
|
||||
unsigned long long idle;
|
||||
sds key;
|
||||
robj *o;
|
||||
robj *o = NULL;
|
||||
dictEntry *de;
|
||||
|
||||
de = samples[j];
|
||||
@@ -141,14 +161,13 @@ int evictionPoolPopulate(redisDb *db, kvstore *samplekvs, struct evictionPoolEnt
|
||||
* dictionary (but the expires one) we need to lookup the key
|
||||
* again in the key dictionary to obtain the value object. */
|
||||
if (server.maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {
|
||||
if (samplekvs != db->keys)
|
||||
de = kvstoreDictFind(db->keys, slot, key);
|
||||
if (sampledict != keydict) de = dictFind(keydict, key);
|
||||
o = dictGetVal(de);
|
||||
}
|
||||
|
||||
/* Calculate the idle time according to the policy. This is called
|
||||
* idle just because the code initially handled LRU, but is in fact
|
||||
* just a score where a higher score means better candidate. */
|
||||
* just a score where an higher score means better candidate. */
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
|
||||
idle = estimateObjectIdleTime(o);
|
||||
} else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
@@ -217,11 +236,8 @@ int evictionPoolPopulate(redisDb *db, kvstore *samplekvs, struct evictionPoolEnt
|
||||
pool[k].key = pool[k].cached;
|
||||
}
|
||||
pool[k].idle = idle;
|
||||
pool[k].dbid = db->id;
|
||||
pool[k].slot = slot;
|
||||
pool[k].dbid = dbid;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -233,40 +249,42 @@ int evictionPoolPopulate(redisDb *db, kvstore *samplekvs, struct evictionPoolEnt
|
||||
*
|
||||
* We split the 24 bits into two fields:
|
||||
*
|
||||
* 16 bits 8 bits
|
||||
* +------------------+--------+
|
||||
* + Last access time | LOG_C |
|
||||
* +------------------+--------+
|
||||
* 16 bits 8 bits
|
||||
* +----------------+--------+
|
||||
* + Last decr time | LOG_C |
|
||||
* +----------------+--------+
|
||||
*
|
||||
* LOG_C is a logarithmic counter that provides an indication of the access
|
||||
* frequency. However this field must also be decremented otherwise what used
|
||||
* to be a frequently accessed key in the past, will remain ranked like that
|
||||
* forever, while we want the algorithm to adapt to access pattern changes.
|
||||
*
|
||||
* So the remaining 16 bits are used in order to store the "access time",
|
||||
* So the remaining 16 bits are used in order to store the "decrement time",
|
||||
* a reduced-precision Unix time (we take 16 bits of the time converted
|
||||
* in minutes since we don't care about wrapping around) where the LOG_C
|
||||
* counter decays every minute by default (depends on lfu-decay-time).
|
||||
* counter is halved if it has an high value, or just decremented if it
|
||||
* has a low value.
|
||||
*
|
||||
* New keys don't start at zero, in order to have the ability to collect
|
||||
* some accesses before being trashed away, so they start at LFU_INIT_VAL.
|
||||
* The logarithmic increment performed on LOG_C takes care of LFU_INIT_VAL
|
||||
* when incrementing the key, so that keys starting at LFU_INIT_VAL
|
||||
* (or having a smaller value) have a very high chance of being incremented
|
||||
* on access. (The chance depends on counter and lfu-log-factor.)
|
||||
* on access.
|
||||
*
|
||||
* During decrement, the value of the logarithmic counter is decremented by
|
||||
* one when lfu-decay-time minutes elapsed.
|
||||
* During decrement, the value of the logarithmic counter is halved if
|
||||
* its current value is greater than two times the LFU_INIT_VAL, otherwise
|
||||
* it is just decremented by one.
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* Return the current time in minutes, just taking the least significant
|
||||
* 16 bits. The returned time is suitable to be stored as LDT (last access
|
||||
* 16 bits. The returned time is suitable to be stored as LDT (last decrement
|
||||
* time) for the LFU implementation. */
|
||||
unsigned long LFUGetTimeInMinutes(void) {
|
||||
return (server.unixtime/60) & 65535;
|
||||
}
|
||||
|
||||
/* Given an object ldt (last access time), compute the minimum number of minutes
|
||||
/* Given an object last access time, compute the minimum number of minutes
|
||||
* that elapsed since the last access. Handle overflow (ldt greater than
|
||||
* the current 16 bits minutes time) considering the time as wrapping
|
||||
* exactly once. */
|
||||
@@ -288,10 +306,10 @@ uint8_t LFULogIncr(uint8_t counter) {
|
||||
return counter;
|
||||
}
|
||||
|
||||
/* If the object's ldt (last access time) is reached, decrement the LFU counter but
|
||||
/* If the object decrement time is reached decrement the LFU counter but
|
||||
* do not update LFU fields of the object, we update the access time
|
||||
* and counter in an explicit way when the object is really accessed.
|
||||
* And we will decrement the counter according to the times of
|
||||
* And we will times halve the counter according to the times of
|
||||
* elapsed time than server.lfu_decay_time.
|
||||
* Return the object frequency counter.
|
||||
*
|
||||
@@ -551,7 +569,6 @@ int performEvictions(void) {
|
||||
|
||||
/* Try to smoke-out bugs (server.also_propagate should be empty here) */
|
||||
serverAssert(server.also_propagate.numops == 0);
|
||||
/* Evictions are performed on random keys that have nothing to do with the current command slot. */
|
||||
|
||||
while (mem_freed < (long long)mem_tofree) {
|
||||
int j, k, i;
|
||||
@@ -559,43 +576,27 @@ int performEvictions(void) {
|
||||
sds bestkey = NULL;
|
||||
int bestdbid;
|
||||
redisDb *db;
|
||||
dict *dict;
|
||||
dictEntry *de;
|
||||
|
||||
if (server.maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
|
||||
server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
|
||||
{
|
||||
struct evictionPoolEntry *pool = EvictionPoolLRU;
|
||||
|
||||
while (bestkey == NULL) {
|
||||
unsigned long total_keys = 0;
|
||||
unsigned long total_keys = 0, keys;
|
||||
|
||||
/* We don't want to make local-db choices when expiring keys,
|
||||
* so to start populate the eviction pool sampling keys from
|
||||
* every DB. */
|
||||
for (i = 0; i < server.dbnum; i++) {
|
||||
db = server.db+i;
|
||||
kvstore *kvs;
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
|
||||
kvs = db->keys;
|
||||
} else {
|
||||
kvs = db->expires;
|
||||
}
|
||||
unsigned long sampled_keys = 0;
|
||||
unsigned long current_db_keys = kvstoreSize(kvs);
|
||||
if (current_db_keys == 0) continue;
|
||||
|
||||
total_keys += current_db_keys;
|
||||
int l = kvstoreNumNonEmptyDicts(kvs);
|
||||
/* Do not exceed the number of non-empty slots when looping. */
|
||||
while (l--) {
|
||||
sampled_keys += evictionPoolPopulate(db, kvs, pool);
|
||||
/* We have sampled enough keys in the current db, exit the loop. */
|
||||
if (sampled_keys >= (unsigned long) server.maxmemory_samples)
|
||||
break;
|
||||
/* If there are not a lot of keys in the current db, dict/s may be very
|
||||
* sparsely populated, exit the loop without meeting the sampling
|
||||
* requirement. */
|
||||
if (current_db_keys < (unsigned long) server.maxmemory_samples*10)
|
||||
break;
|
||||
dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
|
||||
db->dict : db->expires;
|
||||
if ((keys = dictSize(dict)) != 0) {
|
||||
evictionPoolPopulate(i, dict, db->dict, pool);
|
||||
total_keys += keys;
|
||||
}
|
||||
}
|
||||
if (!total_keys) break; /* No keys to evict. */
|
||||
@@ -605,13 +606,13 @@ int performEvictions(void) {
|
||||
if (pool[k].key == NULL) continue;
|
||||
bestdbid = pool[k].dbid;
|
||||
|
||||
kvstore *kvs;
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
|
||||
kvs = server.db[bestdbid].keys;
|
||||
de = dictFind(server.db[bestdbid].dict,
|
||||
pool[k].key);
|
||||
} else {
|
||||
kvs = server.db[bestdbid].expires;
|
||||
de = dictFind(server.db[bestdbid].expires,
|
||||
pool[k].key);
|
||||
}
|
||||
de = kvstoreDictFind(kvs, pool[k].slot, pool[k].key);
|
||||
|
||||
/* Remove the entry from the pool. */
|
||||
if (pool[k].key != pool[k].cached)
|
||||
@@ -641,15 +642,10 @@ int performEvictions(void) {
|
||||
for (i = 0; i < server.dbnum; i++) {
|
||||
j = (++next_db) % server.dbnum;
|
||||
db = server.db+j;
|
||||
kvstore *kvs;
|
||||
if (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) {
|
||||
kvs = db->keys;
|
||||
} else {
|
||||
kvs = db->expires;
|
||||
}
|
||||
int slot = kvstoreGetFairRandomDictIndex(kvs);
|
||||
de = kvstoreDictGetRandomKey(kvs, slot);
|
||||
if (de) {
|
||||
dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
|
||||
db->dict : db->expires;
|
||||
if (dictSize(dict) != 0) {
|
||||
de = dictGetRandomKey(dict);
|
||||
bestkey = dictGetKey(de);
|
||||
bestdbid = j;
|
||||
break;
|
||||
|
||||
+60
-144
@@ -2,11 +2,32 @@
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2016, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
@@ -19,10 +40,6 @@
|
||||
* if no access is performed on them.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Constants table from pow(0.98, 1) to pow(0.98, 16).
|
||||
* Help calculating the db->avg_ttl. */
|
||||
static double avg_ttl_factor[16] = {0.98, 0.9604, 0.941192, 0.922368, 0.903921, 0.885842, 0.868126, 0.850763, 0.833748, 0.817073, 0.800731, 0.784717, 0.769022, 0.753642, 0.738569, 0.723798};
|
||||
|
||||
/* Helper function for the activeExpireCycle() function.
|
||||
* This function will try to expire the key that is stored in the hash table
|
||||
* entry 'de' of the 'expires' hash table of a Redis database.
|
||||
@@ -95,8 +112,6 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
#define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which
|
||||
we do extra efforts. */
|
||||
|
||||
#define HFE_DB_BASE_ACTIVE_EXPIRE_FIELDS_PER_SEC 10000
|
||||
|
||||
/* Data used by the expire dict scan callback. */
|
||||
typedef struct {
|
||||
redisDb *db;
|
||||
@@ -124,66 +139,6 @@ void expireScanCallback(void *privdata, const dictEntry *const_de) {
|
||||
data->sampled++;
|
||||
}
|
||||
|
||||
static inline int isExpiryDictValidForSamplingCb(dict *d) {
|
||||
long long numkeys = dictSize(d);
|
||||
unsigned long buckets = dictBuckets(d);
|
||||
/* When there are less than 1% filled buckets, sampling the key
|
||||
* space is expensive, so stop here waiting for better times...
|
||||
* The dictionary will be resized asap. */
|
||||
if (buckets > DICT_HT_INITIAL_SIZE && (numkeys * 100/buckets < 1)) {
|
||||
return C_ERR;
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Active expiration Cycle for hash-fields.
|
||||
*
|
||||
* Note that releasing fields is expected to be more predictable and rewarding
|
||||
* than releasing keys because it is stored in `ebuckets` DS which optimized for
|
||||
* active expiration and in addition the deletion of fields is simple to handle. */
|
||||
static inline void activeExpireHashFieldCycle(int type) {
|
||||
/* Remember current db across calls */
|
||||
static unsigned int currentDb = 0;
|
||||
|
||||
/* Tracks the count of fields actively expired for the current database.
|
||||
* This count continues as long as it fails to actively expire all expired
|
||||
* fields of currentDb, indicating a possible need to adjust the value of
|
||||
* maxToExpire. */
|
||||
static uint64_t activeExpirySequence = 0;
|
||||
/* Threshold for adjusting maxToExpire */
|
||||
const uint32_t EXPIRED_FIELDS_TH = 1000000;
|
||||
|
||||
redisDb *db = server.db + currentDb;
|
||||
|
||||
/* If db is empty, move to next db and return */
|
||||
if (ebIsEmpty(db->hexpires)) {
|
||||
activeExpirySequence = 0;
|
||||
currentDb = (currentDb + 1) % server.dbnum;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Maximum number of fields to actively expire on a single call */
|
||||
uint32_t maxToExpire = HFE_DB_BASE_ACTIVE_EXPIRE_FIELDS_PER_SEC / server.hz;
|
||||
|
||||
/* If running for a while and didn't manage to active-expire all expired fields of
|
||||
* currentDb (i.e. activeExpirySequence becomes significant) then adjust maxToExpire */
|
||||
if ((activeExpirySequence > EXPIRED_FIELDS_TH) && (type == ACTIVE_EXPIRE_CYCLE_SLOW)) {
|
||||
/* maxToExpire is multiplied by a factor between 1 and 32, proportional to
|
||||
* the number of times activeExpirySequence exceeded EXPIRED_FIELDS_TH */
|
||||
uint64_t factor = activeExpirySequence / EXPIRED_FIELDS_TH;
|
||||
maxToExpire *= (factor<32) ? factor : 32;
|
||||
}
|
||||
|
||||
if (hashTypeDbActiveExpire(db, maxToExpire) == maxToExpire) {
|
||||
/* active-expire reached maxToExpire limit */
|
||||
activeExpirySequence += maxToExpire;
|
||||
} else {
|
||||
/* Managed to active-expire all expired fields of currentDb */
|
||||
activeExpirySequence = 0;
|
||||
currentDb = (currentDb + 1) % server.dbnum;
|
||||
}
|
||||
}
|
||||
|
||||
void activeExpireCycle(int type) {
|
||||
/* Adjust the running parameters according to the configured expire
|
||||
* effort. The default effort is 1, and the maximum configurable effort
|
||||
@@ -207,7 +162,6 @@ void activeExpireCycle(int type) {
|
||||
|
||||
int j, iteration = 0;
|
||||
int dbs_per_call = CRON_DBS_PER_CALL;
|
||||
int dbs_performed = 0;
|
||||
long long start = ustime(), timelimit, elapsed;
|
||||
|
||||
/* If 'expire' action is paused, for whatever reason, then don't expire any key.
|
||||
@@ -260,55 +214,46 @@ void activeExpireCycle(int type) {
|
||||
/* Try to smoke-out bugs (server.also_propagate should be empty here) */
|
||||
serverAssert(server.also_propagate.numops == 0);
|
||||
|
||||
/* Stop iteration when one of the following conditions is met:
|
||||
*
|
||||
* 1) We have checked a sufficient number of databases with expiration time.
|
||||
* 2) The time limit has been exceeded.
|
||||
* 3) All databases have been traversed. */
|
||||
for (j = 0; dbs_performed < dbs_per_call && timelimit_exit == 0 && j < server.dbnum; j++) {
|
||||
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
|
||||
/* Scan callback data including expired and checked count per iteration. */
|
||||
expireScanData data;
|
||||
data.ttl_sum = 0;
|
||||
data.ttl_samples = 0;
|
||||
|
||||
redisDb *db = server.db+(current_db % server.dbnum);
|
||||
data.db = db;
|
||||
|
||||
int db_done = 0; /* The scan of the current DB is done? */
|
||||
int update_avg_ttl_times = 0, repeat = 0;
|
||||
|
||||
/* Increment the DB now so we are sure if we run out of time
|
||||
* in the current DB we'll restart from the next. This allows to
|
||||
* distribute the time evenly across DBs. */
|
||||
current_db++;
|
||||
|
||||
/* Interleaving hash-field expiration with key expiration. Better
|
||||
* call it before handling expired keys because HFE DS is optimized for
|
||||
* active expiration */
|
||||
activeExpireHashFieldCycle(type);
|
||||
|
||||
if (kvstoreSize(db->expires))
|
||||
dbs_performed++;
|
||||
|
||||
/* Continue to expire if at the end of the cycle there are still
|
||||
* a big percentage of keys to expire, compared to the number of keys
|
||||
* we scanned. The percentage, stored in config_cycle_acceptable_stale
|
||||
* is not fixed, but depends on the Redis configured "expire effort". */
|
||||
do {
|
||||
unsigned long num;
|
||||
unsigned long num, slots;
|
||||
iteration++;
|
||||
|
||||
/* If there is nothing to expire try next DB ASAP. */
|
||||
if ((num = kvstoreSize(db->expires)) == 0) {
|
||||
if ((num = dictSize(db->expires)) == 0) {
|
||||
db->avg_ttl = 0;
|
||||
break;
|
||||
}
|
||||
slots = dictSlots(db->expires);
|
||||
data.now = mstime();
|
||||
|
||||
/* When there are less than 1% filled slots, sampling the key
|
||||
* space is expensive, so stop here waiting for better times...
|
||||
* The dictionary will be resized asap. */
|
||||
if (slots > DICT_HT_INITIAL_SIZE &&
|
||||
(num*100/slots < 1)) break;
|
||||
|
||||
/* The main collection cycle. Scan through keys among keys
|
||||
* with an expire set, checking for expired ones. */
|
||||
data.sampled = 0;
|
||||
data.expired = 0;
|
||||
data.ttl_sum = 0;
|
||||
data.ttl_samples = 0;
|
||||
|
||||
if (num > config_keys_per_loop)
|
||||
num = config_keys_per_loop;
|
||||
@@ -326,70 +271,41 @@ void activeExpireCycle(int type) {
|
||||
long max_buckets = num*20;
|
||||
long checked_buckets = 0;
|
||||
|
||||
int origin_ttl_samples = data.ttl_samples;
|
||||
|
||||
while (data.sampled < num && checked_buckets < max_buckets) {
|
||||
db->expires_cursor = kvstoreScan(db->expires, db->expires_cursor, -1, expireScanCallback, isExpiryDictValidForSamplingCb, &data);
|
||||
if (db->expires_cursor == 0) {
|
||||
db_done = 1;
|
||||
break;
|
||||
}
|
||||
db->expires_cursor = dictScan(db->expires, db->expires_cursor,
|
||||
expireScanCallback, &data);
|
||||
checked_buckets++;
|
||||
}
|
||||
total_expired += data.expired;
|
||||
total_sampled += data.sampled;
|
||||
|
||||
/* If find keys with ttl not yet expired, we need to update the average TTL stats once. */
|
||||
if (data.ttl_samples - origin_ttl_samples > 0) update_avg_ttl_times++;
|
||||
/* Update the average TTL stats for this database. */
|
||||
if (data.ttl_samples) {
|
||||
long long avg_ttl = data.ttl_sum / data.ttl_samples;
|
||||
|
||||
/* We don't repeat the cycle for the current database if the db is done
|
||||
* for scanning or an acceptable number of stale keys (logically expired
|
||||
* but yet not reclaimed). */
|
||||
repeat = db_done ? 0 : (data.sampled == 0 || (data.expired * 100 / data.sampled) > config_cycle_acceptable_stale);
|
||||
/* Do a simple running average with a few samples.
|
||||
* We just use the current estimate with a weight of 2%
|
||||
* and the previous estimate with a weight of 98%. */
|
||||
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
|
||||
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
|
||||
}
|
||||
|
||||
/* We can't block forever here even if there are many keys to
|
||||
* expire. So after a given amount of microseconds return to the
|
||||
* expire. So after a given amount of milliseconds return to the
|
||||
* caller waiting for the other active expire cycle. */
|
||||
if ((iteration & 0xf) == 0 || !repeat) { /* Update the average TTL stats every 16 iterations or about to exit. */
|
||||
/* Update the average TTL stats for this database,
|
||||
* because this may reach the time limit. */
|
||||
if (data.ttl_samples) {
|
||||
long long avg_ttl = data.ttl_sum / data.ttl_samples;
|
||||
|
||||
/* Do a simple running average with a few samples.
|
||||
* We just use the current estimate with a weight of 2%
|
||||
* and the previous estimate with a weight of 98%. */
|
||||
if (db->avg_ttl == 0) {
|
||||
db->avg_ttl = avg_ttl;
|
||||
} else {
|
||||
/* The origin code is as follow.
|
||||
* for (int i = 0; i < update_avg_ttl_times; i++) {
|
||||
* db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
|
||||
* }
|
||||
* We can convert the loop into a sum of a geometric progression.
|
||||
* db->avg_ttl = db->avg_ttl * pow(0.98, update_avg_ttl_times) +
|
||||
* avg_ttl / 50 * (pow(0.98, update_avg_ttl_times - 1) + ... + 1)
|
||||
* = db->avg_ttl * pow(0.98, update_avg_ttl_times) +
|
||||
* avg_ttl * (1 - pow(0.98, update_avg_ttl_times))
|
||||
* = avg_ttl + (db->avg_ttl - avg_ttl) * pow(0.98, update_avg_ttl_times)
|
||||
* Notice that update_avg_ttl_times is between 1 and 16, we use a constant table
|
||||
* to accelerate the calculation of pow(0.98, update_avg_ttl_times).*/
|
||||
db->avg_ttl = avg_ttl + (db->avg_ttl - avg_ttl) * avg_ttl_factor[update_avg_ttl_times - 1] ;
|
||||
}
|
||||
update_avg_ttl_times = 0;
|
||||
data.ttl_sum = 0;
|
||||
data.ttl_samples = 0;
|
||||
}
|
||||
if ((iteration & 0xf) == 0) { /* check time limit every 16 iterations. */
|
||||
elapsed = ustime()-start;
|
||||
if (elapsed > timelimit) {
|
||||
timelimit_exit = 1;
|
||||
server.stat_expired_time_cap_reached_count++;
|
||||
break;
|
||||
}
|
||||
if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
|
||||
elapsed = ustime()-start;
|
||||
if (elapsed > timelimit) {
|
||||
timelimit_exit = 1;
|
||||
server.stat_expired_time_cap_reached_count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (repeat);
|
||||
/* We don't repeat the cycle for the current database if there are
|
||||
* an acceptable amount of stale keys (logically expired but yet
|
||||
* not reclaimed). */
|
||||
} while (data.sampled == 0 ||
|
||||
(data.expired * 100 / data.sampled) > config_cycle_acceptable_stale);
|
||||
}
|
||||
|
||||
elapsed = ustime()-start;
|
||||
@@ -464,7 +380,7 @@ void expireSlaveKeys(void) {
|
||||
while(dbids && dbid < server.dbnum) {
|
||||
if ((dbids & 1) != 0) {
|
||||
redisDb *db = server.db+dbid;
|
||||
dictEntry *expire = dbFindExpires(db, keyname);
|
||||
dictEntry *expire = dictFind(db->expires,keyname);
|
||||
int expired = 0;
|
||||
|
||||
if (expire &&
|
||||
|
||||
+24
-3
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2009-Present, Redis Ltd.
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _REDIS_FMACRO_H
|
||||
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright Redis Contributors.
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD 3-Clause
|
||||
*
|
||||
* To make it easier to map each part of the format string with each argument,
|
||||
* this file provides a way to write
|
||||
*
|
||||
* printf("a = %s, b = %s, c = %s\n",
|
||||
* arg1, arg2, arg3);
|
||||
*
|
||||
* as
|
||||
*
|
||||
* printf(FMTARGS("a = %s, ", arg1,
|
||||
* "b = %s, ", arg2,
|
||||
* "c = %s\n", arg3));
|
||||
*
|
||||
* FMTARGS is variadic macro which is implemented by passing on its arguments to
|
||||
* two other variadic macros of which one extracts the odd (the formats) and the
|
||||
* other extracts the even (the arguments). The definitions of these macros
|
||||
* include counting the number of macro arguments. Therefore, they don't accept
|
||||
* an unlimited number of arguments. Currently it is fixed to a maximum of 120
|
||||
* formats and arguments.
|
||||
*/
|
||||
#ifndef FMTARGS_H
|
||||
#define FMTARGS_H
|
||||
|
||||
/* A macro to count the number of arguments. */
|
||||
#define NARG(...) NARG_I(__VA_ARGS__,RSEQ_N())
|
||||
#define NARG_I(...) ARG_N(__VA_ARGS__)
|
||||
|
||||
/* Define a macro which will call an arbitrary macro appended with a number indicating
|
||||
* the number of arguments it has. */
|
||||
#define VFUNC_N_(name, n) name##n
|
||||
#define VFUNC_N(name, n) VFUNC_N_(name, n)
|
||||
#define VFUNC(func, ...) VFUNC_N(func, NARG(__VA_ARGS__)) (__VA_ARGS__)
|
||||
|
||||
/* Macros to extract the formats and the arguments from the fmt-arg pairs and
|
||||
* then combine them again with all formats first and the arguments last. */
|
||||
#define COMPACT_FMT(...) VFUNC(COMPACT_FMT_, __VA_ARGS__)
|
||||
#define COMPACT_VALUES(...) VFUNC(COMPACT_VALUES_, __VA_ARGS__)
|
||||
#define FMTARGS(...) COMPACT_FMT(__VA_ARGS__), COMPACT_VALUES(__VA_ARGS__)
|
||||
|
||||
/* Everything below this line is automatically generated by
|
||||
* generate-fmtargs.py. Do not manually edit. */
|
||||
|
||||
#define ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, _71, _72, _73, _74, _75, _76, _77, _78, _79, _80, _81, _82, _83, _84, _85, _86, _87, _88, _89, _90, _91, _92, _93, _94, _95, _96, _97, _98, _99, _100, _101, _102, _103, _104, _105, _106, _107, _108, _109, _110, _111, _112, _113, _114, _115, _116, _117, _118, _119, _120, N, ...) N
|
||||
|
||||
#define RSEQ_N() 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
#define COMPACT_FMT_2(fmt, value) fmt
|
||||
#define COMPACT_FMT_4(fmt, value, ...) fmt COMPACT_FMT_2(__VA_ARGS__)
|
||||
#define COMPACT_FMT_6(fmt, value, ...) fmt COMPACT_FMT_4(__VA_ARGS__)
|
||||
#define COMPACT_FMT_8(fmt, value, ...) fmt COMPACT_FMT_6(__VA_ARGS__)
|
||||
#define COMPACT_FMT_10(fmt, value, ...) fmt COMPACT_FMT_8(__VA_ARGS__)
|
||||
#define COMPACT_FMT_12(fmt, value, ...) fmt COMPACT_FMT_10(__VA_ARGS__)
|
||||
#define COMPACT_FMT_14(fmt, value, ...) fmt COMPACT_FMT_12(__VA_ARGS__)
|
||||
#define COMPACT_FMT_16(fmt, value, ...) fmt COMPACT_FMT_14(__VA_ARGS__)
|
||||
#define COMPACT_FMT_18(fmt, value, ...) fmt COMPACT_FMT_16(__VA_ARGS__)
|
||||
#define COMPACT_FMT_20(fmt, value, ...) fmt COMPACT_FMT_18(__VA_ARGS__)
|
||||
#define COMPACT_FMT_22(fmt, value, ...) fmt COMPACT_FMT_20(__VA_ARGS__)
|
||||
#define COMPACT_FMT_24(fmt, value, ...) fmt COMPACT_FMT_22(__VA_ARGS__)
|
||||
#define COMPACT_FMT_26(fmt, value, ...) fmt COMPACT_FMT_24(__VA_ARGS__)
|
||||
#define COMPACT_FMT_28(fmt, value, ...) fmt COMPACT_FMT_26(__VA_ARGS__)
|
||||
#define COMPACT_FMT_30(fmt, value, ...) fmt COMPACT_FMT_28(__VA_ARGS__)
|
||||
#define COMPACT_FMT_32(fmt, value, ...) fmt COMPACT_FMT_30(__VA_ARGS__)
|
||||
#define COMPACT_FMT_34(fmt, value, ...) fmt COMPACT_FMT_32(__VA_ARGS__)
|
||||
#define COMPACT_FMT_36(fmt, value, ...) fmt COMPACT_FMT_34(__VA_ARGS__)
|
||||
#define COMPACT_FMT_38(fmt, value, ...) fmt COMPACT_FMT_36(__VA_ARGS__)
|
||||
#define COMPACT_FMT_40(fmt, value, ...) fmt COMPACT_FMT_38(__VA_ARGS__)
|
||||
#define COMPACT_FMT_42(fmt, value, ...) fmt COMPACT_FMT_40(__VA_ARGS__)
|
||||
#define COMPACT_FMT_44(fmt, value, ...) fmt COMPACT_FMT_42(__VA_ARGS__)
|
||||
#define COMPACT_FMT_46(fmt, value, ...) fmt COMPACT_FMT_44(__VA_ARGS__)
|
||||
#define COMPACT_FMT_48(fmt, value, ...) fmt COMPACT_FMT_46(__VA_ARGS__)
|
||||
#define COMPACT_FMT_50(fmt, value, ...) fmt COMPACT_FMT_48(__VA_ARGS__)
|
||||
#define COMPACT_FMT_52(fmt, value, ...) fmt COMPACT_FMT_50(__VA_ARGS__)
|
||||
#define COMPACT_FMT_54(fmt, value, ...) fmt COMPACT_FMT_52(__VA_ARGS__)
|
||||
#define COMPACT_FMT_56(fmt, value, ...) fmt COMPACT_FMT_54(__VA_ARGS__)
|
||||
#define COMPACT_FMT_58(fmt, value, ...) fmt COMPACT_FMT_56(__VA_ARGS__)
|
||||
#define COMPACT_FMT_60(fmt, value, ...) fmt COMPACT_FMT_58(__VA_ARGS__)
|
||||
#define COMPACT_FMT_62(fmt, value, ...) fmt COMPACT_FMT_60(__VA_ARGS__)
|
||||
#define COMPACT_FMT_64(fmt, value, ...) fmt COMPACT_FMT_62(__VA_ARGS__)
|
||||
#define COMPACT_FMT_66(fmt, value, ...) fmt COMPACT_FMT_64(__VA_ARGS__)
|
||||
#define COMPACT_FMT_68(fmt, value, ...) fmt COMPACT_FMT_66(__VA_ARGS__)
|
||||
#define COMPACT_FMT_70(fmt, value, ...) fmt COMPACT_FMT_68(__VA_ARGS__)
|
||||
#define COMPACT_FMT_72(fmt, value, ...) fmt COMPACT_FMT_70(__VA_ARGS__)
|
||||
#define COMPACT_FMT_74(fmt, value, ...) fmt COMPACT_FMT_72(__VA_ARGS__)
|
||||
#define COMPACT_FMT_76(fmt, value, ...) fmt COMPACT_FMT_74(__VA_ARGS__)
|
||||
#define COMPACT_FMT_78(fmt, value, ...) fmt COMPACT_FMT_76(__VA_ARGS__)
|
||||
#define COMPACT_FMT_80(fmt, value, ...) fmt COMPACT_FMT_78(__VA_ARGS__)
|
||||
#define COMPACT_FMT_82(fmt, value, ...) fmt COMPACT_FMT_80(__VA_ARGS__)
|
||||
#define COMPACT_FMT_84(fmt, value, ...) fmt COMPACT_FMT_82(__VA_ARGS__)
|
||||
#define COMPACT_FMT_86(fmt, value, ...) fmt COMPACT_FMT_84(__VA_ARGS__)
|
||||
#define COMPACT_FMT_88(fmt, value, ...) fmt COMPACT_FMT_86(__VA_ARGS__)
|
||||
#define COMPACT_FMT_90(fmt, value, ...) fmt COMPACT_FMT_88(__VA_ARGS__)
|
||||
#define COMPACT_FMT_92(fmt, value, ...) fmt COMPACT_FMT_90(__VA_ARGS__)
|
||||
#define COMPACT_FMT_94(fmt, value, ...) fmt COMPACT_FMT_92(__VA_ARGS__)
|
||||
#define COMPACT_FMT_96(fmt, value, ...) fmt COMPACT_FMT_94(__VA_ARGS__)
|
||||
#define COMPACT_FMT_98(fmt, value, ...) fmt COMPACT_FMT_96(__VA_ARGS__)
|
||||
#define COMPACT_FMT_100(fmt, value, ...) fmt COMPACT_FMT_98(__VA_ARGS__)
|
||||
#define COMPACT_FMT_102(fmt, value, ...) fmt COMPACT_FMT_100(__VA_ARGS__)
|
||||
#define COMPACT_FMT_104(fmt, value, ...) fmt COMPACT_FMT_102(__VA_ARGS__)
|
||||
#define COMPACT_FMT_106(fmt, value, ...) fmt COMPACT_FMT_104(__VA_ARGS__)
|
||||
#define COMPACT_FMT_108(fmt, value, ...) fmt COMPACT_FMT_106(__VA_ARGS__)
|
||||
#define COMPACT_FMT_110(fmt, value, ...) fmt COMPACT_FMT_108(__VA_ARGS__)
|
||||
#define COMPACT_FMT_112(fmt, value, ...) fmt COMPACT_FMT_110(__VA_ARGS__)
|
||||
#define COMPACT_FMT_114(fmt, value, ...) fmt COMPACT_FMT_112(__VA_ARGS__)
|
||||
#define COMPACT_FMT_116(fmt, value, ...) fmt COMPACT_FMT_114(__VA_ARGS__)
|
||||
#define COMPACT_FMT_118(fmt, value, ...) fmt COMPACT_FMT_116(__VA_ARGS__)
|
||||
#define COMPACT_FMT_120(fmt, value, ...) fmt COMPACT_FMT_118(__VA_ARGS__)
|
||||
|
||||
#define COMPACT_VALUES_2(fmt, value) value
|
||||
#define COMPACT_VALUES_4(fmt, value, ...) value, COMPACT_VALUES_2(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_6(fmt, value, ...) value, COMPACT_VALUES_4(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_8(fmt, value, ...) value, COMPACT_VALUES_6(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_10(fmt, value, ...) value, COMPACT_VALUES_8(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_12(fmt, value, ...) value, COMPACT_VALUES_10(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_14(fmt, value, ...) value, COMPACT_VALUES_12(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_16(fmt, value, ...) value, COMPACT_VALUES_14(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_18(fmt, value, ...) value, COMPACT_VALUES_16(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_20(fmt, value, ...) value, COMPACT_VALUES_18(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_22(fmt, value, ...) value, COMPACT_VALUES_20(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_24(fmt, value, ...) value, COMPACT_VALUES_22(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_26(fmt, value, ...) value, COMPACT_VALUES_24(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_28(fmt, value, ...) value, COMPACT_VALUES_26(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_30(fmt, value, ...) value, COMPACT_VALUES_28(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_32(fmt, value, ...) value, COMPACT_VALUES_30(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_34(fmt, value, ...) value, COMPACT_VALUES_32(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_36(fmt, value, ...) value, COMPACT_VALUES_34(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_38(fmt, value, ...) value, COMPACT_VALUES_36(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_40(fmt, value, ...) value, COMPACT_VALUES_38(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_42(fmt, value, ...) value, COMPACT_VALUES_40(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_44(fmt, value, ...) value, COMPACT_VALUES_42(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_46(fmt, value, ...) value, COMPACT_VALUES_44(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_48(fmt, value, ...) value, COMPACT_VALUES_46(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_50(fmt, value, ...) value, COMPACT_VALUES_48(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_52(fmt, value, ...) value, COMPACT_VALUES_50(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_54(fmt, value, ...) value, COMPACT_VALUES_52(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_56(fmt, value, ...) value, COMPACT_VALUES_54(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_58(fmt, value, ...) value, COMPACT_VALUES_56(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_60(fmt, value, ...) value, COMPACT_VALUES_58(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_62(fmt, value, ...) value, COMPACT_VALUES_60(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_64(fmt, value, ...) value, COMPACT_VALUES_62(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_66(fmt, value, ...) value, COMPACT_VALUES_64(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_68(fmt, value, ...) value, COMPACT_VALUES_66(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_70(fmt, value, ...) value, COMPACT_VALUES_68(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_72(fmt, value, ...) value, COMPACT_VALUES_70(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_74(fmt, value, ...) value, COMPACT_VALUES_72(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_76(fmt, value, ...) value, COMPACT_VALUES_74(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_78(fmt, value, ...) value, COMPACT_VALUES_76(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_80(fmt, value, ...) value, COMPACT_VALUES_78(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_82(fmt, value, ...) value, COMPACT_VALUES_80(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_84(fmt, value, ...) value, COMPACT_VALUES_82(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_86(fmt, value, ...) value, COMPACT_VALUES_84(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_88(fmt, value, ...) value, COMPACT_VALUES_86(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_90(fmt, value, ...) value, COMPACT_VALUES_88(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_92(fmt, value, ...) value, COMPACT_VALUES_90(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_94(fmt, value, ...) value, COMPACT_VALUES_92(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_96(fmt, value, ...) value, COMPACT_VALUES_94(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_98(fmt, value, ...) value, COMPACT_VALUES_96(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_100(fmt, value, ...) value, COMPACT_VALUES_98(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_102(fmt, value, ...) value, COMPACT_VALUES_100(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_104(fmt, value, ...) value, COMPACT_VALUES_102(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_106(fmt, value, ...) value, COMPACT_VALUES_104(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_108(fmt, value, ...) value, COMPACT_VALUES_106(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_110(fmt, value, ...) value, COMPACT_VALUES_108(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_112(fmt, value, ...) value, COMPACT_VALUES_110(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_114(fmt, value, ...) value, COMPACT_VALUES_112(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_116(fmt, value, ...) value, COMPACT_VALUES_114(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_118(fmt, value, ...) value, COMPACT_VALUES_116(__VA_ARGS__)
|
||||
#define COMPACT_VALUES_120(fmt, value, ...) value, COMPACT_VALUES_118(__VA_ARGS__)
|
||||
|
||||
#endif
|
||||
+25
-29
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2021-Present, Redis Ltd.
|
||||
* Copyright (c) 2021, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -23,9 +44,6 @@
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#if defined(USE_JEMALLOC)
|
||||
#include <lstate.h>
|
||||
#endif
|
||||
|
||||
#define LUA_ENGINE_NAME "LUA"
|
||||
#define REGISTRY_ENGINE_CTX_NAME "__ENGINE_CTX__"
|
||||
@@ -34,8 +52,6 @@
|
||||
#define LIBRARY_API_NAME "__LIBRARY_API__"
|
||||
#define GLOBALS_API_NAME "__GLOBALS_API__"
|
||||
|
||||
static int gc_count = 0; /* Counter for the number of GC requests, reset after each GC execution */
|
||||
|
||||
/* Lua engine ctx */
|
||||
typedef struct luaEngineCtx {
|
||||
lua_State *lua;
|
||||
@@ -136,7 +152,6 @@ done:
|
||||
|
||||
lua_sethook(lua,NULL,0,0); /* Disable hook */
|
||||
luaSaveOnRegistry(lua, REGISTRY_LOAD_CTX_NAME, NULL);
|
||||
luaGC(lua, &gc_count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -165,7 +180,6 @@ static void luaEngineCall(scriptRunCtx *run_ctx,
|
||||
|
||||
luaCallFunction(run_ctx, lua, keys, nkeys, args, nargs, 0);
|
||||
lua_pop(lua, 1); /* Pop error handler */
|
||||
luaGC(lua, &gc_count);
|
||||
}
|
||||
|
||||
static size_t luaEngineGetUsedMemoy(void *engine_ctx) {
|
||||
@@ -190,23 +204,6 @@ static void luaEngineFreeFunction(void *engine_ctx, void *compiled_function) {
|
||||
zfree(f_ctx);
|
||||
}
|
||||
|
||||
static void luaEngineFreeCtx(void *engine_ctx) {
|
||||
luaEngineCtx *lua_engine_ctx = engine_ctx;
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* When lua is closed, destroy the previously used private tcache. */
|
||||
void *ud = (global_State*)G(lua_engine_ctx->lua)->ud;
|
||||
unsigned int lua_tcache = (unsigned int)(uintptr_t)ud;
|
||||
#endif
|
||||
|
||||
lua_gc(lua_engine_ctx->lua, LUA_GCCOLLECT, 0);
|
||||
lua_close(lua_engine_ctx->lua);
|
||||
zfree(lua_engine_ctx);
|
||||
|
||||
#if defined(USE_JEMALLOC)
|
||||
je_mallctl("tcache.destroy", NULL, NULL, (void *)&lua_tcache, sizeof(unsigned int));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void luaRegisterFunctionArgsInitialize(registerFunctionArgs *register_f_args,
|
||||
sds name,
|
||||
sds desc,
|
||||
@@ -426,7 +423,7 @@ static int luaRegisterFunction(lua_State *lua) {
|
||||
/* Initialize Lua engine, should be called once on start. */
|
||||
int luaEngineInitEngine(void) {
|
||||
luaEngineCtx *lua_engine_ctx = zmalloc(sizeof(*lua_engine_ctx));
|
||||
lua_engine_ctx->lua = createLuaState();
|
||||
lua_engine_ctx->lua = lua_open();
|
||||
|
||||
luaRegisterRedisAPI(lua_engine_ctx->lua);
|
||||
|
||||
@@ -506,7 +503,6 @@ int luaEngineInitEngine(void) {
|
||||
.get_function_memory_overhead = luaEngineFunctionMemoryOverhead,
|
||||
.get_engine_memory_overhead = luaEngineMemoryOverhead,
|
||||
.free_function = luaEngineFreeFunction,
|
||||
.free_ctx = luaEngineFreeCtx,
|
||||
};
|
||||
return functionsRegisterEngine(LUA_ENGINE_NAME, lua_engine);
|
||||
}
|
||||
|
||||
+44
-26
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2011-Present, Redis Ltd.
|
||||
* Copyright (c) 2021, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "functions.h"
|
||||
@@ -24,7 +45,6 @@ static size_t engine_cache_memory = 0;
|
||||
static void engineFunctionDispose(dict *d, void *obj);
|
||||
static void engineStatsDispose(dict *d, void *obj);
|
||||
static void engineLibraryDispose(dict *d, void *obj);
|
||||
static void engineDispose(dict *d, void *obj);
|
||||
static int functionsVerifyName(sds name);
|
||||
|
||||
typedef struct functionsLibEngineStats {
|
||||
@@ -51,7 +71,7 @@ dictType engineDictType = {
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCaseCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
engineDispose, /* val destructor */
|
||||
NULL, /* val destructor */
|
||||
NULL /* allow to expand */
|
||||
};
|
||||
|
||||
@@ -98,7 +118,10 @@ dictType librariesDictType = {
|
||||
/* Dictionary of engines */
|
||||
static dict *engines = NULL;
|
||||
|
||||
/* Libraries Ctx. */
|
||||
/* Libraries Ctx.
|
||||
* Contains the dictionary that map a library name to library object,
|
||||
* Contains the dictionary that map a function name to function object,
|
||||
* and the cache memory used by all the functions */
|
||||
static functionsLibCtx *curr_functions_lib_ctx = NULL;
|
||||
|
||||
static size_t functionMallocSize(functionInfo *fi) {
|
||||
@@ -149,16 +172,6 @@ static void engineLibraryDispose(dict *d, void *obj) {
|
||||
engineLibraryFree(obj);
|
||||
}
|
||||
|
||||
static void engineDispose(dict *d, void *obj) {
|
||||
UNUSED(d);
|
||||
engineInfo *ei = obj;
|
||||
freeClient(ei->c);
|
||||
sdsfree(ei->name);
|
||||
ei->engine->free_ctx(ei->engine->engine_ctx);
|
||||
zfree(ei->engine);
|
||||
zfree(ei);
|
||||
}
|
||||
|
||||
/* Clear all the functions from the given library ctx */
|
||||
void functionsLibCtxClear(functionsLibCtx *lib_ctx) {
|
||||
dictEmpty(lib_ctx->functions, NULL);
|
||||
@@ -171,19 +184,17 @@ void functionsLibCtxClear(functionsLibCtx *lib_ctx) {
|
||||
stats->n_lib = 0;
|
||||
}
|
||||
dictReleaseIterator(iter);
|
||||
lib_ctx->cache_memory = 0;
|
||||
curr_functions_lib_ctx->cache_memory = 0;
|
||||
}
|
||||
|
||||
void functionsLibCtxClearCurrent(int async) {
|
||||
if (async) {
|
||||
functionsLibCtx *old_l_ctx = curr_functions_lib_ctx;
|
||||
dict *old_engines = engines;
|
||||
freeFunctionsAsync(old_l_ctx, old_engines);
|
||||
curr_functions_lib_ctx = functionsLibCtxCreate();
|
||||
freeFunctionsAsync(old_l_ctx);
|
||||
} else {
|
||||
functionsLibCtxFree(curr_functions_lib_ctx);
|
||||
dictRelease(engines);
|
||||
functionsLibCtxClear(curr_functions_lib_ctx);
|
||||
}
|
||||
functionsInit();
|
||||
}
|
||||
|
||||
/* Free the given functions ctx */
|
||||
@@ -488,6 +499,7 @@ static void functionListReplyFlags(client *c, functionInfo *fi) {
|
||||
* Return general information about all the libraries:
|
||||
* * Library name
|
||||
* * The engine used to run the Library
|
||||
* * Library description
|
||||
* * Functions list
|
||||
* * Library code (if WITHCODE is given)
|
||||
*
|
||||
@@ -669,6 +681,7 @@ void fcallroCommand(client *c) {
|
||||
* is saved separately with the following information:
|
||||
* * Library name
|
||||
* * Engine name
|
||||
* * Library description
|
||||
* * Library code
|
||||
* RDB_OPCODE_FUNCTION2 is saved before each library to present
|
||||
* that the payload is a library.
|
||||
@@ -827,6 +840,7 @@ void functionHelpCommand(client *c) {
|
||||
" Return general information on all the libraries:",
|
||||
" * Library name",
|
||||
" * The engine used to run the Library",
|
||||
" * Library description",
|
||||
" * Functions list",
|
||||
" * Library code (if WITHCODE is given)",
|
||||
" It also possible to get only function that matches a pattern using LIBRARYNAME argument.",
|
||||
@@ -880,7 +894,9 @@ static int functionsVerifyName(sds name) {
|
||||
|
||||
int functionExtractLibMetaData(sds payload, functionsLibMataData *md, sds *err) {
|
||||
sds name = NULL;
|
||||
sds desc = NULL;
|
||||
sds engine = NULL;
|
||||
sds code = NULL;
|
||||
if (strncmp(payload, "#!", 2) != 0) {
|
||||
*err = sdsnew("Missing library metadata");
|
||||
return C_ERR;
|
||||
@@ -932,7 +948,9 @@ int functionExtractLibMetaData(sds payload, functionsLibMataData *md, sds *err)
|
||||
|
||||
error:
|
||||
if (name) sdsfree(name);
|
||||
if (desc) sdsfree(desc);
|
||||
if (engine) sdsfree(engine);
|
||||
if (code) sdsfree(code);
|
||||
sdsfreesplitres(parts, numparts);
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -1066,15 +1084,15 @@ void functionLoadCommand(client *c) {
|
||||
unsigned long functionsMemory(void) {
|
||||
dictIterator *iter = dictGetIterator(engines);
|
||||
dictEntry *entry = NULL;
|
||||
size_t engines_memory = 0;
|
||||
size_t engines_nemory = 0;
|
||||
while ((entry = dictNext(iter))) {
|
||||
engineInfo *ei = dictGetVal(entry);
|
||||
engine *engine = ei->engine;
|
||||
engines_memory += engine->get_used_memory(engine->engine_ctx);
|
||||
engines_nemory += engine->get_used_memory(engine->engine_ctx);
|
||||
}
|
||||
dictReleaseIterator(iter);
|
||||
|
||||
return engines_memory;
|
||||
return engines_nemory;
|
||||
}
|
||||
|
||||
/* Return memory overhead of all the engines combine */
|
||||
@@ -1101,7 +1119,7 @@ dict* functionsLibGet(void) {
|
||||
return curr_functions_lib_ctx->libraries;
|
||||
}
|
||||
|
||||
size_t functionsLibCtxFunctionsLen(functionsLibCtx *functions_ctx) {
|
||||
size_t functionsLibCtxfunctionsLen(functionsLibCtx *functions_ctx) {
|
||||
return dictSize(functions_ctx->functions);
|
||||
}
|
||||
|
||||
|
||||
+29
-17
@@ -1,9 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2021-Present, Redis Ltd.
|
||||
* Copyright (c) 2021, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of the Redis Source Available License 2.0
|
||||
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __FUNCTIONS_H_
|
||||
@@ -11,16 +32,11 @@
|
||||
|
||||
/*
|
||||
* functions.c unit provides the Redis Functions API:
|
||||
* * FUNCTION LOAD
|
||||
* * FUNCTION LIST
|
||||
* * FUNCTION CALL (FCALL and FCALL_RO)
|
||||
* * FUNCTION CREATE
|
||||
* * FUNCTION CALL
|
||||
* * FUNCTION DELETE
|
||||
* * FUNCTION STATS
|
||||
* * FUNCTION KILL
|
||||
* * FUNCTION FLUSH
|
||||
* * FUNCTION DUMP
|
||||
* * FUNCTION RESTORE
|
||||
* * FUNCTION HELP
|
||||
* * FUNCTION INFO
|
||||
*
|
||||
* Also contains implementation for:
|
||||
* * Save/Load function from rdb
|
||||
@@ -43,7 +59,7 @@ typedef struct engine {
|
||||
* code - the library code
|
||||
* timeout - timeout for the library creation (0 for no timeout)
|
||||
* err - description of error (if occurred)
|
||||
* returns C_ERR on error and set err to be the error message */
|
||||
* returns NULL on error and set sds to be the error message */
|
||||
int (*create)(void *engine_ctx, functionLibInfo *li, sds code, size_t timeout, sds *err);
|
||||
|
||||
/* Invoking a function, r_ctx is an opaque object (from engine POV).
|
||||
@@ -67,9 +83,6 @@ typedef struct engine {
|
||||
|
||||
/* free the given function */
|
||||
void (*free_function)(void *engine_ctx, void *compiled_function);
|
||||
|
||||
/* Free the engine context. */
|
||||
void (*free_ctx)(void *engine_ctx);
|
||||
} engine;
|
||||
|
||||
/* Hold information about an engine.
|
||||
@@ -107,7 +120,7 @@ unsigned long functionsMemoryOverhead(void);
|
||||
unsigned long functionsNum(void);
|
||||
unsigned long functionsLibNum(void);
|
||||
dict* functionsLibGet(void);
|
||||
size_t functionsLibCtxFunctionsLen(functionsLibCtx *functions_ctx);
|
||||
size_t functionsLibCtxfunctionsLen(functionsLibCtx *functions_ctx);
|
||||
functionsLibCtx* functionsLibCtxGetCurrent(void);
|
||||
functionsLibCtx* functionsLibCtxCreate(void);
|
||||
void functionsLibCtxClearCurrent(int async);
|
||||
@@ -119,6 +132,5 @@ int functionLibCreateFunction(sds name, void *function, functionLibInfo *li, sds
|
||||
|
||||
int luaEngineInitEngine(void);
|
||||
int functionsInit(void);
|
||||
void functionsFree(functionsLibCtx *lib_ctx, dict *engs);
|
||||
|
||||
#endif /* __FUNCTIONS_H_ */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015-current, Redis Ltd.
|
||||
* Copyright (c) 2015-2016, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -300,7 +300,7 @@ int geoGetPointsInRange(robj *zobj, double min, double max, GeoShape *shape, geo
|
||||
zskiplist *zsl = zs->zsl;
|
||||
zskiplistNode *ln;
|
||||
|
||||
if ((ln = zslNthInRange(zsl, &range, 0)) == NULL) {
|
||||
if ((ln = zslFirstInRange(zsl, &range)) == NULL) {
|
||||
/* Nothing exists starting at our min. No results. */
|
||||
return 0;
|
||||
}
|
||||
@@ -690,7 +690,7 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
}
|
||||
|
||||
if (any && !count) {
|
||||
addReplyError(c, "the ANY argument requires COUNT argument");
|
||||
addReplyErrorFormat(c, "the ANY argument requires COUNT argument");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015-current, Redis Ltd.
|
||||
* Copyright (c) 2015-2016, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015-current, Redis Ltd.
|
||||
* Copyright (c) 2015, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015-current, Redis Ltd.
|
||||
* Copyright (c) 2015-2016, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user