Merge tag '6.0.8' into unstable

Former-commit-id: 4c7e4b91a6bb2034636856b608b8c386d07f5541
This commit is contained in:
John Sully 2020-09-30 19:47:55 +00:00
commit 14daf6f909
96 changed files with 4102 additions and 1040 deletions

View File

@ -7,7 +7,7 @@ jobs:
test-ubuntu-latest: test-ubuntu-latest:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: make - name: make
run: | run: |
sudo apt-get update sudo apt-get update
@ -42,14 +42,14 @@ jobs:
build-macos-latest: build-macos-latest:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: make - name: make
run: make -j2 run: make -j2
build-libc-malloc: build-libc-malloc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: make - name: make
run: | run: |
sudo apt-get update sudo apt-get update

View File

@ -1,16 +1,21 @@
name: Daily name: Daily
on: on:
pull_request:
branches:
# any PR to a release branch.
- '[0-9].[0-9]'
schedule: schedule:
- cron: '0 7 * * *' - cron: '0 0 * * *'
jobs: jobs:
test-jemalloc: test-ubuntu-jemalloc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 1200 if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: make - name: make
run: | run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev sudo apt-get -y install uuid-dev libcurl4-openssl-dev
@ -21,12 +26,17 @@ jobs:
./runtest --accurate --verbose ./runtest --accurate --verbose
- name: module api test - name: module api test
run: ./runtest-moduleapi --verbose run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-libc-malloc: test-ubuntu-libc-malloc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 1200 if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: make - name: make
run: | run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev sudo apt-get -y install uuid-dev libcurl4-openssl-dev
@ -37,11 +47,17 @@ jobs:
./runtest --accurate --verbose ./runtest --accurate --verbose
- name: module api test - name: module api test
run: ./runtest-moduleapi --verbose run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: make - name: make
run: | run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev sudo apt-get -y install uuid-dev libcurl4-openssl-dev
@ -59,7 +75,7 @@ jobs:
test-ubuntu-arm: test-ubuntu-arm:
runs-on: [self-hosted, linux, arm] runs-on: [self-hosted, linux, arm]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: make - name: make
run: | run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev sudo apt-get -y install uuid-dev libcurl4-openssl-dev
@ -74,9 +90,10 @@ jobs:
test-valgrind: test-valgrind:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400 timeout-minutes: 14400
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: make - name: make
run: | run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev sudo apt-get -y install uuid-dev libcurl4-openssl-dev
@ -87,3 +104,76 @@ jobs:
./runtest --valgrind --verbose --clients 1 ./runtest --valgrind --verbose --clients 1
- name: module api test - name: module api test
run: ./runtest-moduleapi --valgrind --verbose --clients 1 run: ./runtest-moduleapi --valgrind --verbose --clients 1
test-centos7-jemalloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
container: centos:7
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
yum -y install centos-release-scl
yum -y install devtoolset-7
scl enable devtoolset-7 "make"
- name: test
run: |
yum -y install tcl
./runtest --accurate --verbose
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-centos7-tls:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
container: centos:7
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
yum -y install centos-release-scl epel-release
yum -y install devtoolset-7 openssl-devel openssl
scl enable devtoolset-7 "make BUILD_TLS=yes"
- name: test
run: |
yum -y install tcl tcltls
./utils/gen-test-certs.sh
./runtest --accurate --verbose --tls
./runtest --accurate --verbose
- name: module api test
run: |
./runtest-moduleapi --verbose --tls
./runtest-moduleapi --verbose
- name: sentinel tests
run: |
./runtest-sentinel --tls
./runtest-sentinel
- name: cluster tests
run: |
./runtest-cluster --tls
./runtest-cluster
test-macos-latest:
runs-on: macos-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: make
- name: test
run: |
./runtest --accurate --verbose
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster

4
.gitignore vendored
View File

@ -48,9 +48,13 @@ src/nodes.conf
deps/lua/src/lua deps/lua/src/lua
deps/lua/src/luac deps/lua/src/luac
deps/lua/src/liblua.a deps/lua/src/liblua.a
tests/tls/*
.make-* .make-*
.prerequisites .prerequisites
*.dSYM *.dSYM
Makefile.dep Makefile.dep
.vscode/* .vscode/*
.idea/* .idea/*
.ccls
.ccls-cache/*
compile_commands.json

View File

@ -11,6 +11,723 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
SECURITY: There are security fixes in the release. SECURITY: There are security fixes in the release.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
================================================================================
Redis 6.0.8 Released Wed Sep 09 23:34:17 IDT 2020
================================================================================
Upgrade urgency HIGH: Anyone who's using Redis 6.0.7 with Sentinel or
CONFIG REWRITE command is affected and should upgrade ASAP, see #7760.
Bug fixes:
* CONFIG REWRITE after setting oom-score-adj-values either via CONFIG SET or
loading it from a config file, will generate a corrupt config file that will
cause Redis to fail to start
* Fix issue with redis-cli --pipe on MacOS
* Fix RESP3 response for HKEYS/HVALS on non-existing key
* Various small bug fixes
New features / Changes:
* Remove THP warning when set to madvise
* Allow EXEC with read commands on readonly replica in cluster
* Add masters/replicas options to redis-cli --cluster call command
Module API:
* Add RedisModule_ThreadSafeContextTryLock
Full list of commits:
Oran Agra in commit cdabf696a:
Fix RESP3 response for HKEYS/HVALS on non-existing key
1 file changed, 3 insertions(+), 1 deletion(-)
Oran Agra in commit ec633c716:
Fix leak in new blockedclient module API test
1 file changed, 3 insertions(+)
Yossi Gottlieb in commit 6bac07c5c:
Tests: fix oom-score-adj false positives. (#7772)
1 file changed, 1 insertion(+), 1 deletion(-)
杨博东 in commit 6043dc614:
Tests: Add aclfile load and save tests (#7765)
2 files changed, 41 insertions(+)
Roi Lipman in commit c0b5f9bf0:
RM_ThreadSafeContextTryLock a non-blocking method for acquiring GIL (#7738)
7 files changed, 122 insertions(+), 1 deletion(-)
Yossi Gottlieb in commit 5780a1599:
Tests: validate CONFIG REWRITE for all params. (#7764)
6 files changed, 43 insertions(+), 6 deletions(-)
Oran Agra in commit e3c14b25d:
Change THP warning to use madvise rather than never (#7771)
1 file changed, 1 insertion(+), 1 deletion(-)
Itamar Haber in commit 28929917b:
Documents RM_Call's fmt (#5448)
1 file changed, 25 insertions(+)
Jan-Erik Rediger in commit 9146402c2:
Check that THP is not set to always (madvise is ok) (#4001)
1 file changed, 1 insertion(+), 1 deletion(-)
Yossi Gottlieb in commit d05089429:
Tests: clean up stale .cli files. (#7768)
1 file changed, 2 insertions(+)
Eran Liberty in commit 8861c1bae:
Allow exec with read commands on readonly replica in cluster (#7766)
3 files changed, 59 insertions(+), 3 deletions(-)
Yossi Gottlieb in commit 2cf2ff2f6:
Fix CONFIG REWRITE of oom-score-adj-values. (#7761)
1 file changed, 2 insertions(+), 1 deletion(-)
Oran Agra in commit 1386c80f7:
handle cur_test for nested tests
1 file changed, 3 insertions(+)
Oran Agra in commit c7d4945f0:
Add daily CI for MacOS (#7759)
1 file changed, 18 insertions(+)
bodong.ybd in commit 32548264c:
Tests: Some fixes for macOS
3 files changed, 26 insertions(+), 11 deletions(-)
Oran Agra in commit 1e17f9812:
Fix cluster consistency-check test (#7754)
1 file changed, 55 insertions(+), 29 deletions(-)
Yossi Gottlieb in commit f4ecdf86a:
Tests: fix unmonitored servers. (#7756)
1 file changed, 5 insertions(+)
Oran Agra in commit 9f020050d:
fix broken cluster/sentinel tests by recent commit (#7752)
1 file changed, 1 insertion(+), 1 deletion(-)
Oran Agra in commit fdbabb496:
Improve valgrind support for cluster tests (#7725)
3 files changed, 83 insertions(+), 23 deletions(-)
Oran Agra in commit 35a6a0bbc:
test infra - add durable mode to work around test suite crashing
3 files changed, 35 insertions(+), 3 deletions(-)
Oran Agra in commit e3136b13f:
test infra - wait_done_loading
2 files changed, 16 insertions(+), 36 deletions(-)
Oran Agra in commit 83c75dbd9:
test infra - flushall between tests in external mode
1 file changed, 1 insertion(+)
Oran Agra in commit 265f5d3cf:
test infra - improve test skipping ability
3 files changed, 91 insertions(+), 36 deletions(-)
Oran Agra in commit fcd3a9908:
test infra - reduce disk space usage
3 files changed, 33 insertions(+), 11 deletions(-)
Oran Agra in commit b6ea4699f:
test infra - write test name to logfile
3 files changed, 35 insertions(+)
Yossi Gottlieb in commit 4a4b07fc6:
redis-cli: fix writeConn() buffer handling. (#7749)
1 file changed, 37 insertions(+), 6 deletions(-)
Oran Agra in commit f2d08de2e:
Print server startup messages after daemonization (#7743)
1 file changed, 4 insertions(+), 4 deletions(-)
Thandayuthapani in commit 77541d555:
Add masters/replicas options to redis-cli --cluster call command (#6491)
1 file changed, 13 insertions(+), 2 deletions(-)
Oran Agra in commit 91d13a854:
fix README about BUILD_WITH_SYSTEMD usage (#7739)
1 file changed, 1 insertion(+), 1 deletion(-)
Yossi Gottlieb in commit 88d03d965:
Fix double-make issue with make && make install. (#7734)
1 file changed, 2 insertions(+)
================================================================================
Redis 6.0.7 Released Fri Aug 28 11:05:09 IDT 2020
================================================================================
Upgrade urgency MODERATE: several bugs with moderate impact are fixed,
Specifically the first two listed below which cause protocol errors for clients.
Bug fixes:
* CONFIG SET could hung the client when arrives during RDB/ROF loading (When
processed after another command that was also rejected with -LOADING error)
* LPOS command when RANK is greater than matches responded wiht broken protocol
(negative multi-bulk count)
* UNLINK / Lazyfree for stream type key would have never do async freeing
* PERSIST should invalidate WATCH (Like EXPIRE does)
* EXEC with only read commands could have be rejected when OOM
* TLS: relax verification on CONFIG SET (Don't error if some configs are set
and tls isn't enabled)
* TLS: support cluster/replication without tls-port
* Systemd startup after network is online
* Redis-benchmark improvements
* Various small bug fixes
New features:
* Add oom-score-adj configuration option to control Linux OOM killer
* Show IO threads statistics and status in INFO output
* Add optional tls verification mode (see tls-auth-clients)
Module API:
* Add RedisModule_HoldString
* Add loaded keyspace event
* Fix RedisModuleEvent_LoadingProgress
* Fix RedisModuleEvent_MasterLinkChange hook missing on successful psync
* Fix missing RM_CLIENTINFO_FLAG_SSL
* Refactor redismodule.h for use with -fno-common / extern
Full list of commits:
Oran Agra in commit c26394e4f:
Reduce the probability of failure when start redis in runtest-cluster #7554 (#7635)
1 file changed, 23 insertions(+), 5 deletions(-)
Leoš Literák in commit 745d5e802:
Update README.md with instructions how to build with systemd support (#7730)
1 file changed, 5 insertions(+)
Yossi Gottlieb in commit 03f1d208a:
Fix oom-score-adj on older distros. (#7724)
1 file changed, 2 insertions(+), 2 deletions(-)
Yossi Gottlieb in commit 941174d9c:
Backport Lua 5.2.2 stack overflow fix. (#7733)
1 file changed, 1 insertion(+), 1 deletion(-)
Wang Yuan in commit c897dba14:
Fix wrong format specifiers of 'sdscatfmt' for the INFO command (#7706)
1 file changed, 1 insertion(+), 1 deletion(-)
Wen Hui in commit 5e3fab5e7:
fix make warnings (#7692)
1 file changed, 4 insertions(+), 3 deletions(-)
Nathan Scott in commit a2b09c13f:
Annotate module API functions in redismodule.h for use with -fno-common (#6900)
1 file changed, 265 insertions(+), 241 deletions(-)
Yossi Gottlieb in commit bf244273f:
Add oom-score-adj configuration option to control Linux OOM killer. (#1690)
8 files changed, 306 insertions(+), 1 deletion(-)
Meir Shpilraien (Spielrein) in commit b5a6ab98f:
see #7544, added RedisModule_HoldString api. (#7577)
4 files changed, 83 insertions(+), 8 deletions(-)
ShooterIT in commit ff04cf62b:
[Redis-benchmark] Remove zrem test, add zpopmin test
1 file changed, 5 insertions(+), 5 deletions(-)
ShooterIT in commit 0f3260f31:
[Redis-benchmark] Support zset type
1 file changed, 16 insertions(+)
Arun Ranganathan in commit 45d0b94fc:
Show threading configuration in INFO output (#7446)
3 files changed, 46 insertions(+), 14 deletions(-)
Meir Shpilraien (Spielrein) in commit a22f61e12:
This PR introduces a new loaded keyspace event (#7536)
8 files changed, 135 insertions(+), 4 deletions(-)
Oran Agra in commit 1c9ca1030:
Fix rejectCommand trims newline in shared error objects, hung clients (#7714)
4 files changed, 42 insertions(+), 23 deletions(-)
valentinogeron in commit 217471795:
EXEC with only read commands should not be rejected when OOM (#7696)
2 files changed, 51 insertions(+), 8 deletions(-)
Itamar Haber in commit 6e6c47d16:
Expands lazyfree's effort estimate to include Streams (#5794)
1 file changed, 24 insertions(+)
Yossi Gottlieb in commit da6813623:
Add language servers stuff, test/tls to gitignore. (#7698)
1 file changed, 4 insertions(+)
Valentino Geron in commit de7fb126e:
Assert that setDeferredAggregateLen isn't called with negative value
1 file changed, 1 insertion(+)
Valentino Geron in commit 6cf27f25f:
Fix LPOS command when RANK is greater than matches
2 files changed, 9 insertions(+), 2 deletions(-)
Yossi Gottlieb in commit 9bba54ace:
Tests: fix redis-cli with remote hosts. (#7693)
3 files changed, 5 insertions(+), 5 deletions(-)
huangzhw in commit 0fec2cb81:
RedisModuleEvent_LoadingProgress always at 100% progress (#7685)
1 file changed, 2 insertions(+), 2 deletions(-)
guybe7 in commit 931e19aa6:
Modules: Invalidate saved_oparray after use (#7688)
1 file changed, 2 insertions(+)
杨博东 in commit 6f2065570:
Fix flock cluster config may cause failure to restart after kill -9 (#7674)
4 files changed, 31 insertions(+), 7 deletions(-)
Raghav Muddur in commit 200149a2a:
Update clusterMsgDataPublish to clusterMsgModule (#7682)
1 file changed, 1 insertion(+), 1 deletion(-)
Madelyn Olson in commit 72daa1b4e:
Fixed hset error since it's shared with hmset (#7678)
1 file changed, 1 insertion(+), 1 deletion(-)
guybe7 in commit 3bf9ac994:
PERSIST should signalModifiedKey (Like EXPIRE does) (#7671)
1 file changed, 1 insertion(+)
Oran Agra in commit b37501684:
OOM Crash log include size of allocation attempt. (#7670)
1 file changed, 2 insertions(+), 1 deletion(-)
Wen Hui in commit 2136cb68f:
[module] using predefined REDISMODULE_NO_EXPIRE in RM_GetExpire (#7669)
1 file changed, 2 insertions(+), 1 deletion(-)
Oran Agra in commit f56aee4bc:
Trim trailing spaces in error replies coming from rejectCommand (#7668)
1 file changed, 5 insertions(+), 1 deletion(-)
Yossi Gottlieb in commit 012d7506a:
Module API: fix missing RM_CLIENTINFO_FLAG_SSL. (#7666)
6 files changed, 82 insertions(+), 1 deletion(-)
Yossi Gottlieb in commit a0adbc857:
TLS: relax verification on CONFIG SET. (#7665)
2 files changed, 24 insertions(+), 7 deletions(-)
Madelyn Olson in commit 2ef29715b:
Fixed timer warning (#5953)
1 file changed, 1 insertion(+), 1 deletion(-)
Wagner Francisco Mezaroba in commit b76f171f5:
allow --pattern to be used along with --bigkeys (#3586)
1 file changed, 9 insertions(+), 2 deletions(-)
zhaozhao.zz in commit cc7b57765:
redis-benchmark: fix wrong random key for hset (#4895)
1 file changed, 1 insertion(+), 1 deletion(-)
zhaozhao.zz in commit 479c1ba77:
CLIENT_MASTER should ignore server.proto_max_bulk_len
1 file changed, 2 insertions(+), 1 deletion(-)
zhaozhao.zz in commit f61ce8a52:
config: proto-max-bulk-len must be 1mb or greater
2 files changed, 2 insertions(+), 2 deletions(-)
zhaozhao.zz in commit 0350f597a:
using proto-max-bulk-len in checkStringLength for SETRANGE and APPEND
1 file changed, 2 insertions(+), 2 deletions(-)
YoongHM in commit eea63548d:
Start redis after network is online (#7639)
1 file changed, 2 insertions(+)
Yossi Gottlieb in commit aef6d74fb:
Run daily workflow on main repo only (no forks). (#7646)
1 file changed, 7 insertions(+)
WuYunlong in commit 917b4d241:
see #7250, fix signature of RedisModule_DeauthenticateAndCloseClient (#7645)
1 file changed, 1 insertion(+), 1 deletion(-)
Wang Yuan in commit efab7fd54:
Print error info if failed opening config file (#6943)
1 file changed, 2 insertions(+), 1 deletion(-)
Wen Hui in commit 8c4468bcf:
fix memory leak in ACLLoadFromFile error handling (#7623)
1 file changed, 1 insertion(+)
Oran Agra in commit 89724e1d2:
redis-cli --cluster-yes - negate force flag for clarity
1 file changed, 9 insertions(+), 9 deletions(-)
Frank Meier in commit c813739af:
reintroduce REDISCLI_CLUSTER_YES env variable in redis-cli
1 file changed, 6 insertions(+)
Frank Meier in commit 7e3b86c18:
add force option to 'create-cluster create' script call (#7612)
1 file changed, 6 insertions(+), 2 deletions(-)
Oran Agra in commit 3f7fa4312:
fix new rdb test failing on timing issues (#7604)
1 file changed, 2 insertions(+), 2 deletions(-)
Yossi Gottlieb in commit 417976d7a:
Fix test-centos7-tls daily job. (#7598)
1 file changed, 2 insertions(+), 2 deletions(-)
Oran Agra in commit c41818c51:
module hook for master link up missing on successful psync (#7584)
2 files changed, 22 insertions(+), 2 deletions(-)
Yossi Gottlieb in commit 6ef3fc185:
CI: Add daily CentOS 7.x jobs. (#7582)
1 file changed, 50 insertions(+), 4 deletions(-)
WuYunlong in commit 002c37482:
Fix running single test 14-consistency-check.tcl (#7587)
1 file changed, 1 insertion(+)
Yossi Gottlieb in commit 66cbbb6ad:
Clarify RM_BlockClient() error condition. (#6093)
1 file changed, 9 insertions(+)
namtsui in commit 22aba2207:
Avoid an out-of-bounds read in the redis-sentinel (#7443)
1 file changed, 2 insertions(+), 2 deletions(-)
Wen Hui in commit af08887dc:
Add SignalModifiedKey hook in XGROUP CREATE with MKSTREAM option (#7562)
1 file changed, 1 insertion(+)
Wen Hui in commit a5e0a64b0:
fix leak in error handling of debug populate command (#7062)
1 file changed, 3 insertions(+), 4 deletions(-)
Yossi Gottlieb in commit cbfdfa231:
Fix TLS cluster tests. (#7578)
1 file changed, 4 insertions(+), 1 deletion(-)
Yossi Gottlieb in commit 6d5376d30:
TLS: Propagate and handle SSL_new() failures. (#7576)
4 files changed, 48 insertions(+), 6 deletions(-)
Oran Agra in commit a662cd577:
Fix failing tests due to issues with wait_for_log_message (#7572)
3 files changed, 38 insertions(+), 34 deletions(-)
Jiayuan Chen in commit 2786a4b5e:
Add optional tls verification (#7502)
6 files changed, 40 insertions(+), 5 deletions(-)
Oran Agra in commit 3ef3d3612:
Daily github action: run cluster and sentinel tests with tls (#7575)
1 file changed, 2 insertions(+), 2 deletions(-)
Yossi Gottlieb in commit f20f63322:
TLS: support cluster/replication without tls-port.
2 files changed, 5 insertions(+), 4 deletions(-)
grishaf in commit 3c9ae059d:
Fix prepareForShutdown function declaration (#7566)
1 file changed, 1 insertion(+), 1 deletion(-)
Oran Agra in commit 3f4803af9:
Stabilize bgsave test that sometimes fails with valgrind (#7559)
1 file changed, 20 insertions(+), 2 deletions(-)
Madelyn Olson in commit 1a3c51a1f:
Properly reset errno for rdbLoad (#7542)
1 file changed, 1 insertion(+)
Oran Agra in commit 92d80b13a:
testsuite may leave servers alive on error (#7549)
1 file changed, 3 insertions(+)
Yossi Gottlieb in commit 245582ba7:
Tests: drop TCL 8.6 dependency. (#7548)
1 file changed, 27 insertions(+), 22 deletions(-)
Oran Agra in commit f20e1ba2d:
Fixes to release scripts (#7547)
2 files changed, 2 insertions(+), 2 deletions(-)
Remi Collet in commit 60ff56993:
Fix deprecated tail syntax in tests (#7543)
1 file changed, 1 insertion(+), 1 deletion(-)
Wen Hui in commit 34e8541b9:
Add missing calls to raxStop (#7532)
4 files changed, 63 insertions(+), 19 deletions(-)
Wen Hui in commit 2f7bc5435:
add missing caching command in client help (#7399)
1 file changed, 1 insertion(+)
zhaozhao.zz in commit c15be9ffe:
replication: need handle -NOPERM error after send ping (#7538)
1 file changed, 1 insertion(+)
Scott Brenner in commit 1b29152c3:
GitHub Actions workflows - use latest version of actions/checkout (#7534)
2 files changed, 10 insertions(+), 10 deletions(-)
================================================================================
Redis 6.0.6 Released Mon Jul 20 09:31:30 IDT 2020
================================================================================
Upgrade urgency MODERATE: several bugs with moderate impact are fixed here.
The most important issues are listed here:
* Fix crash when enabling CLIENT TRACKING with prefix
* EXEC always fails with EXECABORT and multi-state is cleared
* RESTORE ABSTTL won't store expired keys into the db
* redis-cli better handling of non-pritable key names
* TLS: Ignore client cert when tls-auth-clients off
* Tracking: fix invalidation message on flush
* Notify systemd on Sentinel startup
* Fix crash on a misuse of STRALGO
* Few fixes in module API
* Fix a few rare leaks (STRALGO error misuse, Sentinel)
* Fix a possible invalid access in defrag of scripts (unlikely to cause real harm)
New features:
* LPOS command to search in a list
* Use user+pass for MIGRATE in redis-cli and redis-benchmark in cluster mode
* redis-cli support TLS for --pipe, --rdb and --replica options
* TLS: Session caching configuration support
And this is the full list of commits:
Itamar Haber in commit 50548cafc:
Adds SHA256SUM to redis-stable tarball upload
1 file changed, 1 insertion(+)
yoav-steinberg in commit 3a4c6684f:
Support passing stack allocated module strings to moduleCreateArgvFromUserFormat (#7528)
1 file changed, 4 insertions(+), 1 deletion(-)
Luke Palmer in commit 2fd0b2bd6:
Send null for invalidate on flush (#7469)
1 file changed, 14 insertions(+), 10 deletions(-)
dmurnane in commit c3c81e1a8:
Notify systemd on sentinel startup (#7168)
1 file changed, 4 insertions(+)
Developer-Ecosystem-Engineering in commit e2770f29b:
Add registers dump support for Apple silicon (#7453)
1 file changed, 54 insertions(+), 2 deletions(-)
Wen Hui in commit b068eae97:
correct error msg for num connections reaching maxclients in cluster mode (#7444)
1 file changed, 2 insertions(+), 2 deletions(-)
WuYunlong in commit e6169ae5c:
Fix command help for unexpected options (#7476)
6 files changed, 20 insertions(+), 3 deletions(-)
WuYunlong in commit abf08fc02:
Refactor RM_KeyType() by using macro. (#7486)
1 file changed, 1 insertion(+), 1 deletion(-)
Oran Agra in commit 11b83076a:
diskless master disconnect replicas when rdb child failed (#7518)
1 file changed, 6 insertions(+), 5 deletions(-)
Oran Agra in commit 8f27f2f7d:
redis-cli tests, fix valgrind timing issue (#7519)
1 file changed, 1 insertion(+), 1 deletion(-)
WuYunlong in commit 180b588e8:
Fix out of update help info in tcl tests. (#7516)
1 file changed, 2 deletions(-)
Qu Chen in commit 417c60bdc:
Replica always reports master's config epoch in CLUSTER NODES output. (#7235)
1 file changed, 5 insertions(+), 1 deletion(-)
Oran Agra in commit 72a242419:
RESTORE ABSTTL skip expired keys - leak (#7511)
1 file changed, 1 insertion(+)
Oran Agra in commit 2ca45239f:
fix recently added time sensitive tests failing with valgrind (#7512)
2 files changed, 12 insertions(+), 6 deletions(-)
Oran Agra in commit 123dc8b21:
runtest --stop pause stops before terminating the redis server (#7513)
2 files changed, 8 insertions(+), 2 deletions(-)
Oran Agra in commit a6added45:
update release scripts for new hosts, and CI to run more tests (#7480)
5 files changed, 68 insertions(+), 26 deletions(-)
jimgreen2013 in commit cf4869f9e:
fix description about ziplist, the code is ok (#6318)
1 file changed, 2 insertions(+), 2 deletions(-)
马永泽 in commit d548f219b:
fix benchmark in cluster mode fails to authenticate (#7488)
1 file changed, 56 insertions(+), 40 deletions(-)
Abhishek Soni in commit e58eb7b89:
fix: typo in CI job name (#7466)
1 file changed, 1 insertion(+), 1 deletion(-)
Jiayuan Chen in commit 6def10a2b:
Fix typo in deps README (#7500)
1 file changed, 1 insertion(+), 1 deletion(-)
WuYunlong in commit 8af61afef:
Add missing latency-monitor tcl test to test_helper.tcl. (#6782)
1 file changed, 1 insertion(+)
Yossi Gottlieb in commit a419f400e:
TLS: Session caching configuration support. (#7420)
6 files changed, 56 insertions(+), 16 deletions(-)
Yossi Gottlieb in commit 2e4bb2667:
TLS: Ignore client cert when tls-auth-clients off. (#7457)
1 file changed, 1 insertion(+), 3 deletions(-)
James Hilliard in commit f0b1aee9e:
Use pkg-config to properly detect libssl and libcrypto libraries (#7452)
1 file changed, 15 insertions(+), 3 deletions(-)
Yossi Gottlieb in commit e92b99564:
TLS: Add missing redis-cli options. (#7456)
3 files changed, 166 insertions(+), 52 deletions(-)
Oran Agra in commit 1f3db5bf5:
redis-cli --hotkeys fixed to handle non-printable key names
1 file changed, 11 insertions(+), 5 deletions(-)
Oran Agra in commit c3044f369:
redis-cli --bigkeys fixed to handle non-printable key names
1 file changed, 24 insertions(+), 16 deletions(-)
Oran Agra in commit b3f75527b:
RESTORE ABSTTL won't store expired keys into the db (#7472)
4 files changed, 46 insertions(+), 16 deletions(-)
huangzhw in commit 6f87fc92f:
defrag.c activeDefragSdsListAndDict when defrag sdsele, We can't use (#7492)
1 file changed, 1 insertion(+), 1 deletion(-)
Oran Agra in commit d8e6a3e5b:
skip a test that uses +inf on valgrind (#7440)
1 file changed, 12 insertions(+), 9 deletions(-)
Oran Agra in commit 28fd1a110:
stabilize tests that look for log lines (#7367)
3 files changed, 33 insertions(+), 11 deletions(-)
Oran Agra in commit a513b4ed9:
tests/valgrind: don't use debug restart (#7404)
4 files changed, 114 insertions(+), 57 deletions(-)
Oran Agra in commit 70e72fc1b:
change references to the github repo location (#7479)
5 files changed, 7 insertions(+), 7 deletions(-)
zhaozhao.zz in commit c63e533cc:
BITOP: propagate only when it really SET or DEL targetkey (#5783)
1 file changed, 2 insertions(+), 1 deletion(-)
antirez in commit 31040ff54:
Update comment to clarify change in #7398.
1 file changed, 4 insertions(+), 1 deletion(-)
antirez in commit b605fe827:
LPOS: option FIRST renamed RANK.
2 files changed, 19 insertions(+), 19 deletions(-)
Dave Nielsen in commit 8deb24954:
updated copyright year
1 file changed, 1 insertion(+), 1 deletion(-)
Oran Agra in commit a61c2930c:
EXEC always fails with EXECABORT and multi-state is cleared
6 files changed, 204 insertions(+), 91 deletions(-)
antirez in commit 3c8041637:
Include cluster.h for getClusterConnectionsCount().
1 file changed, 1 insertion(+)
antirez in commit 5be673ee8:
Fix BITFIELD i64 type handling, see #7417.
1 file changed, 8 insertions(+), 6 deletions(-)
antirez in commit 5f289df9b:
Clarify maxclients and cluster in conf. Remove myself too.
2 files changed, 9 insertions(+), 1 deletion(-)
hwware in commit 000f928d6:
fix memory leak in sentinel connection sharing
1 file changed, 1 insertion(+)
chenhui0212 in commit d9a3c0171:
Fix comments in function raxLowWalk of listpack.c
1 file changed, 2 insertions(+), 2 deletions(-)
Tomasz Poradowski in commit 7526e4506:
ensure SHUTDOWN_NOSAVE in Sentinel mode
2 files changed, 9 insertions(+), 8 deletions(-)
chenhui0212 in commit 6487cbc33:
fix comments in listpack.c
1 file changed, 2 insertions(+), 2 deletions(-)
antirez in commit 69b66bfca:
Use cluster connections too, to limit maxclients.
3 files changed, 23 insertions(+), 8 deletions(-)
antirez in commit 5a960a033:
Tracking: fix enableBcastTrackingForPrefix() invalid sdslen() call.
1 file changed, 1 insertion(+), 1 deletion(-)
root in commit 1c2e50de3:
cluster.c remove if of clusterSendFail in markNodeAsFailingIfNeeded
1 file changed, 1 insertion(+), 1 deletion(-)
meir@redislabs.com in commit 040efb697:
Fix RM_ScanKey module api not to return int encoded strings
3 files changed, 24 insertions(+), 7 deletions(-)
antirez in commit 1b8b7941d:
Fix LCS object type checking. Related to #7379.
1 file changed, 17 insertions(+), 10 deletions(-)
hwware in commit 6b571b45a:
fix memory leak
1 file changed, 11 insertions(+), 12 deletions(-)
hwware in commit 674759062:
fix server crash in STRALGO command
1 file changed, 7 insertions(+)
Benjamin Sergeant in commit a05ffefdc:
Update redis-cli.c
1 file changed, 19 insertions(+), 6 deletions(-)
Jamie Scott in commit 870b63733:
minor fix
1 file changed, 2 insertions(+), 3 deletions(-)
================================================================================ ================================================================================
Redis 6.0.5 Released Tue Jun 09 11:56:08 CEST 2020 Redis 6.0.5 Released Tue Jun 09 11:56:08 CEST 2020
================================================================================ ================================================================================

2
BUGS
View File

@ -1 +1 @@
Please check https://github.com/antirez/redis/issues Please check https://github.com/redis/redis/issues

View File

@ -1,5 +1,5 @@
Copyright (c) 2006-2015, Salvatore Sanfilippo Copyright (c) 2006-2020, Salvatore Sanfilippo
Copyright (C) 2019, John Sully Copyright (C) 2019-2020, John Sully
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View File

@ -95,9 +95,11 @@ libssl-dev on Debian/Ubuntu) and run:
% make BUILD_TLS=yes % make BUILD_TLS=yes
You can enable flash support with: To build with systemd support, you'll need systemd development libraries (such
as libsystemd-dev on Debian/Ubuntu or systemd-devel on CentOS) and run:
% make USE_SYSTEMD=yes
% make MALLOC=memkind
***Note that the following dependencies may be needed: ***Note that the following dependencies may be needed:
% sudo apt-get install autoconf autotools-dev libnuma-dev libtool % sudo apt-get install autoconf autotools-dev libnuma-dev libtool

2
deps/README.md vendored
View File

@ -47,7 +47,7 @@ Hiredis
Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is advised to take a lot of care: Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is advised to take a lot of care:
1. Check with diff if hiredis API changed and what impact it could have in Redis. 1. Check with diff if hiredis API changed and what impact it could have in Redis.
2. Make sure thet the SDS library inside Hiredis and inside Redis are compatible. 2. Make sure that the SDS library inside Hiredis and inside Redis are compatible.
3. After the upgrade, run the Redis Sentinel test. 3. After the upgrade, run the Redis Sentinel test.
4. Check manually that redis-cli and redis-benchmark behave as expecteed, since we have no tests for CLI utilities currently. 4. Check manually that redis-cli and redis-benchmark behave as expecteed, since we have no tests for CLI utilities currently.

2
deps/lua/src/ldo.c vendored
View File

@ -274,7 +274,7 @@ int luaD_precall (lua_State *L, StkId func, int nresults) {
CallInfo *ci; CallInfo *ci;
StkId st, base; StkId st, base;
Proto *p = cl->p; Proto *p = cl->p;
luaD_checkstack(L, p->maxstacksize); luaD_checkstack(L, p->maxstacksize + p->numparams);
func = restorestack(L, funcr); func = restorestack(L, funcr);
if (!p->is_vararg) { /* no varargs? */ if (!p->is_vararg) { /* no varargs? */
base = func + 1; base = func + 1;

View File

@ -159,9 +159,12 @@ tcp-keepalive 300
# By default, clients (including replica servers) on a TLS port are required # By default, clients (including replica servers) on a TLS port are required
# to authenticate using valid client side certificates. # to authenticate using valid client side certificates.
# #
# It is possible to disable authentication using this directive. # If "no" is specified, client certificates are not required and not accepted.
# If "optional" is specified, client certificates are accepted and must be
# valid if provided, but are not required.
# #
# tls-auth-clients no # tls-auth-clients no
# tls-auth-clients optional
# By default, a Redis replica does not attempt to establish a TLS connection # By default, a Redis replica does not attempt to establish a TLS connection
# with its master. # with its master.
@ -775,9 +778,8 @@ replica-priority 100
# #
# The ACL Log tracks failed commands and authentication events associated # The ACL Log tracks failed commands and authentication events associated
# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked # with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
# by ACLs. The ACL Log is stored in and consumes memory. There is no limit # by ACLs. The ACL Log is stored in memory. You can reclaim memory with
# to its length.You can reclaim memory with ACL LOG RESET or set a maximum # ACL LOG RESET. Define the maximum entry length of the ACL Log below.
# length below.
acllog-max-len 128 acllog-max-len 128
# Using an external ACL file # Using an external ACL file
@ -836,6 +838,11 @@ acllog-max-len 128
# Once the limit is reached KeyDB will close all the new connections sending # Once the limit is reached KeyDB will close all the new connections sending
# an error 'max number of clients reached'. # an error 'max number of clients reached'.
# #
# IMPORTANT: When Redis Cluster is used, the max number of connections is also
# shared with the cluster bus: every node in the cluster will use two
# connections, one incoming and another outgoing. It is important to size the
# limit accordingly in case of very large clusters.
#
# maxclients 10000 # maxclients 10000
############################## MEMORY MANAGEMENT ################################ ############################## MEMORY MANAGEMENT ################################
@ -1046,6 +1053,32 @@ lazyfree-lazy-user-del no
# --threads option to match the number of Redis theads, otherwise you'll not # --threads option to match the number of Redis theads, otherwise you'll not
# be able to notice the improvements. # be able to notice the improvements.
############################ KERNEL OOM CONTROL ##############################
# On Linux, it is possible to hint the kernel OOM killer on what processes
# should be killed first when out of memory.
#
# Enabling this feature makes Redis actively control the oom_score_adj value
# for all its processes, depending on their role. The default scores will
# attempt to have background child processes killed before all others, and
# replicas killed before masters.
oom-score-adj no
# When oom-score-adj is used, this directive controls the specific values used
# for master, replica and background child processes. Values range -1000 to
# 1000 (higher means more likely to be killed).
#
# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities)
# can freely increase their value, but not decrease it below its initial
# settings.
#
# Values are used relative to the initial value of oom_score_adj when the server
# starts. Because typically the initial value is 0, they will often match the
# absolute values.
oom-score-adj-values 0 200 800
############################## APPEND ONLY MODE ############################### ############################## APPEND ONLY MODE ###############################
# By default KeyDB asynchronously dumps the dataset on disk. This mode is # By default KeyDB asynchronously dumps the dataset on disk. This mode is
@ -1648,7 +1681,7 @@ client-output-buffer-limit pubsub 32mb 8mb 60
# In the KeyDB protocol, bulk requests, that are, elements representing single # In the KeyDB protocol, bulk requests, that are, elements representing single
# strings, are normally limited ot 512 mb. However you can change this limit # strings, are normally limited ot 512 mb. However you can change this limit
# here. # here, but must be 1mb or greater
# #
# proto-max-bulk-len 512mb # proto-max-bulk-len 512mb

View File

@ -25,4 +25,6 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/scan \ --single unit/moduleapi/scan \
--single unit/moduleapi/datatype \ --single unit/moduleapi/datatype \
--single unit/moduleapi/auth \ --single unit/moduleapi/auth \
--single unit/moduleapi/keyspace_events \
--single unit/moduleapi/blockedclient \
"${@}" "${@}"

View File

@ -236,10 +236,23 @@ ifeq ($(MALLOC),memkind)
endif endif
ifeq ($(BUILD_TLS),yes) ifeq ($(BUILD_TLS),yes)
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS) FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS) FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS) FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
LIBSSL_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libssl && echo $$?)
ifeq ($(LIBSSL_PKGCONFIG),0)
LIBSSL_LIBS=$(shell $(PKG_CONFIG) --libs libssl)
else
LIBSSL_LIBS=-lssl
endif
LIBCRYPTO_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libcrypto && echo $$?)
ifeq ($(LIBCRYPTO_PKGCONFIG),0)
LIBCRYPTO_LIBS=$(shell $(PKG_CONFIG) --libs libcrypto)
else
LIBCRYPTO_LIBS=-lcrypto
endif
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS)
endif endif
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
@ -290,6 +303,8 @@ persist-settings: distclean
echo WARN=$(WARN) >> .make-settings echo WARN=$(WARN) >> .make-settings
echo OPT=$(OPT) >> .make-settings echo OPT=$(OPT) >> .make-settings
echo MALLOC=$(MALLOC) >> .make-settings echo MALLOC=$(MALLOC) >> .make-settings
echo BUILD_TLS=$(BUILD_TLS) >> .make-settings
echo USE_SYSTEMD=$(USE_SYSTEMD) >> .make-settings
echo CFLAGS=$(CFLAGS) >> .make-settings echo CFLAGS=$(CFLAGS) >> .make-settings
echo CXXFLAGS=$(CXXFLAGS) >> .make-settings echo CXXFLAGS=$(CXXFLAGS) >> .make-settings
echo LDFLAGS=$(LDFLAGS) >> .make-settings echo LDFLAGS=$(LDFLAGS) >> .make-settings

View File

@ -1330,6 +1330,7 @@ sds ACLLoadFromFile(const char *filename) {
errors = sdscatprintf(errors, errors = sdscatprintf(errors,
"'%s:%d: username '%s' contains invalid characters. ", "'%s:%d: username '%s' contains invalid characters. ",
g_pserver->acl_filename, linenum, argv[1]); g_pserver->acl_filename, linenum, argv[1]);
sdsfreesplitres(argv,argc);
continue; continue;
} }
@ -1914,7 +1915,7 @@ void aclCommand(client *c) {
addReplyBulkCString(c,"client-info"); addReplyBulkCString(c,"client-info");
addReplyBulkCBuffer(c,le->cinfo,sdslen(le->cinfo)); addReplyBulkCBuffer(c,le->cinfo,sdslen(le->cinfo));
} }
} else if (!strcasecmp(sub,"help")) { } else if (c->argc == 2 && !strcasecmp(sub,"help")) {
const char *help[] = { const char *help[] = {
"LOAD -- Reload users from the ACL file.", "LOAD -- Reload users from the ACL file.",
"SAVE -- Save the current config to the ACL file.", "SAVE -- Save the current config to the ACL file.",

View File

@ -1335,12 +1335,16 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
while(raxNext(&ri)) { while(raxNext(&ri)) {
streamCG *group = (streamCG*)ri.data; streamCG *group = (streamCG*)ri.data;
/* Emit the XGROUP CREATE in order to create the group. */ /* Emit the XGROUP CREATE in order to create the group. */
if (rioWriteBulkCount(r,'*',5) == 0) return 0; if (!rioWriteBulkCount(r,'*',5) ||
if (rioWriteBulkString(r,"XGROUP",6) == 0) return 0; !rioWriteBulkString(r,"XGROUP",6) ||
if (rioWriteBulkString(r,"CREATE",6) == 0) return 0; !rioWriteBulkString(r,"CREATE",6) ||
if (rioWriteBulkObject(r,key) == 0) return 0; !rioWriteBulkObject(r,key) ||
if (rioWriteBulkString(r,(char*)ri.key,ri.key_len) == 0) return 0; !rioWriteBulkString(r,(char*)ri.key,ri.key_len) ||
if (rioWriteBulkStreamID(r,&group->last_id) == 0) return 0; !rioWriteBulkStreamID(r,&group->last_id))
{
raxStop(&ri);
return 0;
}
/* Generate XCLAIMs for each consumer that happens to /* Generate XCLAIMs for each consumer that happens to
* have pending entries. Empty consumers have no semantical * have pending entries. Empty consumers have no semantical
@ -1361,6 +1365,9 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
ri.key_len,consumer, ri.key_len,consumer,
ri_pel.key,nack) == 0) ri_pel.key,nack) == 0)
{ {
raxStop(&ri_pel);
raxStop(&ri_cons);
raxStop(&ri);
return 0; return 0;
} }
} }

View File

@ -257,7 +257,7 @@ int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
/* If the top significant bit is 1, propagate it to all the /* If the top significant bit is 1, propagate it to all the
* higher bits for two's complement representation of signed * higher bits for two's complement representation of signed
* integers. */ * integers. */
if (value & ((uint64_t)1 << (bits-1))) if (bits < 64 && (value & ((uint64_t)1 << (bits-1))))
value |= ((uint64_t)-1) << bits; value |= ((uint64_t)-1) << bits;
return value; return value;
} }
@ -356,7 +356,6 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
handle_wrap: handle_wrap:
{ {
uint64_t mask = ((uint64_t)-1) << bits;
uint64_t msb = (uint64_t)1 << (bits-1); uint64_t msb = (uint64_t)1 << (bits-1);
uint64_t a = value, b = incr, c; uint64_t a = value, b = incr, c;
c = a+b; /* Perform addition as unsigned so that's defined. */ c = a+b; /* Perform addition as unsigned so that's defined. */
@ -364,10 +363,13 @@ handle_wrap:
/* If the sign bit is set, propagate to all the higher order /* If the sign bit is set, propagate to all the higher order
* bits, to cap the negative value. If it's clear, mask to * bits, to cap the negative value. If it's clear, mask to
* the positive integer limit. */ * the positive integer limit. */
if (c & msb) { if (bits < 64) {
c |= mask; uint64_t mask = ((uint64_t)-1) << bits;
} else { if (c & msb) {
c &= ~mask; c |= mask;
} else {
c &= ~mask;
}
} }
*limit = c; *limit = c;
} }
@ -831,11 +833,12 @@ void bitopCommand(client *c) {
setKey(c,c->db,targetkey,o); setKey(c,c->db,targetkey,o);
notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id); notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
decrRefCount(o); decrRefCount(o);
g_pserver->dirty++;
} else if (dbDelete(c->db,targetkey)) { } else if (dbDelete(c->db,targetkey)) {
signalModifiedKey(c,c->db,targetkey); signalModifiedKey(c,c->db,targetkey);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
g_pserver->dirty++;
} }
g_pserver->dirty++;
addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */ addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */
} }

View File

@ -435,7 +435,15 @@ int clusterLockConfig(char *filename) {
return C_ERR; return C_ERR;
} }
/* Lock acquired: leak the 'fd' by not closing it, so that we'll retain the /* Lock acquired: leak the 'fd' by not closing it, so that we'll retain the
* lock to the file as long as the process exists. */ * lock to the file as long as the process exists.
*
* After fork, the child process will get the fd opened by the parent process,
* we need save `fd` to `cluster_config_file_lock_fd`, so that in redisFork(),
* it will be closed in the child process.
* If it is not closed, when the main process is killed -9, but the child process
* (redis-aof-rewrite) is still alive, the fd(lock) will still be held by the
* child process, and the main process will fail to get lock, means fail to start. */
g_pserver->cluster_config_file_lock_fd = fd;
#endif /* __sun */ #endif /* __sun */
return C_OK; return C_OK;
@ -490,6 +498,7 @@ void clusterInit(void) {
/* Lock the cluster config file to make sure every node uses /* Lock the cluster config file to make sure every node uses
* its own nodes.conf. */ * its own nodes.conf. */
g_pserver->cluster_config_file_lock_fd = -1;
if (clusterLockConfig(g_pserver->cluster_configfile) == C_ERR) if (clusterLockConfig(g_pserver->cluster_configfile) == C_ERR)
exit(1); exit(1);
@ -708,7 +717,17 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
return; return;
} }
connection *conn = g_pserver->tls_cluster ? connCreateAcceptedTLS(cfd,1) : connCreateAcceptedSocket(cfd); connection *conn = g_pserver->tls_cluster ?
connCreateAcceptedTLS(cfd, TLS_CLIENT_AUTH_YES) : connCreateAcceptedSocket(cfd);
/* Make sure connection is not in an error state */
if (connGetState(conn) != CONN_STATE_ACCEPTING) {
serverLog(LL_VERBOSE,
"Error creating an accepting connection for cluster node: %s",
connGetLastError(conn));
connClose(conn);
return;
}
connNonBlock(conn); connNonBlock(conn);
connEnableTcpNoDelay(conn); connEnableTcpNoDelay(conn);
@ -729,6 +748,16 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
} }
} }
/* Return the approximated number of sockets we are using in order to
* take the cluster bus connections. */
unsigned long getClusterConnectionsCount(void) {
/* We decrement the number of nodes by one, since there is the
* "myself" node too in the list. Each node uses two file descriptors,
* one incoming and one outgoing, thus the multiplication by 2. */
return g_pserver->cluster_enabled ?
((dictSize(g_pserver->cluster->nodes)-1)*2) : 0;
}
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Key space handling * Key space handling
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
@ -1292,8 +1321,11 @@ void markNodeAsFailingIfNeeded(clusterNode *node) {
node->fail_time = mstime(); node->fail_time = mstime();
/* Broadcast the failing node name to everybody, forcing all the other /* Broadcast the failing node name to everybody, forcing all the other
* reachable nodes to flag the node as FAIL. */ * reachable nodes to flag the node as FAIL.
if (nodeIsMaster(myself)) clusterSendFail(node->name); * We do that even if this node is a replica and not a master: anyway
* the failing state is triggered collecting failure reports from masters,
* so here the replica is only helping propagating this status. */
clusterSendFail(node->name);
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
} }
@ -1786,7 +1818,7 @@ int clusterProcessPacket(clusterLink *link) {
} else if (type == CLUSTERMSG_TYPE_MODULE) { } else if (type == CLUSTERMSG_TYPE_MODULE) {
uint32_t explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); uint32_t explen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
explen += sizeof(clusterMsgDataPublish) - explen += sizeof(clusterMsgModule) -
3 + ntohl(hdr->data.module.msg.len); 3 + ntohl(hdr->data.module.msg.len);
if (totlen != explen) return 1; if (totlen != explen) return 1;
} }
@ -4141,11 +4173,15 @@ sds clusterGenNodeDescription(clusterNode *node) {
else else
ci = sdscatlen(ci," - ",3); ci = sdscatlen(ci," - ",3);
unsigned long long nodeEpoch = node->configEpoch;
if (nodeIsSlave(node) && node->slaveof) {
nodeEpoch = node->slaveof->configEpoch;
}
/* Latency from the POV of this node, config epoch, link status */ /* Latency from the POV of this node, config epoch, link status */
ci = sdscatprintf(ci,"%lld %lld %llu %s", ci = sdscatprintf(ci,"%lld %lld %llu %s",
(long long) node->ping_sent, (long long) node->ping_sent,
(long long) node->pong_received, (long long) node->pong_received,
(unsigned long long) node->configEpoch, nodeEpoch,
(node->link || node->flags & CLUSTER_NODE_MYSELF) ? (node->link || node->flags & CLUSTER_NODE_MYSELF) ?
"connected" : "disconnected"); "connected" : "disconnected");
@ -5025,7 +5061,8 @@ void restoreCommand(client *c) {
} }
/* Make sure this key does not already exist here... */ /* Make sure this key does not already exist here... */
if (!replace && lookupKeyWrite(c->db,c->argv[1]) != NULL) { robj *key = c->argv[1];
if (!replace && lookupKeyWrite(c->db,key) != NULL) {
addReply(c,shared.busykeyerr); addReply(c,shared.busykeyerr);
return; return;
} }
@ -5047,24 +5084,38 @@ void restoreCommand(client *c) {
rioInitWithBuffer(&payload,szFromObj(c->argv[3])); rioInitWithBuffer(&payload,szFromObj(c->argv[3]));
if (((type = rdbLoadObjectType(&payload)) == -1) || if (((type = rdbLoadObjectType(&payload)) == -1) ||
((obj = rdbLoadObject(type,&payload,szFromObj(c->argv[1]), OBJ_MVCC_INVALID)) == NULL)) ((obj = rdbLoadObject(type,&payload,szFromObj(key), OBJ_MVCC_INVALID)) == NULL))
{ {
addReplyError(c,"Bad data format"); addReplyError(c,"Bad data format");
return; return;
} }
/* Remove the old key if needed. */ /* Remove the old key if needed. */
if (replace) dbDelete(c->db,c->argv[1]); int deleted = 0;
if (replace)
deleted = dbDelete(c->db,key);
if (ttl && !absttl) ttl+=mstime();
if (ttl && checkAlreadyExpired(ttl)) {
if (deleted) {
rewriteClientCommandVector(c,2,shared.del,key);
signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
g_pserver->dirty++;
}
decrRefCount(obj);
addReply(c, shared.ok);
return;
}
/* Create the key and set the TTL if any */ /* Create the key and set the TTL if any */
dbAdd(c->db,c->argv[1],obj); dbAdd(c->db,key,obj);
if (ttl) { if (ttl) {
if (!absttl) ttl+=mstime(); setExpire(c,c->db,key,nullptr,ttl);
setExpire(c,c->db,c->argv[1],nullptr,ttl);
} }
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000); objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
signalModifiedKey(c,c->db,c->argv[1]); signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",c->argv[1],c->db->id); notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",key,c->db->id);
addReply(c,shared.ok); addReply(c,shared.ok);
g_pserver->dirty++; g_pserver->dirty++;
} }
@ -5775,8 +5826,10 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
/* Handle the read-only client case reading from a slave: if this /* Handle the read-only client case reading from a slave: if this
* node is a slave and the request is about an hash slot our master * node is a slave and the request is about an hash slot our master
* is serving, we can reply without redirection. */ * is serving, we can reply without redirection. */
int is_readonly_command = (c->cmd->flags & CMD_READONLY) ||
(c->cmd->proc == execCommand && !(c->mstate.cmd_inv_flags & CMD_READONLY));
if (c->flags & CLIENT_READONLY && if (c->flags & CLIENT_READONLY &&
(cmd->flags & CMD_READONLY || cmd->proc == evalCommand || (is_readonly_command || cmd->proc == evalCommand ||
cmd->proc == evalShaCommand) && cmd->proc == evalShaCommand) &&
nodeIsSlave(myself) && nodeIsSlave(myself) &&
myself->slaveof == n) myself->slaveof == n)

View File

@ -287,6 +287,7 @@ typedef struct {
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask); clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);
int clusterRedirectBlockedClientIfNeeded(client *c); int clusterRedirectBlockedClientIfNeeded(client *c);
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code); void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code);
unsigned long getClusterConnectionsCount(void);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -100,6 +100,12 @@ configEnum repl_diskless_load_enum[] = {
{NULL, 0} {NULL, 0}
}; };
configEnum tls_auth_clients_enum[] = {
{"no", TLS_CLIENT_AUTH_NO},
{"yes", TLS_CLIENT_AUTH_YES},
{"optional", TLS_CLIENT_AUTH_OPTIONAL},
{NULL, 0}
};
/* Output buffer limits presets. */ /* Output buffer limits presets. */
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = { clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
{0, 0, 0}, /* normal */ {0, 0, 0}, /* normal */
@ -107,6 +113,9 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
{1024*1024*32, 1024*1024*8, 60} /* pubsub */ {1024*1024*32, 1024*1024*8, 60} /* pubsub */
}; };
/* OOM Score defaults */
int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT] = { 0, 200, 800 };
/* Generic config infrastructure function pointers /* Generic config infrastructure function pointers
* int is_valid_fn(val, err) * int is_valid_fn(val, err)
* Return 1 when val is valid, and 0 when invalid. * Return 1 when val is valid, and 0 when invalid.
@ -289,6 +298,59 @@ void queueLoadModule(sds path, sds *argv, int argc) {
listAddNodeTail(g_pserver->loadmodule_queue,loadmod); listAddNodeTail(g_pserver->loadmodule_queue,loadmod);
} }
/* Parse an array of CONFIG_OOM_COUNT sds strings, validate and populate
* g_pserver->oom_score_adj_values if valid.
*/
static int updateOOMScoreAdjValues(sds *args, const char **err) {
int i;
int values[CONFIG_OOM_COUNT];
for (i = 0; i < CONFIG_OOM_COUNT; i++) {
char *eptr;
long long val = strtoll(args[i], &eptr, 10);
if (*eptr != '\0' || val < -1000 || val > 1000) {
if (err) *err = "Invalid oom-score-adj-values, elements must be between -1000 and 1000.";
return C_ERR;
}
values[i] = val;
}
/* Verify that the values make sense. If they don't omit a warning but
* keep the configuration, which may still be valid for privileged processes.
*/
if (values[CONFIG_OOM_REPLICA] < values[CONFIG_OOM_MASTER] ||
values[CONFIG_OOM_BGCHILD] < values[CONFIG_OOM_REPLICA]) {
serverLog(LOG_WARNING,
"The oom-score-adj-values configuration may not work for non-privileged processes! "
"Please consult the documentation.");
}
/* Store values, retain previous config for rollback in case we fail. */
int old_values[CONFIG_OOM_COUNT];
for (i = 0; i < CONFIG_OOM_COUNT; i++) {
old_values[i] = g_pserver->oom_score_adj_values[i];
g_pserver->oom_score_adj_values[i] = values[i];
}
/* Update */
if (setOOMScoreAdj(-1) == C_ERR) {
/* Roll back */
for (i = 0; i < CONFIG_OOM_COUNT; i++)
g_pserver->oom_score_adj_values[i] = old_values[i];
if (err)
*err = "Failed to apply oom-score-adj-values configuration, check server logs.";
return C_ERR;
}
return C_OK;
}
void initConfigValues() { void initConfigValues() {
for (standardConfig *config = configs; config->name != NULL; config++) { for (standardConfig *config = configs; config->name != NULL; config++) {
config->interface.init(config->data); config->interface.init(config->data);
@ -480,6 +542,8 @@ void loadServerConfigFromString(char *config) {
cserver.client_obuf_limits[type].hard_limit_bytes = hard; cserver.client_obuf_limits[type].hard_limit_bytes = hard;
cserver.client_obuf_limits[type].soft_limit_bytes = soft; cserver.client_obuf_limits[type].soft_limit_bytes = soft;
cserver.client_obuf_limits[type].soft_limit_seconds = soft_seconds; cserver.client_obuf_limits[type].soft_limit_seconds = soft_seconds;
} else if (!strcasecmp(argv[0],"oom-score-adj-values") && argc == 1 + CONFIG_OOM_COUNT) {
if (updateOOMScoreAdjValues(&argv[1], &err) == C_ERR) goto loaderr;
} else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) { } else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
int flags = keyspaceEventsStringToFlags(argv[1]); int flags = keyspaceEventsStringToFlags(argv[1]);
@ -541,7 +605,7 @@ void loadServerConfigFromString(char *config) {
} }
} else if (!strcasecmp(argv[0], "active-replica") && argc == 2) { } else if (!strcasecmp(argv[0], "active-replica") && argc == 2) {
g_pserver->fActiveReplica = yesnotoi(argv[1]); g_pserver->fActiveReplica = yesnotoi(argv[1]);
if (g_pserver->repl_slave_ro) { if (g_pserver->fActiveReplica && g_pserver->repl_slave_ro) {
g_pserver->repl_slave_ro = FALSE; g_pserver->repl_slave_ro = FALSE;
serverLog(LL_NOTICE, "Notice: \"active-replica yes\" implies \"replica-read-only no\""); serverLog(LL_NOTICE, "Notice: \"active-replica yes\" implies \"replica-read-only no\"");
} }
@ -605,7 +669,8 @@ void loadServerConfig(char *filename, char *options) {
} else { } else {
if ((fp = fopen(filename,"r")) == NULL) { if ((fp = fopen(filename,"r")) == NULL) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Fatal error, can't open config file '%s'", filename); "Fatal error, can't open config file '%s': %s",
filename, strerror(errno));
exit(1); exit(1);
} }
} }
@ -775,6 +840,17 @@ void configSetCommand(client *c) {
cserver.client_obuf_limits[type].soft_limit_seconds = soft_seconds; cserver.client_obuf_limits[type].soft_limit_seconds = soft_seconds;
} }
sdsfreesplitres(v,vlen); sdsfreesplitres(v,vlen);
} config_set_special_field("oom-score-adj-values") {
int vlen;
int success = 1;
sds *v = sdssplitlen(szFromObj(o), sdslen(szFromObj(o)), " ", 1, &vlen);
if (vlen != CONFIG_OOM_COUNT || updateOOMScoreAdjValues(v, &errstr) == C_ERR)
success = 0;
sdsfreesplitres(v, vlen);
if (!success)
goto badfmt;
} config_set_special_field("notify-keyspace-events") { } config_set_special_field("notify-keyspace-events") {
int flags = keyspaceEventsStringToFlags(szFromObj(o)); int flags = keyspaceEventsStringToFlags(szFromObj(o));
@ -987,6 +1063,26 @@ void configGetCommand(client *c) {
} }
matches++; matches++;
} }
if (stringmatch(pattern,"oom-score-adj-values",0)) {
sds buf = sdsempty();
int j;
for (j = 0; j < CONFIG_OOM_COUNT; j++) {
buf = sdscatprintf(buf,"%d", g_pserver->oom_score_adj_values[j]);
if (j != CONFIG_OOM_COUNT-1)
buf = sdscatlen(buf," ",1);
}
addReplyBulkCString(c,"oom-score-adj-values");
addReplyBulkCString(c,buf);
sdsfree(buf);
matches++;
}
if (stringmatch(pattern,"active-replica",1)) {
addReplyBulkCString(c,"active-replica");
addReplyBulkCString(c, g_pserver->fActiveReplica ? "yes" : "no");
matches++;
}
setDeferredMapLen(c,replylen,matches); setDeferredMapLen(c,replylen,matches);
} }
@ -1035,6 +1131,8 @@ struct rewriteConfigState {
sds *lines; /* Current lines as an array of sds strings */ sds *lines; /* Current lines as an array of sds strings */
int has_tail; /* True if we already added directives that were int has_tail; /* True if we already added directives that were
not present in the original config file. */ not present in the original config file. */
int force_all; /* True if we want all keywords to be force
written. Currently only used for testing. */
}; };
/* Append the new line to the current configuration state. */ /* Append the new line to the current configuration state. */
@ -1082,6 +1180,7 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
state->numlines = 0; state->numlines = 0;
state->lines = NULL; state->lines = NULL;
state->has_tail = 0; state->has_tail = 0;
state->force_all = 0;
if (fp == NULL) return state; if (fp == NULL) return state;
/* Read the old file line by line, populate the state. */ /* Read the old file line by line, populate the state. */
@ -1160,7 +1259,7 @@ void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *opti
rewriteConfigMarkAsProcessed(state,option); rewriteConfigMarkAsProcessed(state,option);
if (!l && !force) { if (!l && !force && !state->force_all) {
/* Option not used previously, and we are not forced to use it. */ /* Option not used previously, and we are not forced to use it. */
sdsfree(line); sdsfree(line);
sdsfree(o); sdsfree(o);
@ -1404,6 +1503,26 @@ void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state
} }
} }
/* Rewrite the oom-score-adj-values option. */
void rewriteConfigOOMScoreAdjValuesOption(struct rewriteConfigState *state) {
int force = 0;
int j;
const char *option = "oom-score-adj-values";
sds line;
line = sdsnew(option);
line = sdscatlen(line, " ", 1);
for (j = 0; j < CONFIG_OOM_COUNT; j++) {
if (g_pserver->oom_score_adj_values[j] != configOOMScoreAdjValuesDefaults[j])
force = 1;
line = sdscatprintf(line, "%d", g_pserver->oom_score_adj_values[j]);
if (j+1 != CONFIG_OOM_COUNT)
line = sdscatlen(line, " ", 1);
}
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite the bind option. */ /* Rewrite the bind option. */
void rewriteConfigBindOption(struct rewriteConfigState *state) { void rewriteConfigBindOption(struct rewriteConfigState *state) {
int force = 1; int force = 1;
@ -1572,15 +1691,18 @@ cleanup:
* *
* Configuration parameters that are at their default value, unless already * Configuration parameters that are at their default value, unless already
* explicitly included in the old configuration file, are not rewritten. * explicitly included in the old configuration file, are not rewritten.
* The force_all flag overrides this behavior and forces everything to be
* written. This is currently only used for testing purposes.
* *
* On error -1 is returned and errno is set accordingly, otherwise 0. */ * On error -1 is returned and errno is set accordingly, otherwise 0. */
int rewriteConfig(char *path) { int rewriteConfig(char *path, int force_all) {
struct rewriteConfigState *state; struct rewriteConfigState *state;
sds newcontent; sds newcontent;
int retval; int retval;
/* Step 1: read the old config into our rewrite state. */ /* Step 1: read the old config into our rewrite state. */
if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1; if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1;
if (force_all) state->force_all = 1;
/* Step 2: rewrite every single option, replacing or appending it inside /* Step 2: rewrite every single option, replacing or appending it inside
* the rewrite state. */ * the rewrite state. */
@ -1604,6 +1726,7 @@ int rewriteConfig(char *path) {
rewriteConfigClientoutputbufferlimitOption(state); rewriteConfigClientoutputbufferlimitOption(state);
rewriteConfigYesNoOption(state,"active-replica",g_pserver->fActiveReplica,CONFIG_DEFAULT_ACTIVE_REPLICA); rewriteConfigYesNoOption(state,"active-replica",g_pserver->fActiveReplica,CONFIG_DEFAULT_ACTIVE_REPLICA);
rewriteConfigStringOption(state, "version-override",KEYDB_SET_VERSION,KEYDB_REAL_VERSION); rewriteConfigStringOption(state, "version-override",KEYDB_SET_VERSION,KEYDB_REAL_VERSION);
rewriteConfigOOMScoreAdjValuesOption(state);
/* Rewrite Sentinel config if in Sentinel mode. */ /* Rewrite Sentinel config if in Sentinel mode. */
if (g_pserver->sentinel_mode) rewriteConfigSentinelOption(state); if (g_pserver->sentinel_mode) rewriteConfigSentinelOption(state);
@ -2174,12 +2297,28 @@ static int validateMultiMasterNoForward(int val, const char **) {
return 1; return 1;
} }
static int updateOOMScoreAdj(int val, int prev, const char **err) {
UNUSED(prev);
if (val) {
if (setOOMScoreAdj(-1) == C_ERR) {
*err = "Failed to set current oom_score_adj. Check server logs.";
return 0;
}
}
return 1;
}
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
static int updateTlsCfg(char *val, char *prev, const char **err) { static int updateTlsCfg(char *val, char *prev, const char **err) {
UNUSED(val); UNUSED(val);
UNUSED(prev); UNUSED(prev);
UNUSED(err); UNUSED(err);
if (tlsConfigure(&g_pserver->tls_ctx_config) == C_ERR) {
/* If TLS is enabled, try to configure OpenSSL. */
if ((g_pserver->tls_port || g_pserver->tls_replication || g_pserver->tls_cluster)
&& tlsConfigure(&g_pserver->tls_ctx_config) == C_ERR) {
*err = "Unable to update TLS configuration. Check server logs."; *err = "Unable to update TLS configuration. Check server logs.";
return 0; return 0;
} }
@ -2238,6 +2377,7 @@ standardConfig configs[] = {
createBoolConfig("multi-master-no-forward", NULL, MODIFIABLE_CONFIG, cserver.multimaster_no_forward, 0, validateMultiMasterNoForward, NULL), createBoolConfig("multi-master-no-forward", NULL, MODIFIABLE_CONFIG, cserver.multimaster_no_forward, 0, validateMultiMasterNoForward, NULL),
createBoolConfig("allow-write-during-load", NULL, MODIFIABLE_CONFIG, g_pserver->fWriteDuringActiveLoad, 0, NULL, NULL), createBoolConfig("allow-write-during-load", NULL, MODIFIABLE_CONFIG, g_pserver->fWriteDuringActiveLoad, 0, NULL, NULL),
createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, fDummy, 0, NULL, NULL), createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, fDummy, 0, NULL, NULL),
createBoolConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, g_pserver->oom_score_adj, 0, NULL, updateOOMScoreAdj),
/* String Configs */ /* String Configs */
createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->acl_filename, "", NULL, NULL), createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->acl_filename, "", NULL, NULL),
@ -2309,7 +2449,7 @@ standardConfig configs[] = {
createLongLongConfig("cluster-node-timeout", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->cluster_node_timeout, 15000, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("cluster-node-timeout", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->cluster_node_timeout, 15000, INTEGER_CONFIG, NULL, NULL),
createLongLongConfig("slowlog-log-slower-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, g_pserver->slowlog_log_slower_than, 10000, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("slowlog-log-slower-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, g_pserver->slowlog_log_slower_than, 10000, INTEGER_CONFIG, NULL, NULL),
createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL),
createLongLongConfig("proto-max-bulk-len", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */ createLongLongConfig("proto-max-bulk-len", NULL, MODIFIABLE_CONFIG, 1024*1024, LLONG_MAX, g_pserver->proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */
createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL),
createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, g_pserver->repl_backlog_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */ createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, g_pserver->repl_backlog_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */
@ -2387,7 +2527,7 @@ NULL
addReplyError(c,"The server is running without a config file"); addReplyError(c,"The server is running without a config file");
return; return;
} }
if (rewriteConfig(cserver.configfile) == -1) { if (rewriteConfig(cserver.configfile, 0) == -1) {
serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", strerror(errno)); serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", strerror(errno));
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno)); addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
} else { } else {

View File

@ -54,6 +54,7 @@
#define HAVE_PROC_MAPS 1 #define HAVE_PROC_MAPS 1
#define HAVE_PROC_SMAPS 1 #define HAVE_PROC_SMAPS 1
#define HAVE_PROC_SOMAXCONN 1 #define HAVE_PROC_SOMAXCONN 1
#define HAVE_PROC_OOM_SCORE_ADJ 1
#endif #endif
/* Test for task_info() */ /* Test for task_info() */

View File

@ -85,8 +85,12 @@ connection *connCreateSocket() {
/* Create a new socket-type connection that is already associated with /* Create a new socket-type connection that is already associated with
* an accepted connection. * an accepted connection.
* *
* The socket is not read for I/O until connAccept() was called and * The socket is not ready for I/O until connAccept() was called and
* invoked the connection-level accept handler. * invoked the connection-level accept handler.
*
* Callers should use connGetState() and verify the created connection
* is not in an error state (which is not possible for a socket connection,
* but could but possible with other protocols).
*/ */
connection *connCreateAcceptedSocket(int fd) { connection *connCreateAcceptedSocket(int fd) {
connection *conn = connCreateSocket(); connection *conn = connCreateSocket();
@ -334,6 +338,11 @@ static ssize_t connSocketSyncReadLine(connection *conn, char *ptr, ssize_t size,
return syncReadLine(conn->fd, ptr, size, timeout); return syncReadLine(conn->fd, ptr, size, timeout);
} }
static int connSocketGetType(struct connection *conn) {
(void) conn;
return CONN_TYPE_SOCKET;
}
ConnectionType CT_Socket = { ConnectionType CT_Socket = {
connSocketEventHandler, connSocketEventHandler,
@ -348,7 +357,9 @@ ConnectionType CT_Socket = {
connSocketBlockingConnect, connSocketBlockingConnect,
connSocketSyncWrite, connSocketSyncWrite,
connSocketSyncRead, connSocketSyncRead,
connSocketSyncReadLine connSocketSyncReadLine,
nullptr,
connSocketGetType
}; };

View File

@ -52,6 +52,9 @@ typedef enum {
#define CONN_FLAG_READ_THREADSAFE (1<<2) #define CONN_FLAG_READ_THREADSAFE (1<<2)
#define CONN_FLAG_WRITE_THREADSAFE (1<<3) #define CONN_FLAG_WRITE_THREADSAFE (1<<3)
#define CONN_TYPE_SOCKET 1
#define CONN_TYPE_TLS 2
typedef void (*ConnectionCallbackFunc)(struct connection *conn); typedef void (*ConnectionCallbackFunc)(struct connection *conn);
typedef struct ConnectionType { typedef struct ConnectionType {
@ -69,6 +72,7 @@ typedef struct ConnectionType {
ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout); ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout); ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
void (*marshal_thread)(struct connection *conn); void (*marshal_thread)(struct connection *conn);
int (*get_type)(struct connection *conn);
} ConnectionType; } ConnectionType;
struct connection { struct connection {
@ -204,6 +208,11 @@ static inline void connMarshalThread(connection *conn) {
conn->type->marshal_thread(conn); conn->type->marshal_thread(conn);
} }
/* Return CONN_TYPE_* for the specified connection */
static inline int connGetType(connection *conn) {
return conn->type->get_type(conn);
}
connection *connCreateSocket(); connection *connCreateSocket();
connection *connCreateAcceptedSocket(int fd); connection *connCreateAcceptedSocket(int fd);

View File

@ -1057,14 +1057,6 @@ void shutdownCommand(client *c) {
return; return;
} }
} }
/* When SHUTDOWN is called while the server is loading a dataset in
* memory we need to make sure no attempt is performed to save
* the dataset on shutdown (otherwise it could overwrite the current DB
* with half-read data).
*
* Also when in Sentinel mode clear the SAVE flag and force NOSAVE. */
if (g_pserver->loading || g_pserver->sentinel_mode)
flags = (flags & ~SHUTDOWN_SAVE) | SHUTDOWN_NOSAVE;
if (prepareForShutdown(flags) == C_OK) throw ShutdownException(); if (prepareForShutdown(flags) == C_OK) throw ShutdownException();
addReplyError(c,"Errors trying to SHUTDOWN. Check logs."); addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
} }

View File

@ -392,6 +392,7 @@ void debugCommand(client *c) {
"DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false]", "DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false]",
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.", "ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
"LOG <message> -- write message to the server log.", "LOG <message> -- write message to the server log.",
"LEAK <string> -- Create a memory leak of the input string.",
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.", "HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
"HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.", "HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.",
"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.", "LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.",
@ -410,6 +411,7 @@ void debugCommand(client *c) {
"STRUCTSIZE -- Return the size of different Redis core C structures.", "STRUCTSIZE -- Return the size of different Redis core C structures.",
"ZIPLIST <key> -- Show low level info about the ziplist encoding.", "ZIPLIST <key> -- Show low level info about the ziplist encoding.",
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.", "STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
"CONFIG-REWRITE-FORCE-ALL -- Like CONFIG REWRITE but writes all configuration options, including keywords not listed in original configuration file or default values.",
#ifdef USE_JEMALLOC #ifdef USE_JEMALLOC
"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.", "MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.",
"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.", "MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.",
@ -444,6 +446,9 @@ NULL
} else if (!strcasecmp(szFromObj(c->argv[1]),"log") && c->argc == 3) { } else if (!strcasecmp(szFromObj(c->argv[1]),"log") && c->argc == 3) {
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)ptrFromObj(c->argv[2])); serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)ptrFromObj(c->argv[2]));
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"leak") && c->argc == 3) {
sdsdup(szFromObj(c->argv[2]));
addReply(c,shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"reload")) { } else if (!strcasecmp(szFromObj(c->argv[1]),"reload")) {
int flush = 1, save = 1; int flush = 1, save = 1;
int flags = RDBFLAGS_NONE; int flags = RDBFLAGS_NONE;
@ -601,14 +606,13 @@ NULL
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != C_OK) if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != C_OK)
return; return;
dictExpand(c->db->pdict,keys); dictExpand(c->db->pdict,keys);
long valsize = 0;
if ( c->argc == 5 && getLongFromObjectOrReply(c, c->argv[4], &valsize, NULL) != C_OK )
return;
for (j = 0; j < keys; j++) { for (j = 0; j < keys; j++) {
long valsize = 0;
snprintf(buf,sizeof(buf),"%s:%lu", snprintf(buf,sizeof(buf),"%s:%lu",
(c->argc == 3) ? "key" : (char*)ptrFromObj(c->argv[3]), j); (c->argc == 3) ? "key" : (char*)ptrFromObj(c->argv[3]), j);
key = createStringObject(buf,strlen(buf)); key = createStringObject(buf,strlen(buf));
if (c->argc == 5)
if (getLongFromObjectOrReply(c, c->argv[4], &valsize, NULL) != C_OK)
return;
if (lookupKeyWrite(c->db,key) != NULL) { if (lookupKeyWrite(c->db,key) != NULL) {
decrRefCount(key); decrRefCount(key);
continue; continue;
@ -825,6 +829,12 @@ NULL
c->flags &= ~(CLIENT_MASTER | CLIENT_MASTER_FORCE_REPLY); c->flags &= ~(CLIENT_MASTER | CLIENT_MASTER_FORCE_REPLY);
} }
addReply(c, shared.ok); addReply(c, shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"config-rewrite-force-all") && c->argc == 2)
{
if (rewriteConfig(cserver.configfile, 1) == -1)
addReplyError(c, "CONFIG-REWRITE-FORCE-ALL failed");
else
addReply(c, shared.ok);
#ifdef USE_JEMALLOC #ifdef USE_JEMALLOC
} else if(!strcasecmp(szFromObj(c->argv[1]),"mallctl") && c->argc >= 3) { } else if(!strcasecmp(szFromObj(c->argv[1]),"mallctl") && c->argc >= 3) {
mallctl_int(c, c->argv+2, c->argc-2); mallctl_int(c, c->argv+2, c->argc-2);
@ -958,8 +968,11 @@ static void *getMcontextEip(ucontext_t *uc) {
/* OSX >= 10.6 */ /* OSX >= 10.6 */
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__) #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
return (void*) uc->uc_mcontext->__ss.__rip; return (void*) uc->uc_mcontext->__ss.__rip;
#else #elif defined(__i386__)
return (void*) uc->uc_mcontext->__ss.__eip; return (void*) uc->uc_mcontext->__ss.__eip;
#else
/* OSX ARM64 */
return (void*) arm_thread_state64_get_pc(uc->uc_mcontext->__ss);
#endif #endif
#elif defined(__linux__) #elif defined(__linux__)
/* Linux */ /* Linux */
@ -1045,7 +1058,7 @@ void logRegisters(ucontext_t *uc) {
(unsigned long) uc->uc_mcontext->__ss.__gs (unsigned long) uc->uc_mcontext->__ss.__gs
); );
logStackContent((void**)uc->uc_mcontext->__ss.__rsp); logStackContent((void**)uc->uc_mcontext->__ss.__rsp);
#else #elif defined(__i386__)
/* OSX x86 */ /* OSX x86 */
serverLog(LL_WARNING, serverLog(LL_WARNING,
"\n" "\n"
@ -1071,6 +1084,55 @@ void logRegisters(ucontext_t *uc) {
(unsigned long) uc->uc_mcontext->__ss.__gs (unsigned long) uc->uc_mcontext->__ss.__gs
); );
logStackContent((void**)uc->uc_mcontext->__ss.__esp); logStackContent((void**)uc->uc_mcontext->__ss.__esp);
#else
/* OSX ARM64 */
serverLog(LL_WARNING,
"\n"
"x0:%016lx x1:%016lx x2:%016lx x3:%016lx\n"
"x4:%016lx x5:%016lx x6:%016lx x7:%016lx\n"
"x8:%016lx x9:%016lx x10:%016lx x11:%016lx\n"
"x12:%016lx x13:%016lx x14:%016lx x15:%016lx\n"
"x16:%016lx x17:%016lx x18:%016lx x19:%016lx\n"
"x20:%016lx x21:%016lx x22:%016lx x23:%016lx\n"
"x24:%016lx x25:%016lx x26:%016lx x27:%016lx\n"
"x28:%016lx fp:%016lx lr:%016lx\n"
"sp:%016lx pc:%016lx cpsr:%08lx\n",
(unsigned long) uc->uc_mcontext->__ss.__x[0],
(unsigned long) uc->uc_mcontext->__ss.__x[1],
(unsigned long) uc->uc_mcontext->__ss.__x[2],
(unsigned long) uc->uc_mcontext->__ss.__x[3],
(unsigned long) uc->uc_mcontext->__ss.__x[4],
(unsigned long) uc->uc_mcontext->__ss.__x[5],
(unsigned long) uc->uc_mcontext->__ss.__x[6],
(unsigned long) uc->uc_mcontext->__ss.__x[7],
(unsigned long) uc->uc_mcontext->__ss.__x[8],
(unsigned long) uc->uc_mcontext->__ss.__x[9],
(unsigned long) uc->uc_mcontext->__ss.__x[10],
(unsigned long) uc->uc_mcontext->__ss.__x[11],
(unsigned long) uc->uc_mcontext->__ss.__x[12],
(unsigned long) uc->uc_mcontext->__ss.__x[13],
(unsigned long) uc->uc_mcontext->__ss.__x[14],
(unsigned long) uc->uc_mcontext->__ss.__x[15],
(unsigned long) uc->uc_mcontext->__ss.__x[16],
(unsigned long) uc->uc_mcontext->__ss.__x[17],
(unsigned long) uc->uc_mcontext->__ss.__x[18],
(unsigned long) uc->uc_mcontext->__ss.__x[19],
(unsigned long) uc->uc_mcontext->__ss.__x[20],
(unsigned long) uc->uc_mcontext->__ss.__x[21],
(unsigned long) uc->uc_mcontext->__ss.__x[22],
(unsigned long) uc->uc_mcontext->__ss.__x[23],
(unsigned long) uc->uc_mcontext->__ss.__x[24],
(unsigned long) uc->uc_mcontext->__ss.__x[25],
(unsigned long) uc->uc_mcontext->__ss.__x[26],
(unsigned long) uc->uc_mcontext->__ss.__x[27],
(unsigned long) uc->uc_mcontext->__ss.__x[28],
(unsigned long) arm_thread_state64_get_fp(uc->uc_mcontext->__ss),
(unsigned long) arm_thread_state64_get_lr(uc->uc_mcontext->__ss),
(unsigned long) arm_thread_state64_get_sp(uc->uc_mcontext->__ss),
(unsigned long) arm_thread_state64_get_pc(uc->uc_mcontext->__ss),
(unsigned long) uc->uc_mcontext->__ss.__cpsr
);
logStackContent((void**) arm_thread_state64_get_sp(uc->uc_mcontext->__ss));
#endif #endif
/* Linux */ /* Linux */
#elif defined(__linux__) #elif defined(__linux__)

View File

@ -355,7 +355,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
sdsele = (sds)ln->value; sdsele = (sds)ln->value;
if ((newsds = activeDefragSds(sdsele))) { if ((newsds = activeDefragSds(sdsele))) {
/* When defragging an sds value, we need to update the dict key */ /* When defragging an sds value, we need to update the dict key */
uint64_t hash = dictGetHash(d, sdsele); uint64_t hash = dictGetHash(d, newsds);
replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged); replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
ln->value = newsds; ln->value = newsds;
defragged++; defragged++;
@ -683,6 +683,7 @@ int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime,
/* if cursor is non-zero, we seek to the static 'last' */ /* if cursor is non-zero, we seek to the static 'last' */
if (!raxSeek(&ri,">", last, sizeof(last))) { if (!raxSeek(&ri,">", last, sizeof(last))) {
*cursor = 0; *cursor = 0;
raxStop(&ri);
return 0; return 0;
} }
/* assign the iterator node callback after the seek, so that the /* assign the iterator node callback after the seek, so that the

View File

@ -575,6 +575,16 @@ void flushSlaveKeysWithExpireList(void) {
} }
} }
int checkAlreadyExpired(long long when) {
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
* should never be executed as a DEL when load the AOF or in the context
* of a slave instance.
*
* Instead we add the already expired key to the database with expire time
* (possibly in the past) and wait for an explicit DEL from the master. */
return (when <= mstime() && !g_pserver->loading && (!listLength(g_pserver->masters) || g_pserver->fActiveReplica));
}
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Expires Commands * Expires Commands
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
@ -602,13 +612,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
return; return;
} }
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past if (checkAlreadyExpired(when)) {
* should never be executed as a DEL when load the AOF or in the context
* of a replica instance.
*
* Instead we take the other branch of the IF statement setting an expire
* (possibly in the past) and wait for an explicit DEL from the master. */
if (when <= mstime() && !g_pserver->loading && (!listLength(g_pserver->masters) || g_pserver->fActiveReplica)) {
robj *aux; robj *aux;
int deleted = g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) : int deleted = g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
@ -715,6 +719,7 @@ void persistCommand(client *c) {
if (lookupKeyWrite(c->db,c->argv[1])) { if (lookupKeyWrite(c->db,c->argv[1])) {
if (c->argc == 2) { if (c->argc == 2) {
if (removeExpire(c->db,c->argv[1])) { if (removeExpire(c->db,c->argv[1])) {
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id); notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
addReply(c,shared.cone); addReply(c,shared.cone);
g_pserver->dirty++; g_pserver->dirty++;
@ -723,6 +728,7 @@ void persistCommand(client *c) {
} }
} else if (c->argc == 3) { } else if (c->argc == 3) {
if (removeSubkeyExpire(c->db, c->argv[1], c->argv[2])) { if (removeSubkeyExpire(c->db, c->argv[1], c->argv[2])) {
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id); notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
addReply(c,shared.cone); addReply(c,shared.cone);
g_pserver->dirty++; g_pserver->dirty++;

View File

@ -1,4 +1,4 @@
/* Automatically generated by generate-command-help.rb, do not edit. */ /* Automatically generated by ./utils/generate-command-help.rb, do not edit. */
#ifndef __REDIS_HELP_H #ifndef __REDIS_HELP_H
#define __REDIS_HELP_H #define __REDIS_HELP_H
@ -44,6 +44,16 @@ struct commandHelp {
"Generate a pseudorandom secure password to use for ACL users", "Generate a pseudorandom secure password to use for ACL users",
9, 9,
"6.0.0" }, "6.0.0" },
{ "ACL GETUSER",
"username",
"Get the rules for a specific ACL user",
9,
"6.0.0" },
{ "ACL HELP",
"-",
"Show helpful text about the different subcommands",
9,
"6.0.0" },
{ "ACL LIST", { "ACL LIST",
"-", "-",
"List the current ACL rules in ACL config file format", "List the current ACL rules in ACL config file format",
@ -65,7 +75,7 @@ struct commandHelp {
9, 9,
"6.0.0" }, "6.0.0" },
{ "ACL SETUSER", { "ACL SETUSER",
"rule [rule ...]", "username [rule [rule ...]]",
"Modify or create the rules for a specific ACL user", "Modify or create the rules for a specific ACL user",
9, 9,
"6.0.0" }, "6.0.0" },
@ -165,7 +175,7 @@ struct commandHelp {
8, 8,
"5.0.0" }, "5.0.0" },
{ "CLIENT KILL", { "CLIENT KILL",
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]", "[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [USER username] [ADDR ip:port] [SKIPME yes/no]",
"Kill the connection of a client", "Kill the connection of a client",
8, 8,
"2.4.0" }, "2.4.0" },
@ -183,14 +193,14 @@ struct commandHelp {
"ON|OFF|SKIP", "ON|OFF|SKIP",
"Instruct the server whether to reply to commands", "Instruct the server whether to reply to commands",
8, 8,
"3.2" }, "3.2.0" },
{ "CLIENT SETNAME", { "CLIENT SETNAME",
"connection-name", "connection-name",
"Set the current connection name", "Set the current connection name",
8, 8,
"2.6.9" }, "2.6.9" },
{ "CLIENT TRACKING", { "CLIENT TRACKING",
"ON|OFF [REDIRECT client-id] [PREFIX prefix] [BCAST] [OPTIN] [OPTOUT] [NOLOOP]", "ON|OFF [REDIRECT client-id] [PREFIX prefix [PREFIX prefix ...]] [BCAST] [OPTIN] [OPTOUT] [NOLOOP]",
"Enable or disable server assisted client side caching support", "Enable or disable server assisted client side caching support",
8, 8,
"6.0.0" }, "6.0.0" },
@ -626,7 +636,7 @@ struct commandHelp {
9, 9,
"2.8.13" }, "2.8.13" },
{ "LATENCY RESET", { "LATENCY RESET",
"[event]", "[event [event ...]]",
"Reset latency data for one or more events.", "Reset latency data for one or more events.",
9, 9,
"2.8.13" }, "2.8.13" },
@ -655,6 +665,11 @@ struct commandHelp {
"Remove and get the first element in a list", "Remove and get the first element in a list",
2, 2,
"1.0.0" }, "1.0.0" },
{ "LPOS",
"key element [RANK rank] [COUNT num-matches] [MAXLEN len]",
"Return the index of matching elements on a list",
2,
"6.0.6" },
{ "LPUSH", { "LPUSH",
"key element [element ...]", "key element [element ...]",
"Prepend one or multiple elements to a list", "Prepend one or multiple elements to a list",
@ -966,7 +981,7 @@ struct commandHelp {
8, 8,
"1.0.0" }, "1.0.0" },
{ "SET", { "SET",
"key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]", "key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX]",
"Set the string value of a key", "Set the string value of a key",
1, 1,
"1.0.0" }, "1.0.0" },

View File

@ -71,7 +71,7 @@ int THPIsEnabled(void) {
return 0; return 0;
} }
fclose(fp); fclose(fp);
return (strstr(buf,"[never]") == NULL) ? 1 : 0; return (strstr(buf,"[always]") != NULL) ? 1 : 0;
} }
#endif #endif
@ -621,7 +621,7 @@ NULL
resets += latencyResetEvent(szFromObj(c->argv[j])); resets += latencyResetEvent(szFromObj(c->argv[j]));
addReplyLongLong(c,resets); addReplyLongLong(c,resets);
} }
} else if (!strcasecmp(szFromObj(c->argv[1]),"help") && c->argc >= 2) { } else if (!strcasecmp(szFromObj(c->argv[1]),"help") && c->argc == 2) {
addReplyHelp(c, help); addReplyHelp(c, help);
} else { } else {
addReplySubcommandSyntaxError(c); addReplySubcommandSyntaxError(c);

View File

@ -41,6 +41,30 @@ size_t lazyfreeGetFreeEffort(robj *obj) {
} else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) { } else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) {
dict *ht = (dict*)ptrFromObj(obj); dict *ht = (dict*)ptrFromObj(obj);
return dictSize(ht); return dictSize(ht);
} else if (obj->type == OBJ_STREAM) {
size_t effort = 0;
stream *s = (stream*)ptrFromObj(obj);
/* Make a best effort estimate to maintain constant runtime. Every macro
* node in the Stream is one allocation. */
effort += s->prax->numnodes;
/* Every consumer group is an allocation and so are the entries in its
* PEL. We use size of the first group's PEL as an estimate for all
* others. */
if (s->cgroups) {
raxIterator ri;
streamCG *cg;
raxStart(&ri,s->cgroups);
raxSeek(&ri,"^",NULL,0);
/* There must be at least one group so the following should always
* work. */
serverAssert(raxNext(&ri));
cg = (streamCG*)ri.data;
effort += raxSize(s->cgroups)*(1+raxSize(cg->pel));
raxStop(&ri);
}
return effort;
} else { } else {
return 1; /* Everything else is a single allocation. */ return 1; /* Everything else is a single allocation. */
} }

View File

@ -773,13 +773,13 @@ unsigned char *lpSeek(unsigned char *lp, long index) {
* is past the half of the listpack. */ * is past the half of the listpack. */
if (index > numele/2) { if (index > numele/2) {
forward = 0; forward = 0;
/* Left to right scanning always expects a negative index. Convert /* Right to left scanning always expects a negative index. Convert
* our index to negative form. */ * our index to negative form. */
index -= numele; index -= numele;
} }
} else { } else {
/* If the listpack length is unspecified, for negative indexes we /* If the listpack length is unspecified, for negative indexes we
* want to always scan left-to-right. */ * want to always scan right-to-left. */
if (index < 0) forward = 0; if (index < 0) forward = 0;
} }

View File

@ -623,6 +623,8 @@ void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
redisOpArrayFree(&g_pserver->also_propagate); redisOpArrayFree(&g_pserver->also_propagate);
/* Restore the previous oparray in case of nexted use of the API. */ /* Restore the previous oparray in case of nexted use of the API. */
g_pserver->also_propagate = ctx->saved_oparray; g_pserver->also_propagate = ctx->saved_oparray;
/* We're done with saved_oparray, let's invalidate it. */
redisOpArrayInit(&ctx->saved_oparray);
} }
} }
@ -1152,6 +1154,65 @@ void RM_RetainString(RedisModuleCtx *ctx, RedisModuleString *str) {
} }
} }
/**
* This function can be used instead of RedisModule_RetainString().
* The main difference between the two is that this function will always
* succeed, whereas RedisModule_RetainString() may fail because of an
* assertion.
*
* The function returns a pointer to RedisModuleString, which is owned
* by the caller. It requires a call to RedisModule_FreeString() to free
* the string when automatic memory management is disabled for the context.
* When automatic memory management is enabled, you can either call
* RedisModule_FreeString() or let the automation free it.
*
* This function is more efficient than RedisModule_CreateStringFromString()
* because whenever possible, it avoids copying the underlying
* RedisModuleString. The disadvantage of using this function is that it
* might not be possible to use RedisModule_StringAppendBuffer() on the
* returned RedisModuleString.
*
* It is possible to call this function with a NULL context.
 */
RedisModuleString* RM_HoldString(RedisModuleCtx *ctx, RedisModuleString *str) {
if (str->getrefcount(std::memory_order_relaxed) == OBJ_STATIC_REFCOUNT) {
return RM_CreateStringFromString(ctx, str);
}
incrRefCount(str);
if (ctx != NULL) {
/*
* Put the str in the auto memory management of the ctx.
         * It might already be there, in this case, the ref count will
         * be 2 and we will decrease the ref count twice and free the
         * object in the auto memory free function.
         *
         * Why we can not do the same trick of just remove the object
         * from the auto memory (like in RM_RetainString)?
         * This code shows the issue:
         *
         * RM_AutoMemory(ctx);
         * str1 = RM_CreateString(ctx, "test", 4);
         * str2 = RM_HoldString(ctx, str1);
         * RM_FreeString(str1);
         * RM_FreeString(str2);
         *
         * If after the RM_HoldString we would just remove the string from
         * the auto memory, this example will cause access to a freed memory
         * on 'RM_FreeString(str2);' because the String will be free
         * on 'RM_FreeString(str1);'.
         *
         * So it's safer to just increase the ref count
         * and add the String to auto memory again.
         *
         * The limitation is that it is not possible to use RedisModule_StringAppendBuffer
         * on the String.
*/
autoMemoryAdd(ctx,REDISMODULE_AM_STRING,str);
}
return str;
}
/* Given a string module object, this function returns the string pointer /* Given a string module object, this function returns the string pointer
* and length of the string. The returned pointer and length should only * and length of the string. The returned pointer and length should only
* be used for read only accesses and never modified. */ * be used for read only accesses and never modified. */
@ -1754,6 +1815,8 @@ int modulePopulateClientInfoStructure(void *ci, client *client, int structver) {
ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_TRACKING; ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_TRACKING;
if (client->flags & CLIENT_BLOCKED) if (client->flags & CLIENT_BLOCKED)
ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_BLOCKED; ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_BLOCKED;
if (connGetType(client->conn) == CONN_TYPE_TLS)
ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_SSL;
int port; int port;
connPeerToString(client->conn,ci1->addr,sizeof(ci1->addr),&port); connPeerToString(client->conn,ci1->addr,sizeof(ci1->addr),&port);
@ -2114,7 +2177,7 @@ int RM_KeyType(RedisModuleKey *key) {
case OBJ_HASH: return REDISMODULE_KEYTYPE_HASH; case OBJ_HASH: return REDISMODULE_KEYTYPE_HASH;
case OBJ_MODULE: return REDISMODULE_KEYTYPE_MODULE; case OBJ_MODULE: return REDISMODULE_KEYTYPE_MODULE;
case OBJ_STREAM: return REDISMODULE_KEYTYPE_STREAM; case OBJ_STREAM: return REDISMODULE_KEYTYPE_STREAM;
default: return 0; default: return REDISMODULE_KEYTYPE_EMPTY;
} }
} }
@ -2172,7 +2235,8 @@ mstime_t RM_GetExpire(RedisModuleKey *key) {
if (pexpire != nullptr) if (pexpire != nullptr)
pexpire->FGetPrimaryExpire(&expire); pexpire->FGetPrimaryExpire(&expire);
if (expire == -1 || key->value == NULL) return -1; if (expire == -1 || key->value == NULL)
return REDISMODULE_NO_EXPIRE;
expire -= mstime(); expire -= mstime();
return expire >= 0 ? expire : 0; return expire >= 0 ? expire : 0;
} }
@ -3263,8 +3327,11 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
argv[argc++] = createStringObject(cstr,strlen(cstr)); argv[argc++] = createStringObject(cstr,strlen(cstr));
} else if (*p == 's') { } else if (*p == 's') {
robj *obj = (robj*)va_arg(ap,void*); robj *obj = (robj*)va_arg(ap,void*);
if (obj->getrefcount(std::memory_order_relaxed) == OBJ_STATIC_REFCOUNT)
obj = createStringObject(szFromObj(obj),sdslen(szFromObj(obj)));
else
incrRefCount(obj);
argv[argc++] = obj; argv[argc++] = obj;
incrRefCount(obj);
} else if (*p == 'b') { } else if (*p == 'b') {
char *buf = va_arg(ap,char*); char *buf = va_arg(ap,char*);
size_t len = va_arg(ap,size_t); size_t len = va_arg(ap,size_t);
@ -3310,6 +3377,23 @@ fmterr:
} }
/* Exported API to call any Redis command from modules. /* Exported API to call any Redis command from modules.
*
* * **cmdname**: The Redis command to call.
* * **fmt**: A format specifier string for the command's arguments. Each
* of the arguments should be specified by a valid type specification:
* b The argument is a buffer and is immediately followed by another
* argument that is the buffer's length.
* c The argument is a pointer to a plain C string (null-terminated).
* l The argument is long long integer.
* s The argument is a RedisModuleString.
* v The argument(s) is a vector of RedisModuleString.
*
* The format specifier can also include modifiers:
* ! Sends the Redis command and its arguments to replicas and AOF.
* A Suppress AOF propagation, send only to replicas (requires `!`).
* R Suppress replicas propagation, send only to AOF (requires `!`).
* * **...**: The actual arguments to the Redis command.
*
* On success a RedisModuleCallReply object is returned, otherwise * On success a RedisModuleCallReply object is returned, otherwise
* NULL is returned and errno is set to the following values: * NULL is returned and errno is set to the following values:
* *
@ -3321,6 +3405,14 @@ fmterr:
* in a readonly state. * in a readonly state.
* ENETDOWN: operation in Cluster instance when cluster is down. * ENETDOWN: operation in Cluster instance when cluster is down.
* *
* Example code fragment:
*
* reply = RedisModule_Call(ctx,"INCRBY","sc",argv[1],"10");
* if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) {
* long long myval = RedisModule_CallReplyInteger(reply);
* // Do something with myval.
* }
*
* This API is documented here: https://redis.io/topics/modules-intro * This API is documented here: https://redis.io/topics/modules-intro
*/ */
RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) { RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
@ -4424,6 +4516,7 @@ void unblockClientFromModule(client *c) {
* Even when blocking on keys, RM_UnblockClient() can be called however, but * Even when blocking on keys, RM_UnblockClient() can be called however, but
* in that case the privdata argument is disregarded, because we pass the * in that case the privdata argument is disregarded, because we pass the
* reply callback the privdata that is set here while blocking. * reply callback the privdata that is set here while blocking.
*
*/ */
RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) { RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) {
client *c = ctx->client; client *c = ctx->client;
@ -4516,6 +4609,14 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
* Note: RedisModule_UnblockClient should be called for every blocked client, * Note: RedisModule_UnblockClient should be called for every blocked client,
* even if client was killed, timed-out or disconnected. Failing to do so * even if client was killed, timed-out or disconnected. Failing to do so
* will result in memory leaks. * will result in memory leaks.
*
* There are some cases where RedisModule_BlockClient() cannot be used:
*
* 1. If the client is a Lua script.
* 2. If the client is executing a MULTI block.
*
* In these cases, a call to RedisModule_BlockClient() will **not** block the
* client, but instead produce a specific error reply.
*/ */
RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) { RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) {
return moduleBlockClient(ctx,reply_callback,timeout_callback,free_privdata,timeout_ms, NULL,0,NULL); return moduleBlockClient(ctx,reply_callback,timeout_callback,free_privdata,timeout_ms, NULL,0,NULL);
@ -4908,6 +5009,23 @@ void RM_ThreadSafeContextLock(RedisModuleCtx *ctx) {
moduleAcquireGIL(FALSE /*fServerThread*/); moduleAcquireGIL(FALSE /*fServerThread*/);
} }
/* Similar to RM_ThreadSafeContextLock but this function
* would not block if the server lock is already acquired.
*
* If successful (lock acquired) REDISMODULE_OK is returned,
* otherwise REDISMODULE_ERR is returned and errno is set
* accordingly. */
int RM_ThreadSafeContextTryLock(RedisModuleCtx *ctx) {
UNUSED(ctx);
int res = moduleTryAcquireGIL(false /*fServerThread*/);
if(res != 0) {
errno = res;
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
/* Release the server lock after a thread safe API call was executed. */ /* Release the server lock after a thread safe API call was executed. */
void RM_ThreadSafeContextUnlock(RedisModuleCtx *ctx) { void RM_ThreadSafeContextUnlock(RedisModuleCtx *ctx) {
UNUSED(ctx); UNUSED(ctx);
@ -4942,6 +5060,31 @@ void moduleAcquireGIL(int fServerThread) {
} }
} }
int moduleTryAcquireGIL(bool fServerThread) {
std::unique_lock<std::mutex> lock(s_mutex, std::defer_lock);
if (!lock.try_lock())
return 1;
int *pcheck = fServerThread ? &s_cAcquisitionsModule : &s_cAcquisitionsServer;
if (FModuleCallBackLock(fServerThread)) {
return 0;
}
if (*pcheck > 0)
return 1;
if (fServerThread)
{
++s_cAcquisitionsServer;
}
else
{
++s_cAcquisitionsModule;
fModuleGILWlocked++;
}
return 0;
}
void moduleReleaseGIL(int fServerThread) { void moduleReleaseGIL(int fServerThread) {
std::unique_lock<std::mutex> lock(s_mutex); std::unique_lock<std::mutex> lock(s_mutex);
@ -4993,6 +5136,11 @@ int moduleGILAcquiredByModule(void) {
* - REDISMODULE_NOTIFY_STREAM: Stream events * - REDISMODULE_NOTIFY_STREAM: Stream events
* - REDISMODULE_NOTIFY_KEYMISS: Key-miss events * - REDISMODULE_NOTIFY_KEYMISS: Key-miss events
* - REDISMODULE_NOTIFY_ALL: All events (Excluding REDISMODULE_NOTIFY_KEYMISS) * - REDISMODULE_NOTIFY_ALL: All events (Excluding REDISMODULE_NOTIFY_KEYMISS)
* - REDISMODULE_NOTIFY_LOADED: A special notification available only for modules,
* indicates that the key was loaded from persistence.
* Notice, when this event fires, the given key
* can not be retained, use RM_CreateStringFromString
* instead.
* *
* We do not distinguish between key events and keyspace events, and it is up * We do not distinguish between key events and keyspace events, and it is up
* to the module to filter the actions taken based on the key. * to the module to filter the actions taken based on the key.
@ -6862,7 +7010,7 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
int pos = 0; int pos = 0;
int64_t ll; int64_t ll;
while(intsetGet((intset*)ptrFromObj(o),pos++,&ll)) { while(intsetGet((intset*)ptrFromObj(o),pos++,&ll)) {
robj *field = createStringObjectFromLongLong(ll); robj *field = createObject(OBJ_STRING,sdsfromlonglong(ll));
fn(key, field, NULL, privdata); fn(key, field, NULL, privdata);
decrRefCount(field); decrRefCount(field);
} }
@ -6878,12 +7026,12 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
ziplistGet(p,&vstr,&vlen,&vll); ziplistGet(p,&vstr,&vlen,&vll);
robj *field = (vstr != NULL) ? robj *field = (vstr != NULL) ?
createStringObject((char*)vstr,vlen) : createStringObject((char*)vstr,vlen) :
createStringObjectFromLongLong(vll); createObject(OBJ_STRING,sdsfromlonglong(vll));
p = ziplistNext((unsigned char*)ptrFromObj(o),p); p = ziplistNext((unsigned char*)ptrFromObj(o),p);
ziplistGet(p,&vstr,&vlen,&vll); ziplistGet(p,&vstr,&vlen,&vll);
robj *value = (vstr != NULL) ? robj *value = (vstr != NULL) ?
createStringObject((char*)vstr,vlen) : createStringObject((char*)vstr,vlen) :
createStringObjectFromLongLong(vll); createObject(OBJ_STRING,sdsfromlonglong(vll));
fn(key, field, value, privdata); fn(key, field, value, privdata);
p = ziplistNext((unsigned char*)ptrFromObj(o),p); p = ziplistNext((unsigned char*)ptrFromObj(o),p);
decrRefCount(field); decrRefCount(field);
@ -7338,7 +7486,7 @@ void processModuleLoadingProgressEvent(int is_aof) {
int progress = -1; int progress = -1;
if (g_pserver->loading_total_bytes) if (g_pserver->loading_total_bytes)
progress = (g_pserver->loading_total_bytes<<10) / g_pserver->loading_total_bytes; progress = (g_pserver->loading_total_bytes<<10) / g_pserver->loading_total_bytes;
RedisModuleFlushInfoV1 fi = {REDISMODULE_LOADING_PROGRESS_VERSION, RedisModuleLoadingProgressV1 fi = {REDISMODULE_LOADING_PROGRESS_VERSION,
g_pserver->hz, g_pserver->hz,
progress}; progress};
moduleFireServerEvent(REDISMODULE_EVENT_LOADING_PROGRESS, moduleFireServerEvent(REDISMODULE_EVENT_LOADING_PROGRESS,
@ -7954,6 +8102,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(LatencyAddSample); REGISTER_API(LatencyAddSample);
REGISTER_API(StringAppendBuffer); REGISTER_API(StringAppendBuffer);
REGISTER_API(RetainString); REGISTER_API(RetainString);
REGISTER_API(HoldString);
REGISTER_API(StringCompare); REGISTER_API(StringCompare);
REGISTER_API(GetContextFromIO); REGISTER_API(GetContextFromIO);
REGISTER_API(GetKeyNameFromIO); REGISTER_API(GetKeyNameFromIO);
@ -7968,6 +8117,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(GetThreadSafeContext); REGISTER_API(GetThreadSafeContext);
REGISTER_API(FreeThreadSafeContext); REGISTER_API(FreeThreadSafeContext);
REGISTER_API(ThreadSafeContextLock); REGISTER_API(ThreadSafeContextLock);
REGISTER_API(ThreadSafeContextTryLock);
REGISTER_API(ThreadSafeContextUnlock); REGISTER_API(ThreadSafeContextUnlock);
REGISTER_API(DigestAddStringBuffer); REGISTER_API(DigestAddStringBuffer);
REGISTER_API(DigestAddLongLong); REGISTER_API(DigestAddLongLong);

View File

@ -40,7 +40,7 @@
/* Timer callback. */ /* Timer callback. */
void timerHandler(RedisModuleCtx *ctx, void *data) { void timerHandler(RedisModuleCtx *ctx, void *data) {
REDISMODULE_NOT_USED(ctx); REDISMODULE_NOT_USED(ctx);
printf("Fired %s!\n", (const char *) data); printf("Fired %s!\n", (const char *)data);
RedisModule_Free(data); RedisModule_Free(data);
} }

View File

@ -37,6 +37,7 @@ void initClientMultiState(client *c) {
c->mstate.commands = NULL; c->mstate.commands = NULL;
c->mstate.count = 0; c->mstate.count = 0;
c->mstate.cmd_flags = 0; c->mstate.cmd_flags = 0;
c->mstate.cmd_inv_flags = 0;
} }
/* Release all the resources associated with MULTI/EXEC state */ /* Release all the resources associated with MULTI/EXEC state */
@ -77,6 +78,7 @@ void queueMultiCommand(client *c) {
incrRefCount(mc->argv[j]); incrRefCount(mc->argv[j]);
c->mstate.count++; c->mstate.count++;
c->mstate.cmd_flags |= c->cmd->flags; c->mstate.cmd_flags |= c->cmd->flags;
c->mstate.cmd_inv_flags |= ~c->cmd->flags;
} }
void discardTransaction(client *c) { void discardTransaction(client *c) {
@ -125,6 +127,24 @@ void execCommandPropagateExec(client *c) {
PROPAGATE_AOF|PROPAGATE_REPL); PROPAGATE_AOF|PROPAGATE_REPL);
} }
/* Aborts a transaction, with a specific error message.
* The transaction is always aboarted with -EXECABORT so that the client knows
* the server exited the multi state, but the actual reason for the abort is
* included too.
* Note: 'error' may or may not end with \r\n. see addReplyErrorFormat. */
void execCommandAbort(client *c, sds error) {
discardTransaction(c);
if (error[0] == '-') error++;
addReplyErrorFormat(c, "-EXECABORT Transaction discarded because of: %s", error);
/* Send EXEC to clients waiting data from MONITOR. We did send a MULTI
* already, and didn't send any of the queued commands, now we'll just send
* EXEC so it is clear that the transaction is over. */
if (listLength(g_pserver->monitors) && !g_pserver->loading)
replicationFeedMonitors(c,g_pserver->monitors,c->db->id,c->argv,c->argc);
}
void execCommand(client *c) { void execCommand(client *c) {
int j; int j;
robj **orig_argv; robj **orig_argv;
@ -138,15 +158,6 @@ void execCommand(client *c) {
return; return;
} }
/* If we are in -BUSY state, flag the transaction and return the
* -BUSY error, like Redis <= 5. This is a temporary fix, may be changed
* ASAP, see issue #7353 on Github. */
if (g_pserver->lua_timedout) {
flagTransaction(c);
addReply(c, shared.slowscripterr);
return;
}
/* Check if we need to abort the EXEC because: /* Check if we need to abort the EXEC because:
* 1) Some WATCHed key was touched. * 1) Some WATCHed key was touched.
* 2) There was a previous error while queueing commands. * 2) There was a previous error while queueing commands.
@ -160,21 +171,6 @@ void execCommand(client *c) {
goto handle_monitor; goto handle_monitor;
} }
/* If there are write commands inside the transaction, and this is a read
* only replica, we want to send an error. This happens when the transaction
* was initiated when the instance was a master or a writable replica and
* then the configuration changed (for example instance was turned into
* a replica). */
if (!g_pserver->loading && listLength(g_pserver->masters) && g_pserver->repl_slave_ro &&
!(c->flags & CLIENT_MASTER) && c->mstate.cmd_flags & CMD_WRITE)
{
addReplyError(c,
"Transaction contains write commands but instance "
"is now a read-only replica. EXEC aborted.");
discardTransaction(c);
goto handle_monitor;
}
/* Exec all the queued commands */ /* Exec all the queued commands */
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */ unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
orig_argv = c->argv; orig_argv = c->argv;

View File

@ -30,6 +30,7 @@
#include "server.h" #include "server.h"
#include "atomicvar.h" #include "atomicvar.h"
#include "cluster.h"
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/uio.h> #include <sys/uio.h>
#include <math.h> #include <math.h>
@ -473,14 +474,18 @@ std::string escapeString(sds str)
* *
* If the error code is already passed in the string 's', the error * If the error code is already passed in the string 's', the error
* code provided is used, otherwise the string "-ERR " for the generic * code provided is used, otherwise the string "-ERR " for the generic
* error code is automatically added. */ * error code is automatically added.
* Note that 's' must NOT end with \r\n. */
void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync) { void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync) {
/* If the string already starts with "-..." then the error code /* If the string already starts with "-..." then the error code
* is provided by the caller. Otherwise we use "-ERR". */ * is provided by the caller. Otherwise we use "-ERR". */
if (!len || s[0] != '-') addReplyProtoCore(c,"-ERR ",5,fAsync); if (!len || s[0] != '-') addReplyProtoCore(c,"-ERR ",5,fAsync);
addReplyProtoCore(c,s,len,fAsync); addReplyProtoCore(c,s,len,fAsync);
addReplyProtoCore(c,"\r\n",2,fAsync); addReplyProtoCore(c,"\r\n",2,fAsync);
}
/* Do some actions after an error reply was sent (Log if needed, updates stats, etc.) */
void afterErrorReply(client *c, const char *s, size_t len) {
/* Sometimes it could be normal that a replica replies to a master with /* Sometimes it could be normal that a replica replies to a master with
* an error and this function gets called. Actually the error will never * an error and this function gets called. Actually the error will never
* be sent because addReply*() against master clients has no effect... * be sent because addReply*() against master clients has no effect...
@ -506,10 +511,11 @@ void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync)
from = "master"; from = "master";
} }
if (len > 4096) len = 4096;
const char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>"; const char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error " serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
"to its %s: '%s' after processing the command " "to its %s: '%.*s' after processing the command "
"'%s'", from, to, s, cmdname); "'%s'", from, to, (int)len, s, cmdname);
if (ctype == CLIENT_TYPE_MASTER && g_pserver->repl_backlog && if (ctype == CLIENT_TYPE_MASTER && g_pserver->repl_backlog &&
g_pserver->repl_backlog_histlen > 0) g_pserver->repl_backlog_histlen > 0)
{ {
@ -524,27 +530,43 @@ void addReplyErrorLength(client *c, const char *s, size_t len)
addReplyErrorLengthCore(c, s, len, false); addReplyErrorLengthCore(c, s, len, false);
} }
/* The 'err' object is expected to start with -ERRORCODE and end with \r\n.
* Unlike addReplyErrorSds and others alike which rely on addReplyErrorLength. */
void addReplyErrorObject(client *c, robj *err) {
addReply(c, err);
afterErrorReply(c, szFromObj(err), sdslen(szFromObj(err))-2); /* Ignore trailing \r\n */
}
/* See addReplyErrorLength for expectations from the input string. */
void addReplyError(client *c, const char *err) { void addReplyError(client *c, const char *err) {
addReplyErrorLengthCore(c,err,strlen(err), false); addReplyErrorLengthCore(c,err,strlen(err), false);
} }
void addReplyErrorAsync(client *c, const char *err) { void addReplyErrorAsync(client *c, const char *err) {
addReplyErrorLengthCore(c, err, strlen(err), true); addReplyErrorLengthCore(c, err, strlen(err), true);
afterErrorReply(c,err,strlen(err));
} }
/* See addReplyErrorLength for expectations from the input string. */
void addReplyErrorSds(client *c, sds err) {
addReplyErrorLength(c,err,sdslen(err));
afterErrorReply(c,err,sdslen(err));
}
/* See addReplyErrorLength for expectations from the formatted string.
* The formatted string is safe to contain \r and \n anywhere. */
void addReplyErrorFormat(client *c, const char *fmt, ...) { void addReplyErrorFormat(client *c, const char *fmt, ...) {
size_t l, j;
va_list ap; va_list ap;
va_start(ap,fmt); va_start(ap,fmt);
sds s = sdscatvprintf(sdsempty(),fmt,ap); sds s = sdscatvprintf(sdsempty(),fmt,ap);
va_end(ap); va_end(ap);
/* Make sure there are no newlines in the string, otherwise invalid protocol /* Trim any newlines at the end (ones will be added by addReplyErrorLength) */
* is emitted. */ s = sdstrim(s, "\r\n");
l = sdslen(s); /* Make sure there are no newlines in the middle of the string, otherwise
for (j = 0; j < l; j++) { * invalid protocol is emitted. */
if (s[j] == '\r' || s[j] == '\n') s[j] = ' '; s = sdsmapchars(s, "\r\n", " ", 2);
}
addReplyErrorLength(c,s,sdslen(s)); addReplyErrorLength(c,s,sdslen(s));
afterErrorReply(c,s,sdslen(s));
sdsfree(s); sdsfree(s);
} }
@ -616,6 +638,7 @@ void *addReplyDeferredLenAsync(client *c) {
/* Populate the length object and try gluing it to the next chunk. */ /* Populate the length object and try gluing it to the next chunk. */
void setDeferredAggregateLen(client *c, void *node, long length, char prefix) { void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
serverAssert(length >= 0);
listNode *ln = (listNode*)node; listNode *ln = (listNode*)node;
clientReplyBlock *next; clientReplyBlock *next;
char lenstr[128]; char lenstr[128];
@ -1205,21 +1228,38 @@ void clientAcceptHandler(connection *conn) {
#define MAX_ACCEPTS_PER_CALL 1000 #define MAX_ACCEPTS_PER_CALL 1000
static void acceptCommonHandler(connection *conn, int flags, char *ip, int iel) { static void acceptCommonHandler(connection *conn, int flags, char *ip, int iel) {
client *c; client *c;
char conninfo[100];
UNUSED(ip); UNUSED(ip);
AeLocker locker; AeLocker locker;
locker.arm(nullptr); locker.arm(nullptr);
/* Admission control will happen before a client is created and connAccept() if (connGetState(conn) != CONN_STATE_ACCEPTING) {
serverLog(LL_VERBOSE,
"Accepted client connection in error state: %s (conn: %s)",
connGetLastError(conn),
connGetInfo(conn, conninfo, sizeof(conninfo)));
connClose(conn);
return;
}
/* Limit the number of connections we take at the same time.
*
* Admission control will happen before a client is created and connAccept()
* called, because we don't want to even start transport-level negotiation * called, because we don't want to even start transport-level negotiation
* if rejected. * if rejected. */
*/ if (listLength(g_pserver->clients) + getClusterConnectionsCount()
if (listLength(g_pserver->clients) >= g_pserver->maxclients) { >= g_pserver->maxclients)
const char *err = "-ERR max number of clients reached\r\n"; {
const char *err;
if (g_pserver->cluster_enabled)
err = "-ERR max number of clients + cluster "
"connections reached\r\n";
else
err = "-ERR max number of clients reached\r\n";
/* That's a best effort error message, don't check write errors. /* That's a best effort error message, don't check write errors.
* Note that for TLS connections, no handshake was done yet so nothing is written * Note that for TLS connections, no handshake was done yet so nothing
* and the connection will just drop. * is written and the connection will just drop. */
*/
if (connWrite(conn,err,strlen(err)) == -1) { if (connWrite(conn,err,strlen(err)) == -1) {
/* Nothing to do, Just to avoid the warning... */ /* Nothing to do, Just to avoid the warning... */
} }
@ -1230,7 +1270,6 @@ static void acceptCommonHandler(connection *conn, int flags, char *ip, int iel)
/* Create connection and client */ /* Create connection and client */
if ((c = createClient(conn, iel)) == NULL) { if ((c = createClient(conn, iel)) == NULL) {
char conninfo[100];
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Error registering fd event for the new client: %s (conn: %s)", "Error registering fd event for the new client: %s (conn: %s)",
connGetLastError(conn), connGetLastError(conn),
@ -1680,6 +1719,9 @@ client *lookupClientByID(uint64_t id) {
* set to 0. So when handler_installed is set to 0 the function must be * set to 0. So when handler_installed is set to 0 the function must be
* thread safe. */ * thread safe. */
int writeToClient(client *c, int handler_installed) { int writeToClient(client *c, int handler_installed) {
/* Update total number of writes on server */
g_pserver->stat_total_writes_processed.fetch_add(1, std::memory_order_relaxed);
ssize_t nwritten = 0, totwritten = 0; ssize_t nwritten = 0, totwritten = 0;
clientReplyBlock *o; clientReplyBlock *o;
AssertCorrectThread(c); AssertCorrectThread(c);
@ -2192,7 +2234,8 @@ int processMultibulkBuffer(client *c) {
} }
ok = string2ll(c->querybuf+c->qb_pos+1,newline-(c->querybuf+c->qb_pos+1),&ll); ok = string2ll(c->querybuf+c->qb_pos+1,newline-(c->querybuf+c->qb_pos+1),&ll);
if (!ok || ll < 0 || ll > g_pserver->proto_max_bulk_len) { if (!ok || ll < 0 ||
(!(c->flags & CLIENT_MASTER) && ll > g_pserver->proto_max_bulk_len)) {
addReplyError(c,"Protocol error: invalid bulk length"); addReplyError(c,"Protocol error: invalid bulk length");
setProtocolError("invalid bulk length",c); setProtocolError("invalid bulk length",c);
return C_ERR; return C_ERR;
@ -2402,6 +2445,9 @@ void readQueryFromClient(connection *conn) {
if (!lock.try_lock()) if (!lock.try_lock())
return; // Process something else while we wait return; // Process something else while we wait
/* Update total number of reads on server */
g_pserver->stat_total_reads_processed.fetch_add(1, std::memory_order_relaxed);
readlen = PROTO_IOBUF_LEN; readlen = PROTO_IOBUF_LEN;
/* If this is a multi bulk request, and we are processing a bulk reply /* If this is a multi bulk request, and we are processing a bulk reply
* that is large enough, try to maximize the probability that the query * that is large enough, try to maximize the probability that the query
@ -2670,6 +2716,7 @@ void clientCommand(client *c) {
"SETNAME <name> -- Assign the name <name> to the current connection.", "SETNAME <name> -- Assign the name <name> to the current connection.",
"UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.", "UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
"TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first] [PREFIX second] [OPTIN] [OPTOUT]... -- Enable client keys tracking for client side caching.", "TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first] [PREFIX second] [OPTIN] [OPTOUT]... -- Enable client keys tracking for client side caching.",
"CACHING (yes|no) -- Enable/Disable tracking of the keys for next command in OPTIN/OPTOUT mode.",
"GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.", "GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.",
NULL NULL
}; };

View File

@ -487,8 +487,8 @@ static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode
if (h->iscompr) j = 0; /* Compressed node only child is at index 0. */ if (h->iscompr) j = 0; /* Compressed node only child is at index 0. */
memcpy(&h,children+j,sizeof(h)); memcpy(&h,children+j,sizeof(h));
parentlink = children+j; parentlink = children+j;
j = 0; /* If the new node is compressed and we do not j = 0; /* If the new node is non compressed and we do not
iterate again (since i == l) set the split iterate again (since i == len) set the split
position to 0 to signal this node represents position to 0 to signal this node represents
the searched key. */ the searched key. */
} }

View File

@ -706,15 +706,23 @@ ssize_t rdbSaveStreamPEL(rio *rdb, rax *pel, int nacks) {
while(raxNext(&ri)) { while(raxNext(&ri)) {
/* We store IDs in raw form as 128 big big endian numbers, like /* We store IDs in raw form as 128 big big endian numbers, like
* they are inside the radix tree key. */ * they are inside the radix tree key. */
if ((n = rdbWriteRaw(rdb,ri.key,sizeof(streamID))) == -1) return -1; if ((n = rdbWriteRaw(rdb,ri.key,sizeof(streamID))) == -1) {
raxStop(&ri);
return -1;
}
nwritten += n; nwritten += n;
if (nacks) { if (nacks) {
streamNACK *nack = (streamNACK*)ri.data; streamNACK *nack = (streamNACK*)ri.data;
if ((n = rdbSaveMillisecondTime(rdb,nack->delivery_time)) == -1) if ((n = rdbSaveMillisecondTime(rdb,nack->delivery_time)) == -1) {
raxStop(&ri);
return -1; return -1;
}
nwritten += n; nwritten += n;
if ((n = rdbSaveLen(rdb,nack->delivery_count)) == -1) return -1; if ((n = rdbSaveLen(rdb,nack->delivery_count)) == -1) {
raxStop(&ri);
return -1;
}
nwritten += n; nwritten += n;
/* We don't save the consumer name: we'll save the pending IDs /* We don't save the consumer name: we'll save the pending IDs
* for each consumer in the consumer PEL, and resolve the consumer * for each consumer in the consumer PEL, and resolve the consumer
@ -743,20 +751,27 @@ size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
streamConsumer *consumer = (streamConsumer*)ri.data; streamConsumer *consumer = (streamConsumer*)ri.data;
/* Consumer name. */ /* Consumer name. */
if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) return -1; if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) {
raxStop(&ri);
return -1;
}
nwritten += n; nwritten += n;
/* Last seen time. */ /* Last seen time. */
if ((n = rdbSaveMillisecondTime(rdb,consumer->seen_time)) == -1) if ((n = rdbSaveMillisecondTime(rdb,consumer->seen_time)) == -1) {
raxStop(&ri);
return -1; return -1;
}
nwritten += n; nwritten += n;
/* Consumer PEL, without the ACKs (see last parameter of the function /* Consumer PEL, without the ACKs (see last parameter of the function
* passed with value of 0), at loading time we'll lookup the ID * passed with value of 0), at loading time we'll lookup the ID
* in the consumer group global PEL and will put a reference in the * in the consumer group global PEL and will put a reference in the
* consumer local PEL. */ * consumer local PEL. */
if ((n = rdbSaveStreamPEL(rdb,consumer->pel,0)) == -1) if ((n = rdbSaveStreamPEL(rdb,consumer->pel,0)) == -1) {
raxStop(&ri);
return -1; return -1;
}
nwritten += n; nwritten += n;
} }
raxStop(&ri); raxStop(&ri);
@ -921,9 +936,15 @@ ssize_t rdbSaveObject(rio *rdb, robj_roptr o, robj *key) {
while (raxNext(&ri)) { while (raxNext(&ri)) {
unsigned char *lp = (unsigned char*)ri.data; unsigned char *lp = (unsigned char*)ri.data;
size_t lp_bytes = lpBytes(lp); size_t lp_bytes = lpBytes(lp);
if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) return -1; if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) {
raxStop(&ri);
return -1;
}
nwritten += n; nwritten += n;
if ((n = rdbSaveRawString(rdb,lp,lp_bytes)) == -1) return -1; if ((n = rdbSaveRawString(rdb,lp,lp_bytes)) == -1) {
raxStop(&ri);
return -1;
}
nwritten += n; nwritten += n;
} }
raxStop(&ri); raxStop(&ri);
@ -955,22 +976,36 @@ ssize_t rdbSaveObject(rio *rdb, robj_roptr o, robj *key) {
streamCG *cg = (streamCG*)ri.data; streamCG *cg = (streamCG*)ri.data;
/* Save the group name. */ /* Save the group name. */
if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) {
raxStop(&ri);
return -1; return -1;
}
nwritten += n; nwritten += n;
/* Last ID. */ /* Last ID. */
if ((n = rdbSaveLen(rdb,cg->last_id.ms)) == -1) return -1; if ((n = rdbSaveLen(rdb,cg->last_id.ms)) == -1) {
raxStop(&ri);
return -1;
}
nwritten += n; nwritten += n;
if ((n = rdbSaveLen(rdb,cg->last_id.seq)) == -1) return -1; if ((n = rdbSaveLen(rdb,cg->last_id.seq)) == -1) {
raxStop(&ri);
return -1;
}
nwritten += n; nwritten += n;
/* Save the global PEL. */ /* Save the global PEL. */
if ((n = rdbSaveStreamPEL(rdb,cg->pel,1)) == -1) return -1; if ((n = rdbSaveStreamPEL(rdb,cg->pel,1)) == -1) {
raxStop(&ri);
return -1;
}
nwritten += n; nwritten += n;
/* Save the consumers of this group. */ /* Save the consumers of this group. */
if ((n = rdbSaveStreamConsumers(rdb,cg)) == -1) return -1; if ((n = rdbSaveStreamConsumers(rdb,cg)) == -1) {
raxStop(&ri);
return -1;
}
nwritten += n; nwritten += n;
} }
raxStop(&ri); raxStop(&ri);
@ -2437,6 +2472,9 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
/* Set usage information (for eviction). */ /* Set usage information (for eviction). */
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock,1000); objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock,1000);
/* call key space notification on key loaded for modules only */
moduleNotifyKeyspaceEvent(NOTIFY_LOADED, "loaded", &keyobj, db->id);
} }
else else
{ {

View File

@ -1,4 +1,4 @@
/* Redis benchmark utility. /* Redis benchmark utility.
* *
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved. * All rights reserved.
@ -189,6 +189,8 @@ static void *execBenchmarkThread(void *ptr);
static clusterNode *createClusterNode(char *ip, int port); static clusterNode *createClusterNode(char *ip, int port);
static redisConfig *getRedisConfig(const char *ip, int port, static redisConfig *getRedisConfig(const char *ip, int port,
const char *hostsocket); const char *hostsocket);
static redisContext *getRedisContext(const char *ip, int port,
const char *hostsocket);
static void freeRedisConfig(redisConfig *cfg); static void freeRedisConfig(redisConfig *cfg);
static int fetchClusterSlotsConfiguration(client c); static int fetchClusterSlotsConfiguration(client c);
static void updateClusterSlotsConfiguration(); static void updateClusterSlotsConfiguration();
@ -244,46 +246,69 @@ extern "C" void _serverAssert(const char *estr, const char *file, int line) {
*((char*)-1) = 'x'; *((char*)-1) = 'x';
} }
static redisContext *getRedisContext(const char *ip, int port,
const char *hostsocket)
{
redisContext *ctx = NULL;
redisReply *reply = NULL;
if (hostsocket == NULL)
ctx = redisConnect(ip, port);
else
ctx = redisConnectUnix(hostsocket);
if (ctx == NULL || ctx->err) {
fprintf(stderr,"Could not connect to Redis at ");
const char *err = (ctx != NULL ? ctx->errstr : "");
if (hostsocket == NULL)
fprintf(stderr,"%s:%d: %s\n",ip,port,err);
else
fprintf(stderr,"%s: %s\n",hostsocket,err);
goto cleanup;
}
if (config.auth == NULL)
return ctx;
if (config.user == NULL)
reply = (redisReply*)redisCommand(ctx,"AUTH %s", config.auth);
else
reply = (redisReply*)redisCommand(ctx,"AUTH %s %s", config.user, config.auth);
if (reply != NULL) {
if (reply->type == REDIS_REPLY_ERROR) {
if (hostsocket == NULL)
fprintf(stderr, "Node %s:%d replied with error:\n%s\n", ip, port, reply->str);
else
fprintf(stderr, "Node %s replied with error:\n%s\n", hostsocket, reply->str);
goto cleanup;
}
freeReplyObject(reply);
return ctx;
}
fprintf(stderr, "ERROR: failed to fetch reply from ");
if (hostsocket == NULL)
fprintf(stderr, "%s:%d\n", ip, port);
else
fprintf(stderr, "%s\n", hostsocket);
cleanup:
freeReplyObject(reply);
redisFree(ctx);
return NULL;
}
static redisConfig *getRedisConfig(const char *ip, int port, static redisConfig *getRedisConfig(const char *ip, int port,
const char *hostsocket) const char *hostsocket)
{ {
redisConfig *cfg = (redisConfig*)zcalloc(sizeof(*cfg), MALLOC_LOCAL); redisConfig *cfg = (redisConfig*)zcalloc(sizeof(*cfg));
int i = 0;
void *r = NULL;
if (!cfg) return NULL; if (!cfg) return NULL;
redisContext *c = NULL; redisContext *c = NULL;
redisReply *reply = NULL, *sub_reply = NULL; redisReply *reply = NULL, *sub_reply = NULL;
if (hostsocket == NULL) c = getRedisContext(ip, port, hostsocket);
c = redisConnect(ip, port); if (c == NULL) {
else freeRedisConfig(cfg);
c = redisConnectUnix(hostsocket); return NULL;
if (c == NULL || c->err) {
fprintf(stderr,"Could not connect to Redis at ");
const char *err = (c != NULL ? c->errstr : "");
if (hostsocket == NULL) fprintf(stderr,"%s:%d: %s\n",ip,port,err);
else fprintf(stderr,"%s: %s\n",hostsocket,err);
goto fail;
} }
if(config.auth) {
void *authReply = NULL;
if (config.user == NULL)
redisAppendCommand(c, "AUTH %s", config.auth);
else
redisAppendCommand(c, "AUTH %s %s", config.user, config.auth);
if (REDIS_OK != redisGetReply(c, &authReply)) goto fail;
if (reply) freeReplyObject(reply);
reply = ((redisReply *) authReply);
if (reply->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "ERROR: %s\n", reply->str);
goto fail;
}
}
redisAppendCommand(c, "CONFIG GET %s", "save"); redisAppendCommand(c, "CONFIG GET %s", "save");
redisAppendCommand(c, "CONFIG GET %s", "appendonly"); redisAppendCommand(c, "CONFIG GET %s", "appendonly");
for (; i < 2; i++) { void *r;
for (int i=0; i < 2; i++) {
int res = redisGetReply(c, &r); int res = redisGetReply(c, &r);
if (reply) freeReplyObject(reply); if (reply) freeReplyObject(reply);
reply = res == REDIS_OK ? ((redisReply *) r) : NULL; reply = res == REDIS_OK ? ((redisReply *) r) : NULL;
@ -1001,17 +1026,11 @@ static int fetchClusterConfiguration() {
int success = 1; int success = 1;
redisContext *ctx = NULL; redisContext *ctx = NULL;
redisReply *reply = NULL; redisReply *reply = NULL;
char *lines = reply->str, *p, *line; char *lines = NULL;
if (config.hostsocket == NULL) char *line = NULL;
ctx = redisConnect(config.hostip,config.hostport); char *p = NULL;
else ctx = getRedisContext(config.hostip, config.hostport, config.hostsocket);
ctx = redisConnectUnix(config.hostsocket); if (ctx == NULL) {
if (ctx->err) {
fprintf(stderr,"Could not connect to Redis at ");
if (config.hostsocket == NULL) {
fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,
ctx->errstr);
} else fprintf(stderr,"%s: %s\n",config.hostsocket,ctx->errstr);
exit(1); exit(1);
} }
clusterNode *firstNode = createClusterNode((char *) config.hostip, clusterNode *firstNode = createClusterNode((char *) config.hostip,
@ -1207,11 +1226,9 @@ static int fetchClusterSlotsConfiguration(client c) {
assert(node->port); assert(node->port);
/* Use first node as entry point to connect to. */ /* Use first node as entry point to connect to. */
if (ctx == NULL) { if (ctx == NULL) {
ctx = redisConnect(node->ip, node->port); ctx = getRedisContext(node->ip, node->port, NULL);
if (!ctx || ctx->err) { if (!ctx) {
success = 0; success = 0;
if (ctx && ctx->err)
fprintf(stderr, "REDIS CONNECTION ERROR: %s\n", ctx->errstr);
goto cleanup; goto cleanup;
} }
} }
@ -1425,7 +1442,8 @@ usage:
" --cluster Enable cluster mode.\n" " --cluster Enable cluster mode.\n"
" --enable-tracking Send CLIENT TRACKING on before starting benchmark.\n" " --enable-tracking Send CLIENT TRACKING on before starting benchmark.\n"
" -k <boolean> 1=keep alive 0=reconnect (default 1)\n" " -k <boolean> 1=keep alive 0=reconnect (default 1)\n"
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n" " -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD,\n"
" random members and scores for ZADD.\n"
" Using this option the benchmark will expand the string __rand_int__\n" " Using this option the benchmark will expand the string __rand_int__\n"
" inside an argument with a 12 digits number in the specified range\n" " inside an argument with a 12 digits number in the specified range\n"
" from 0 to keyspacelen-1. The substitution changes every time a command\n" " from 0 to keyspacelen-1. The substitution changes every time a command\n"
@ -1722,7 +1740,7 @@ int main(int argc, const char **argv) {
if (test_is_selected("hset")) { if (test_is_selected("hset")) {
len = redisFormatCommand(&cmd, len = redisFormatCommand(&cmd,
"HSET myhash:{tag}:__rand_int__ element:__rand_int__ %s",data); "HSET myhash:{tag} element:__rand_int__ %s",data);
benchmark("HSET",cmd,len); benchmark("HSET",cmd,len);
free(cmd); free(cmd);
} }
@ -1733,6 +1751,21 @@ int main(int argc, const char **argv) {
free(cmd); free(cmd);
} }
if (test_is_selected("zadd")) {
const char *score = "0";
if (config.randomkeys) score = "__rand_int__";
len = redisFormatCommand(&cmd,
"ZADD myzset:{tag} %s element:__rand_int__",score);
benchmark("ZADD",cmd,len);
free(cmd);
}
if (test_is_selected("zpopmin")) {
len = redisFormatCommand(&cmd,"ZPOPMIN myzset:{tag}");
benchmark("ZPOPMIN",cmd,len);
free(cmd);
}
if (test_is_selected("lrange") || if (test_is_selected("lrange") ||
test_is_selected("lrange_100") || test_is_selected("lrange_100") ||
test_is_selected("lrange_300") || test_is_selected("lrange_300") ||

View File

@ -109,7 +109,6 @@ extern "C" void freeClusterManager(void) {
} }
/* This function returns a random master node, return NULL if none */ /* This function returns a random master node, return NULL if none */
static clusterManagerNode *clusterManagerNodeMasterRandom() { static clusterManagerNode *clusterManagerNodeMasterRandom() {
int master_count = 0; int master_count = 0;
int idx; int idx;
@ -140,7 +139,7 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
int force_fix = config.cluster_manager_command.flags & int force_fix = config.cluster_manager_command.flags &
CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS; CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
/* we want explicit manual confirmation from users for all the fix cases */ /* we want explicit manual confirmation from users for all the fix cases */
int force = 0; int ignore_force = 1;
dictIterator *iter = nullptr; dictIterator *iter = nullptr;
@ -213,7 +212,7 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
"across the cluster:\n"); "across the cluster:\n");
clusterManagerPrintSlotsList(none); clusterManagerPrintSlotsList(none);
if (confirmWithYes("Fix these slots by covering with a random node?", if (confirmWithYes("Fix these slots by covering with a random node?",
force)) { ignore_force)) {
listIter li; listIter li;
listNode *ln; listNode *ln;
listRewind(none, &li); listRewind(none, &li);
@ -240,7 +239,7 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
printf("The following uncovered slots have keys in just one node:\n"); printf("The following uncovered slots have keys in just one node:\n");
clusterManagerPrintSlotsList(single); clusterManagerPrintSlotsList(single);
if (confirmWithYes("Fix these slots by covering with those nodes?", if (confirmWithYes("Fix these slots by covering with those nodes?",
force)) { ignore_force)) {
listIter li; listIter li;
listNode *ln; listNode *ln;
listRewind(single, &li); listRewind(single, &li);
@ -272,7 +271,7 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
printf("The following uncovered slots have keys in multiple nodes:\n"); printf("The following uncovered slots have keys in multiple nodes:\n");
clusterManagerPrintSlotsList(multi); clusterManagerPrintSlotsList(multi);
if (confirmWithYes("Fix these slots by moving keys " if (confirmWithYes("Fix these slots by moving keys "
"into a single node?", force)) { "into a single node?", ignore_force)) {
listIter li; listIter li;
listNode *ln; listNode *ln;
listRewind(multi, &li); listRewind(multi, &li);
@ -336,6 +335,7 @@ cleanup:
return fixed; return fixed;
} }
/* Return the anti-affinity score, which is a measure of the amount of /* Return the anti-affinity score, which is a measure of the amount of
* violations of anti-affinity in the current cluster layout, that is, how * violations of anti-affinity in the current cluster layout, that is, how
* badly the masters and slaves are distributed in the different IP * badly the masters and slaves are distributed in the different IP
@ -759,7 +759,9 @@ static void getKeyTypes(dict *types_dict, redisReply *keys, typeinfo **types) {
/* Pipeline TYPE commands */ /* Pipeline TYPE commands */
for(i=0;i<keys->elements;i++) { for(i=0;i<keys->elements;i++) {
redisAppendCommand(context, "TYPE %s", keys->element[i]->str); const char* argv[] = {"TYPE", keys->element[i]->str};
size_t lens[] = {4, keys->element[i]->len};
redisAppendCommandArgv(context, 2, argv, lens);
} }
/* Retrieve types */ /* Retrieve types */
@ -856,20 +858,20 @@ void findBigKeys(int memkeys, unsigned memkeys_samples) {
sampled++; sampled++;
if(type->biggest<sizes[i]) { if(type->biggest<sizes[i]) {
printf(
"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
pct, type->name, keys->element[i]->str, sizes[i],
!memkeys? type->sizeunit: "bytes");
/* Keep track of biggest key name for this type */ /* Keep track of biggest key name for this type */
if (type->biggest_key) if (type->biggest_key)
sdsfree(type->biggest_key); sdsfree(type->biggest_key);
type->biggest_key = sdsnew(keys->element[i]->str); type->biggest_key = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
if(!type->biggest_key) { if(!type->biggest_key) {
fprintf(stderr, "Failed to allocate memory for key!\n"); fprintf(stderr, "Failed to allocate memory for key!\n");
exit(1); exit(1);
} }
printf(
"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
pct, type->name, type->biggest_key, sizes[i],
!memkeys? type->sizeunit: "bytes");
/* Keep track of the biggest size for this type */ /* Keep track of the biggest size for this type */
type->biggest = sizes[i]; type->biggest = sizes[i];
} }

View File

@ -1354,6 +1354,12 @@ static int parseOptions(int argc, char **argv) {
i = j; i = j;
} else if (!strcmp(argv[i],"--cluster") && lastarg) { } else if (!strcmp(argv[i],"--cluster") && lastarg) {
usage(); usage();
} else if ((!strcmp(argv[i],"--cluster-only-masters"))) {
config.cluster_manager_command.flags |=
CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY;
} else if ((!strcmp(argv[i],"--cluster-only-replicas"))) {
config.cluster_manager_command.flags |=
CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY;
} else if (!strcmp(argv[i],"--cluster-replicas") && !lastarg) { } else if (!strcmp(argv[i],"--cluster-replicas") && !lastarg) {
config.cluster_manager_command.replicas = atoi(argv[++i]); config.cluster_manager_command.replicas = atoi(argv[++i]);
} else if (!strcmp(argv[i],"--cluster-master-id") && !lastarg) { } else if (!strcmp(argv[i],"--cluster-master-id") && !lastarg) {
@ -1435,6 +1441,8 @@ static int parseOptions(int argc, char **argv) {
printf("keydb-cli %s\n", version); printf("keydb-cli %s\n", version);
sdsfree(version); sdsfree(version);
exit(0); exit(0);
} else if (!strcmp(argv[i],"--no-motd")) {
config.disable_motd = true;
} else if (!strcmp(argv[i],"-3")) { } else if (!strcmp(argv[i],"-3")) {
config.resp3 = 1; config.resp3 = 1;
} else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') { } else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
@ -1480,6 +1488,11 @@ static void parseEnv() {
if (auth != NULL && config.auth == NULL) { if (auth != NULL && config.auth == NULL) {
config.auth = auth; config.auth = auth;
} }
char *cluster_yes = getenv(REDIS_CLI_CLUSTER_YES_ENV);
if (cluster_yes != NULL && !strcmp(cluster_yes, "1")) {
config.cluster_manager_command.flags |= CLUSTER_MANAGER_CMD_FLAG_YES;
}
} }
static sds readArgFromStdin(void) { static sds readArgFromStdin(void) {
@ -1570,7 +1583,8 @@ static void usage(void) {
" --hotkeys Sample Redis keys looking for hot keys.\n" " --hotkeys Sample Redis keys looking for hot keys.\n"
" only works when maxmemory-policy is *lfu.\n" " only works when maxmemory-policy is *lfu.\n"
" --scan List all keys using the SCAN command.\n" " --scan List all keys using the SCAN command.\n"
" --pattern <pat> Useful with --scan to specify a SCAN pattern.\n" " --pattern <pat> Keys pattern when using the --scan, --bigkeys or --hotkeys\n"
" options (default: *).\n"
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n" " --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
" The test will run for the specified amount of seconds.\n" " The test will run for the specified amount of seconds.\n"
" --eval <file> Send an EVAL command using the Lua script at <file>.\n" " --eval <file> Send an EVAL command using the Lua script at <file>.\n"
@ -1609,10 +1623,10 @@ static void usage(void) {
exit(1); exit(1);
} }
int confirmWithYes(const char *msg, int force) { int confirmWithYes(const char *msg, int ignore_force) {
/* if force is true and --cluster-yes option is on, /* if force is true and --cluster-yes option is on,
* do not prompt for an answer */ * do not prompt for an answer */
if (force && if (!ignore_force &&
(config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_YES)) { (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_YES)) {
return 1; return 1;
} }
@ -1801,6 +1815,7 @@ static void repl(void) {
if (argv == NULL) { if (argv == NULL) {
printf("Invalid argument(s)\n"); printf("Invalid argument(s)\n");
fflush(stdout);
linenoiseFree(line); linenoiseFree(line);
continue; continue;
} else if (argc > 0) { } else if (argc > 0) {
@ -2045,7 +2060,7 @@ clusterManagerCommandDef clusterManagerCommands[] = {
"new_host:new_port existing_host:existing_port", "slave,master-id <arg>"}, "new_host:new_port existing_host:existing_port", "slave,master-id <arg>"},
{"del-node", clusterManagerCommandDeleteNode, 2, "host:port node_id",NULL}, {"del-node", clusterManagerCommandDeleteNode, 2, "host:port node_id",NULL},
{"call", clusterManagerCommandCall, -2, {"call", clusterManagerCommandCall, -2,
"host:port command arg arg .. arg", NULL}, "host:port command arg arg .. arg", "only-masters,only-replicas"},
{"set-timeout", clusterManagerCommandSetTimeout, 2, {"set-timeout", clusterManagerCommandSetTimeout, 2,
"host:port milliseconds", NULL}, "host:port milliseconds", NULL},
{"import", clusterManagerCommandImport, 1, "host:port", {"import", clusterManagerCommandImport, 1, "host:port",
@ -3037,6 +3052,7 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
size_t *argv_len = NULL; size_t *argv_len = NULL;
int c = (replace ? 8 : 7); int c = (replace ? 8 : 7);
if (config.auth) c += 2; if (config.auth) c += 2;
if (config.user) c += 1;
size_t argc = c + reply->elements; size_t argc = c + reply->elements;
size_t i, offset = 6; // Keys Offset size_t i, offset = 6; // Keys Offset
argv = zcalloc(argc * sizeof(char *), MALLOC_LOCAL); argv = zcalloc(argc * sizeof(char *), MALLOC_LOCAL);
@ -3063,12 +3079,24 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
offset++; offset++;
} }
if (config.auth) { if (config.auth) {
argv[offset] = "AUTH"; if (config.user) {
argv_len[offset] = 4; argv[offset] = "AUTH2";
offset++; argv_len[offset] = 5;
argv[offset] = config.auth; offset++;
argv_len[offset] = strlen(config.auth); argv[offset] = config.user;
offset++; argv_len[offset] = strlen(config.user);
offset++;
argv[offset] = config.auth;
argv_len[offset] = strlen(config.auth);
offset++;
} else {
argv[offset] = "AUTH";
argv_len[offset] = 4;
offset++;
argv[offset] = config.auth;
argv_len[offset] = strlen(config.auth);
offset++;
}
} }
argv[offset] = "KEYS"; argv[offset] = "KEYS";
argv_len[offset] = 4; argv_len[offset] = 4;
@ -4595,8 +4623,8 @@ assign_replicas:
} }
clusterManagerOptimizeAntiAffinity(ip_nodes, ip_count); clusterManagerOptimizeAntiAffinity(ip_nodes, ip_count);
clusterManagerShowNodes(); clusterManagerShowNodes();
int force = 1; int ignore_force = 0;
if (confirmWithYes("Can I set the above configuration?", force)) { if (confirmWithYes("Can I set the above configuration?", ignore_force)) {
listRewind(cluster_manager.nodes, &li); listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *node = ln->value; clusterManagerNode *node = ln->value;
@ -5487,6 +5515,10 @@ static int clusterManagerCommandCall(int argc, char **argv) {
listRewind(cluster_manager.nodes, &li); listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value; clusterManagerNode *n = ln->value;
if ((config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY)
&& (n->replicate != NULL)) continue; // continue if node is slave
if ((config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY)
&& (n->replicate == NULL)) continue; // continue if node is master
if (!n->context && !clusterManagerNodeConnect(n)) continue; if (!n->context && !clusterManagerNodeConnect(n)) continue;
redisReply *reply = NULL; redisReply *reply = NULL;
redisAppendCommandArgv(n->context, argc, (const char **) argv, argvlen); redisAppendCommandArgv(n->context, argc, (const char **) argv, argvlen);
@ -5856,10 +5888,84 @@ void sendCapa() {
sendReplconf("capa", "eof"); sendReplconf("capa", "eof");
} }
/* Wrapper around hiredis to allow arbitrary reads and writes.
*
* We piggybacks on top of hiredis to achieve transparent TLS support,
* and use its internal buffers so it can co-exist with commands
* previously/later issued on the connection.
*
* Interface is close to enough to read()/write() so things should mostly
* work transparently.
*/
/* Write a raw buffer through a redisContext. If we already have something
* in the buffer (leftovers from hiredis operations) it will be written
* as well.
*/
static ssize_t writeConn(redisContext *c, const char *buf, size_t buf_len)
{
int done = 0;
/* Append data to buffer which is *usually* expected to be empty
* but we don't assume that, and write.
*/
c->obuf = sdscatlen(c->obuf, buf, buf_len);
if (redisBufferWrite(c, &done) == REDIS_ERR) {
if (!(c->flags & REDIS_BLOCK))
errno = EAGAIN;
/* On error, we assume nothing was written and we roll back the
* buffer to its original state.
*/
if (sdslen(c->obuf) > buf_len)
sdsrange(c->obuf, 0, -(buf_len+1));
else
sdsclear(c->obuf);
return -1;
}
/* If we're done, free up everything. We may have written more than
* buf_len (if c->obuf was not initially empty) but we don't have to
* tell.
*/
if (done) {
sdsclear(c->obuf);
return buf_len;
}
/* Write was successful but we have some leftovers which we should
* remove from the buffer.
*
* Do we still have data that was there prior to our buf? If so,
* restore buffer to it's original state and report no new data was
* writen.
*/
if (sdslen(c->obuf) > buf_len) {
sdsrange(c->obuf, 0, -(buf_len+1));
return 0;
}
/* At this point we're sure no prior data is left. We flush the buffer
* and report how much we've written.
*/
size_t left = sdslen(c->obuf);
sdsclear(c->obuf);
return buf_len - left;
}
/* Read raw bytes through a redisContext. The read operation is not greedy
* and may not fill the buffer entirely.
*/
static ssize_t readConn(redisContext *c, char *buf, size_t len)
{
return c->funcs->read(c, buf, len);
}
/* Sends SYNC and reads the number of bytes in the payload. Used both by /* Sends SYNC and reads the number of bytes in the payload. Used both by
* slaveMode() and getRDB(). * slaveMode() and getRDB().
* returns 0 in case an EOF marker is used. */ * returns 0 in case an EOF marker is used. */
unsigned long long sendSync(int fd, char *out_eof) { unsigned long long sendSync(redisContext *c, char *out_eof) {
/* To start we need to send the SYNC command and return the payload. /* To start we need to send the SYNC command and return the payload.
* The hiredis client lib does not understand this part of the protocol * The hiredis client lib does not understand this part of the protocol
* and we don't want to mess with its buffers, so everything is performed * and we don't want to mess with its buffers, so everything is performed
@ -5868,7 +5974,7 @@ unsigned long long sendSync(int fd, char *out_eof) {
ssize_t nread; ssize_t nread;
/* Send the SYNC command. */ /* Send the SYNC command. */
if (write(fd,"SYNC\r\n",6) != 6) { if (writeConn(c, "SYNC\r\n", 6) != 6) {
fprintf(stderr,"Error writing to master\n"); fprintf(stderr,"Error writing to master\n");
exit(1); exit(1);
} }
@ -5876,7 +5982,7 @@ unsigned long long sendSync(int fd, char *out_eof) {
/* Read $<payload>\r\n, making sure to read just up to "\n" */ /* Read $<payload>\r\n, making sure to read just up to "\n" */
p = buf; p = buf;
while(1) { while(1) {
nread = read(fd,p,1); nread = readConn(c,p,1);
if (nread <= 0) { if (nread <= 0) {
fprintf(stderr,"Error reading bulk length while SYNCing\n"); fprintf(stderr,"Error reading bulk length while SYNCing\n");
exit(1); exit(1);
@ -5897,11 +6003,10 @@ unsigned long long sendSync(int fd, char *out_eof) {
} }
static void slaveMode(void) { static void slaveMode(void) {
int fd = context->fd;
static char eofmark[RDB_EOF_MARK_SIZE]; static char eofmark[RDB_EOF_MARK_SIZE];
static char lastbytes[RDB_EOF_MARK_SIZE]; static char lastbytes[RDB_EOF_MARK_SIZE];
static int usemark = 0; static int usemark = 0;
unsigned long long payload = sendSync(fd, eofmark); unsigned long long payload = sendSync(context,eofmark);
char buf[1024]; char buf[1024];
int original_output = config.output; int original_output = config.output;
@ -5921,7 +6026,7 @@ static void slaveMode(void) {
while(payload) { while(payload) {
ssize_t nread; ssize_t nread;
nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); nread = readConn(context,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
if (nread <= 0) { if (nread <= 0) {
fprintf(stderr,"Error reading RDB payload while SYNCing\n"); fprintf(stderr,"Error reading RDB payload while SYNCing\n");
exit(1); exit(1);
@ -5964,14 +6069,15 @@ static void slaveMode(void) {
/* This function implements --rdb, so it uses the replication protocol in order /* This function implements --rdb, so it uses the replication protocol in order
* to fetch the RDB file from a remote server. */ * to fetch the RDB file from a remote server. */
static void getRDB(clusterManagerNode *node) { static void getRDB(clusterManagerNode *node) {
int s, fd; int fd;
redisContext *s;
char *filename; char *filename;
if (node != NULL) { if (node != NULL) {
assert(node->context); assert(node->context);
s = node->context->fd; s = node->context;
filename = clusterManagerGetNodeRDBFilename(node); filename = clusterManagerGetNodeRDBFilename(node);
} else { } else {
s = context->fd; s = context;
filename = config.rdb_filename; filename = config.rdb_filename;
} }
static char eofmark[RDB_EOF_MARK_SIZE]; static char eofmark[RDB_EOF_MARK_SIZE];
@ -6006,7 +6112,7 @@ static void getRDB(clusterManagerNode *node) {
while(payload) { while(payload) {
ssize_t nread, nwritten; ssize_t nread, nwritten;
nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); nread = readConn(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
if (nread <= 0) { if (nread <= 0) {
fprintf(stderr,"I/O Error reading RDB payload from socket\n"); fprintf(stderr,"I/O Error reading RDB payload from socket\n");
exit(1); exit(1);
@ -6040,7 +6146,7 @@ static void getRDB(clusterManagerNode *node) {
} else { } else {
fprintf(stderr,"Transfer finished with success.\n"); fprintf(stderr,"Transfer finished with success.\n");
} }
close(s); /* Close the file descriptor ASAP as fsync() may take time. */ redisFree(s); /* Close the file descriptor ASAP as fsync() may take time. */
fsync(fd); fsync(fd);
close(fd); close(fd);
fprintf(stderr,"Transfer finished with success.\n"); fprintf(stderr,"Transfer finished with success.\n");
@ -6057,11 +6163,9 @@ static void getRDB(clusterManagerNode *node) {
#define PIPEMODE_WRITE_LOOP_MAX_BYTES (128*1024) #define PIPEMODE_WRITE_LOOP_MAX_BYTES (128*1024)
static void pipeMode(void) { static void pipeMode(void) {
int fd = context->fd;
long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0; long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */ char obuf[1024*16]; /* Output buffer */
char aneterr[ANET_ERR_LEN]; char aneterr[ANET_ERR_LEN];
redisReader *reader = redisReaderCreate();
redisReply *reply; redisReply *reply;
int eof = 0; /* True once we consumed all the standard input. */ int eof = 0; /* True once we consumed all the standard input. */
int done = 0; int done = 0;
@ -6071,47 +6175,38 @@ static void pipeMode(void) {
srand(time(NULL)); srand(time(NULL));
/* Use non blocking I/O. */ /* Use non blocking I/O. */
if (anetNonBlock(aneterr,fd) == ANET_ERR) { if (anetNonBlock(aneterr,context->fd) == ANET_ERR) {
fprintf(stderr, "Can't set the socket in non blocking mode: %s\n", fprintf(stderr, "Can't set the socket in non blocking mode: %s\n",
aneterr); aneterr);
exit(1); exit(1);
} }
context->flags &= ~REDIS_BLOCK;
/* Transfer raw protocol and read replies from the server at the same /* Transfer raw protocol and read replies from the server at the same
* time. */ * time. */
while(!done) { while(!done) {
int mask = AE_READABLE; int mask = AE_READABLE;
if (!eof || obuf_len != 0) mask |= AE_WRITABLE; if (!eof || obuf_len != 0) mask |= AE_WRITABLE;
mask = aeWait(fd,mask,1000); mask = aeWait(context->fd,mask,1000);
/* Handle the readable state: we can read replies from the server. */ /* Handle the readable state: we can read replies from the server. */
if (mask & AE_READABLE) { if (mask & AE_READABLE) {
ssize_t nread;
int read_error = 0; int read_error = 0;
/* Read from socket and feed the hiredis reader. */
do { do {
nread = read(fd,ibuf,sizeof(ibuf)); if (!read_error && redisBufferRead(context) == REDIS_ERR) {
if (nread == -1 && errno != EAGAIN && errno != EINTR) {
fprintf(stderr, "Error reading from the server: %s\n",
strerror(errno));
read_error = 1; read_error = 1;
break;
} }
if (nread > 0) {
redisReaderFeed(reader,ibuf,nread);
last_read_time = time(NULL);
}
} while(nread > 0);
/* Consume replies. */ reply = NULL;
do { if (redisGetReply(context, (void **) &reply) == REDIS_ERR) {
if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) {
fprintf(stderr, "Error reading replies from server\n"); fprintf(stderr, "Error reading replies from server\n");
exit(1); exit(1);
} }
if (reply) { if (reply) {
last_read_time = time(NULL);
if (reply->type == REDIS_REPLY_ERROR) { if (reply->type == REDIS_REPLY_ERROR) {
fprintf(stderr,"%s\n", reply->str); fprintf(stderr,"%s\n", reply->str);
errors++; errors++;
@ -6144,7 +6239,7 @@ static void pipeMode(void) {
while(1) { while(1) {
/* Transfer current buffer to server. */ /* Transfer current buffer to server. */
if (obuf_len != 0) { if (obuf_len != 0) {
ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len); ssize_t nwritten = writeConn(context,obuf+obuf_pos,obuf_len);
if (nwritten == -1) { if (nwritten == -1) {
if (errno != EAGAIN && errno != EINTR) { if (errno != EAGAIN && errno != EINTR) {
@ -6160,6 +6255,10 @@ static void pipeMode(void) {
loop_nwritten += nwritten; loop_nwritten += nwritten;
if (obuf_len != 0) break; /* Can't accept more data. */ if (obuf_len != 0) break; /* Can't accept more data. */
} }
if (context->err) {
fprintf(stderr, "Server I/O Error: %s\n", context->errstr);
exit(1);
}
/* If buffer is empty, load from stdin. */ /* If buffer is empty, load from stdin. */
if (obuf_len == 0 && !eof) { if (obuf_len == 0 && !eof) {
ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf)); ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf));
@ -6210,7 +6309,6 @@ static void pipeMode(void) {
break; break;
} }
} }
redisReaderFree(reader);
printf("errors: %lld, replies: %lld\n", errors, replies); printf("errors: %lld, replies: %lld\n", errors, replies);
if (errors) if (errors)
exit(1); exit(1);
@ -6223,7 +6321,13 @@ static void pipeMode(void) {
*--------------------------------------------------------------------------- */ *--------------------------------------------------------------------------- */
redisReply *sendScan(unsigned long long *it) { redisReply *sendScan(unsigned long long *it) {
redisReply *reply = redisCommand(context, "SCAN %llu", *it); redisReply *reply;
if (config.pattern)
reply = redisCommand(context,"SCAN %llu MATCH %s",
*it,config.pattern);
else
reply = redisCommand(context,"SCAN %llu",*it);
/* Handle any error conditions */ /* Handle any error conditions */
if(reply == NULL) { if(reply == NULL) {
@ -6298,15 +6402,21 @@ void getKeySizes(redisReply *keys, typeinfo **types,
if(!types[i] || (!types[i]->sizecmd && !memkeys)) if(!types[i] || (!types[i]->sizecmd && !memkeys))
continue; continue;
if (!memkeys) if (!memkeys) {
redisAppendCommand(context, "%s %s", const char* argv[] = {types[i]->sizecmd, keys->element[i]->str};
types[i]->sizecmd, keys->element[i]->str); size_t lens[] = {strlen(types[i]->sizecmd), keys->element[i]->len};
else if (memkeys_samples==0) redisAppendCommandArgv(context, 2, argv, lens);
redisAppendCommand(context, "%s %s %s", } else if (memkeys_samples==0) {
"MEMORY", "USAGE", keys->element[i]->str); const char* argv[] = {"MEMORY", "USAGE", keys->element[i]->str};
else size_t lens[] = {6, 5, keys->element[i]->len};
redisAppendCommand(context, "%s %s %s SAMPLES %u", redisAppendCommandArgv(context, 3, argv, lens);
"MEMORY", "USAGE", keys->element[i]->str, memkeys_samples); } else {
sds samplesstr = sdsfromlonglong(memkeys_samples);
const char* argv[] = {"MEMORY", "USAGE", keys->element[i]->str, "SAMPLES", samplesstr};
size_t lens[] = {6, 5, keys->element[i]->len, 7, sdslen(samplesstr)};
redisAppendCommandArgv(context, 5, argv, lens);
sdsfree(samplesstr);
}
} }
/* Retrieve sizes */ /* Retrieve sizes */
@ -6344,21 +6454,27 @@ static void getKeyFreqs(redisReply *keys, unsigned long long *freqs) {
/* Pipeline OBJECT freq commands */ /* Pipeline OBJECT freq commands */
for(i=0;i<keys->elements;i++) { for(i=0;i<keys->elements;i++) {
redisAppendCommand(context, "OBJECT freq %s", keys->element[i]->str); const char* argv[] = {"OBJECT", "FREQ", keys->element[i]->str};
size_t lens[] = {6, 4, keys->element[i]->len};
redisAppendCommandArgv(context, 3, argv, lens);
} }
/* Retrieve freqs */ /* Retrieve freqs */
for(i=0;i<keys->elements;i++) { for(i=0;i<keys->elements;i++) {
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) { if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
sds keyname = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
fprintf(stderr, "Error getting freq for key '%s' (%d: %s)\n", fprintf(stderr, "Error getting freq for key '%s' (%d: %s)\n",
keys->element[i]->str, context->err, context->errstr); keyname, context->err, context->errstr);
sdsfree(keyname);
exit(1); exit(1);
} else if(reply->type != REDIS_REPLY_INTEGER) { } else if(reply->type != REDIS_REPLY_INTEGER) {
if(reply->type == REDIS_REPLY_ERROR) { if(reply->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "Error: %s\n", reply->str); fprintf(stderr, "Error: %s\n", reply->str);
exit(1); exit(1);
} else { } else {
fprintf(stderr, "Warning: OBJECT freq on '%s' failed (may have been deleted)\n", keys->element[i]->str); sds keyname = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
fprintf(stderr, "Warning: OBJECT freq on '%s' failed (may have been deleted)\n", keyname);
sdsfree(keyname);
freqs[i] = 0; freqs[i] = 0;
} }
} else { } else {
@ -6429,10 +6545,10 @@ static void findHotKeys(void) {
memmove(hotkeys,hotkeys+1,sizeof(hotkeys[0])*k); memmove(hotkeys,hotkeys+1,sizeof(hotkeys[0])*k);
} }
counters[k] = freqs[i]; counters[k] = freqs[i];
hotkeys[k] = sdsnew(keys->element[i]->str); hotkeys[k] = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
printf( printf(
"[%05.2f%%] Hot key '%s' found so far with counter %llu\n", "[%05.2f%%] Hot key '%s' found so far with counter %llu\n",
pct, keys->element[i]->str, freqs[i]); pct, hotkeys[k], freqs[i]);
} }
/* Sleep if we've been directed to do so */ /* Sleep if we've been directed to do so */
@ -6995,7 +7111,7 @@ int main(int argc, char **argv) {
/* Start interactive mode when no command is provided */ /* Start interactive mode when no command is provided */
if (argc == 0 && !config.eval) { if (argc == 0 && !config.eval) {
/* Show the message of the day if we are interactive */ /* Show the message of the day if we are interactive */
if (config.output == OUTPUT_STANDARD) { if (config.output == OUTPUT_STANDARD && !config.disable_motd) {
char *szMotd = fetchMOTD(1 /* cache */); char *szMotd = fetchMOTD(1 /* cache */);
if (szMotd != NULL) { if (szMotd != NULL) {
printf("Message of the day:\n %s\n", szMotd); printf("Message of the day:\n %s\n", szMotd);
@ -7021,4 +7137,3 @@ int main(int argc, char **argv) {
return noninteractive(argc,convertToSds(argc,argv)); return noninteractive(argc,convertToSds(argc,argv));
} }
} }

View File

@ -16,6 +16,7 @@ extern "C" {
#define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE" #define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
#define REDIS_CLI_RCFILE_DEFAULT ".redisclirc" #define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
#define REDIS_CLI_AUTH_ENV "REDISCLI_AUTH" #define REDIS_CLI_AUTH_ENV "REDISCLI_AUTH"
#define REDIS_CLI_CLUSTER_YES_ENV "REDISCLI_CLUSTER_YES"
#define CLUSTER_MANAGER_SLOTS 16384 #define CLUSTER_MANAGER_SLOTS 16384
#define CLUSTER_MANAGER_MIGRATE_TIMEOUT 60000 #define CLUSTER_MANAGER_MIGRATE_TIMEOUT 60000
@ -67,6 +68,8 @@ extern "C" {
#define CLUSTER_MANAGER_CMD_FLAG_COLOR 1 << 8 #define CLUSTER_MANAGER_CMD_FLAG_COLOR 1 << 8
#define CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS 1 << 9 #define CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS 1 << 9
#define CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS 1 << 10 #define CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS 1 << 10
#define CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY 1 << 11
#define CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY 1 << 12
#define CLUSTER_MANAGER_OPT_GETFRIENDS 1 << 0 #define CLUSTER_MANAGER_OPT_GETFRIENDS 1 << 0
#define CLUSTER_MANAGER_OPT_COLD 1 << 1 #define CLUSTER_MANAGER_OPT_COLD 1 << 1
@ -179,6 +182,7 @@ extern struct config {
clusterManagerCommand cluster_manager_command; clusterManagerCommand cluster_manager_command;
int no_auth_warning; int no_auth_warning;
int resp3; int resp3;
int disable_motd;
} config; } config;
struct clusterManager { struct clusterManager {

View File

@ -132,9 +132,9 @@ extern "C" {
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */ #define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */ #define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */ #define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */
#define REDISMODULE_NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */ #define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */
/* A special pointer that we can use between the core and the module to signal /* A special pointer that we can use between the core and the module to signal
* field deletion, and that is impossible to be a valid pointer. */ * field deletion, and that is impossible to be a valid pointer. */
#define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1) #define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1)
@ -384,6 +384,31 @@ typedef struct RedisModuleLoadingProgressInfo {
typedef long long mstime_t; typedef long long mstime_t;
/* Macro definitions specific to individual compilers */
#ifndef REDISMODULE_ATTR_UNUSED
# ifdef __GNUC__
# define REDISMODULE_ATTR_UNUSED __attribute__((unused))
# else
# define REDISMODULE_ATTR_UNUSED
# endif
#endif
#ifndef REDISMODULE_ATTR_PRINTF
# ifdef __GNUC__
# define REDISMODULE_ATTR_PRINTF(idx,cnt) __attribute__((format(printf,idx,cnt)))
# else
# define REDISMODULE_ATTR_PRINTF(idx,cnt)
# endif
#endif
#ifndef REDISMODULE_ATTR_COMMON
# if defined(__GNUC__) && !defined(__clang__)
# define REDISMODULE_ATTR_COMMON __attribute__((__common__))
# else
# define REDISMODULE_ATTR_COMMON
# endif
#endif
/* Incomplete structures for compiler checks but opaque access. */ /* Incomplete structures for compiler checks but opaque access. */
typedef struct RedisModuleCtx RedisModuleCtx; typedef struct RedisModuleCtx RedisModuleCtx;
typedef struct RedisModuleKey RedisModuleKey; typedef struct RedisModuleKey RedisModuleKey;
@ -440,256 +465,257 @@ typedef struct RedisModuleTypeMethods {
#define REDISMODULE_GET_API(name) \ #define REDISMODULE_GET_API(name) \
RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name)) RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name))
#define REDISMODULE_API_FUNC(x) (*x) /* Default API declaration prefix (not 'extern' for backwards compatibility) */
#ifndef REDISMODULE_API
#define REDISMODULE_API
void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes);
void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes);
void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr);
void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size);
char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str);
int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);
int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);
void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name);
int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll);
int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid);
void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode);
void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp);
int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp);
size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp);
int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where);
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len);
void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply);
int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply);
long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply);
size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply);
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
#ifdef __GNUC__
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#else
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
#endif #endif
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len); /* Default API declaration suffix (compiler attributes) */
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err); #ifndef REDISMODULE_ATTR
int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg); #define REDISMODULE_ATTR REDISMODULE_ATTR_COMMON
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx);
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);
int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll);
int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d);
int REDISMODULE_API_FUNC(RedisModule_StringToLongDouble)(const RedisModuleString *str, long double *d);
void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply);
int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key);
int REDISMODULE_API_FUNC(RedisModule_UnlinkKey)(RedisModuleKey *key);
int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str);
char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode);
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key);
int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire);
void REDISMODULE_API_FUNC(RedisModule_ResetDataset)(int restart_aof, int async);
unsigned long long REDISMODULE_API_FUNC(RedisModule_DbSize)(RedisModuleCtx *ctx);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_RandomKey)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr);
int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore);
int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score);
int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted);
void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key);
int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);
int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);
int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);
int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score);
int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key);
int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key);
int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key);
int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...);
int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...);
int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_GetClientInfoById)(void *ci, uint64_t id);
int REDISMODULE_API_FUNC(RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message);
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_AvoidReplicaTraffic)();
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeReplaceValue)(RedisModuleKey *key, RedisModuleType *mt, void *new_value, void **old_value);
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options);
int REDISMODULE_API_FUNC(RedisModule_SignalModifiedKey)(RedisModuleCtx *ctx, RedisModuleString *keyname);
void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value);
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value);
int64_t REDISMODULE_API_FUNC(RedisModule_LoadSigned)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...);
void REDISMODULE_API_FUNC(RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s);
void REDISMODULE_API_FUNC(RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_LoadString)(RedisModuleIO *io);
char *REDISMODULE_API_FUNC(RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr);
void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double value);
double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value);
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_SaveLongDouble)(RedisModuleIO *io, long double value);
long double REDISMODULE_API_FUNC(RedisModule_LoadLongDouble)(RedisModuleIO *io);
void *REDISMODULE_API_FUNC(RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt);
#ifdef __GNUC__
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
#else
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
#endif #endif
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
void REDISMODULE_API_FUNC(RedisModule_LatencyAddSample)(const char *event, mstime_t latency); REDISMODULE_API void * (*RedisModule_Alloc)(size_t bytes) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len); REDISMODULE_API void * (*RedisModule_Realloc)(void *ptr, size_t bytes) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str); REDISMODULE_API void (*RedisModule_Free)(void *ptr) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); REDISMODULE_API void * (*RedisModule_Calloc)(size_t nmemb, size_t size) REDISMODULE_ATTR;
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io); REDISMODULE_API char * (*RedisModule_Strdup)(const char *str) REDISMODULE_ATTR;
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io); REDISMODULE_API int (*RedisModule_GetApi)(const char *, void *) REDISMODULE_ATTR;
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromModuleKey)(RedisModuleKey *key); REDISMODULE_API int (*RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR;
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void); REDISMODULE_API void (*RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len); REDISMODULE_API int (*RedisModule_IsModuleNameBusy)(const char *name) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele); REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md); REDISMODULE_API int (*RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR;
RedisModuleDict *REDISMODULE_API_FUNC(RedisModule_CreateDict)(RedisModuleCtx *ctx); REDISMODULE_API int (*RedisModule_GetSelectedDb)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d); REDISMODULE_API int (*RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid) REDISMODULE_ATTR;
uint64_t REDISMODULE_API_FUNC(RedisModule_DictSize)(RedisModuleDict *d); REDISMODULE_API void * (*RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr); REDISMODULE_API void (*RedisModule_CloseKey)(RedisModuleKey *kp) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr); REDISMODULE_API int (*RedisModule_KeyType)(RedisModuleKey *kp) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr); REDISMODULE_API size_t (*RedisModule_ValueLength)(RedisModuleKey *kp) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr); REDISMODULE_API int (*RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele) REDISMODULE_ATTR;
void *REDISMODULE_API_FUNC(RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey); REDISMODULE_API RedisModuleString * (*RedisModule_ListPop)(RedisModuleKey *key, int where) REDISMODULE_ATTR;
void *REDISMODULE_API_FUNC(RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey); REDISMODULE_API RedisModuleCallReply * (*RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval); REDISMODULE_API const char * (*RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval); REDISMODULE_API void (*RedisModule_FreeCallReply)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen); REDISMODULE_API int (*RedisModule_CallReplyType)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key); REDISMODULE_API long long (*RedisModule_CallReplyInteger)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_DictIteratorStop)(RedisModuleDictIter *di); REDISMODULE_API size_t (*RedisModule_CallReplyLength)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen); REDISMODULE_API RedisModuleCallReply * (*RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key); REDISMODULE_API RedisModuleString * (*RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len) REDISMODULE_ATTR;
void *REDISMODULE_API_FUNC(RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr); REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR;
void *REDISMODULE_API_FUNC(RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr); REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR;
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr); REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly) REDISMODULE_ATTR;
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr); REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen); REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...) REDISMODULE_ATTR_PRINTF(2,3) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key); REDISMODULE_API void (*RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb); REDISMODULE_API const char * (*RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name); REDISMODULE_API int (*RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name); REDISMODULE_API int (*RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx); REDISMODULE_API int (*RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value); REDISMODULE_API int (*RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value); REDISMODULE_API int (*RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value); REDISMODULE_API void (*RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value); REDISMODULE_API int (*RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value); REDISMODULE_API int (*RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf) REDISMODULE_ATTR;
RedisModuleServerInfoData *REDISMODULE_API_FUNC(RedisModule_GetServerInfo)(RedisModuleCtx *ctx, const char *section); REDISMODULE_API int (*RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_FreeServerInfo)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data); REDISMODULE_API int (*RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ServerInfoGetField)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field); REDISMODULE_API int (*RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR;
const char *REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldC)(RedisModuleServerInfoData *data, const char* field); REDISMODULE_API int (*RedisModule_ReplyWithNull)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldSigned)(RedisModuleServerInfoData *data, const char* field, int *out_err); REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR;
unsigned long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldUnsigned)(RedisModuleServerInfoData *data, const char* field, int *out_err); REDISMODULE_API int (*RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d) REDISMODULE_ATTR;
double REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldDouble)(RedisModuleServerInfoData *data, const char* field, int *out_err); REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback); REDISMODULE_API int (*RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_SetLRU)(RedisModuleKey *key, mstime_t lru_idle); REDISMODULE_API int (*RedisModule_StringToDouble)(const RedisModuleString *str, double *d) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_GetLRU)(RedisModuleKey *key, mstime_t *lru_idle); REDISMODULE_API int (*RedisModule_StringToLongDouble)(const RedisModuleString *str, long double *d) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_SetLFU)(RedisModuleKey *key, long long lfu_freq); REDISMODULE_API void (*RedisModule_AutoMemory)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_GetLFU)(RedisModuleKey *key, long long *lfu_freq); REDISMODULE_API int (*RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR;
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata); REDISMODULE_API int (*RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key); REDISMODULE_API const char * (*RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR;
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientReadyKey)(RedisModuleCtx *ctx); REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
RedisModuleScanCursor *REDISMODULE_API_FUNC(RedisModule_ScanCursorCreate)(); REDISMODULE_API int (*RedisModule_DeleteKey)(RedisModuleKey *key) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_ScanCursorRestart)(RedisModuleScanCursor *cursor); REDISMODULE_API int (*RedisModule_UnlinkKey)(RedisModuleKey *key) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_ScanCursorDestroy)(RedisModuleScanCursor *cursor); REDISMODULE_API int (*RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_Scan)(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata); REDISMODULE_API char * (*RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_ScanKey)(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata); REDISMODULE_API int (*RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen) REDISMODULE_ATTR;
REDISMODULE_API mstime_t (*RedisModule_GetExpire)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ResetDataset)(int restart_aof, int async) REDISMODULE_ATTR;
REDISMODULE_API unsigned long long (*RedisModule_DbSize)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_RandomKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ZsetRangeStop)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetRangeNext)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetRangePrev)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ZsetRangeEndReached)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_HashSet)(RedisModuleKey *key, int flags, ...) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_HashGet)(RedisModuleKey *key, int flags, ...) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR;
REDISMODULE_API unsigned long long (*RedisModule_GetClientId)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetClientInfoById)(void *ci, uint64_t id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetContextFlags)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_AvoidReplicaTraffic)() REDISMODULE_ATTR;
REDISMODULE_API void * (*RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleType * (*RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ModuleTypeReplaceValue)(RedisModuleKey *key, RedisModuleType *mt, void *new_value, void **old_value) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleType * (*RedisModule_ModuleTypeGetType)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API void * (*RedisModule_ModuleTypeGetValue)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_IsIOError)(RedisModuleIO *io) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SignalModifiedKey)(RedisModuleCtx *ctx, RedisModuleString *keyname) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value) REDISMODULE_ATTR;
REDISMODULE_API uint64_t (*RedisModule_LoadUnsigned)(RedisModuleIO *io) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value) REDISMODULE_ATTR;
REDISMODULE_API int64_t (*RedisModule_LoadSigned)(RedisModuleIO *io) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_LoadString)(RedisModuleIO *io) REDISMODULE_ATTR;
REDISMODULE_API char * (*RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SaveDouble)(RedisModuleIO *io, double value) REDISMODULE_ATTR;
REDISMODULE_API double (*RedisModule_LoadDouble)(RedisModuleIO *io) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SaveFloat)(RedisModuleIO *io, float value) REDISMODULE_ATTR;
REDISMODULE_API float (*RedisModule_LoadFloat)(RedisModuleIO *io) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SaveLongDouble)(RedisModuleIO *io, long double value) REDISMODULE_ATTR;
REDISMODULE_API long double (*RedisModule_LoadLongDouble)(RedisModuleIO *io) REDISMODULE_ATTR;
REDISMODULE_API void * (*RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...) REDISMODULE_ATTR REDISMODULE_ATTR_PRINTF(3,4);
REDISMODULE_API void (*RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) REDISMODULE_ATTR REDISMODULE_ATTR_PRINTF(3,4);
REDISMODULE_API void (*RedisModule__Assert)(const char *estr, const char *file, int line) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_LatencyAddSample)(const char *event, mstime_t latency) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_HoldString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleCtx * (*RedisModule_GetContextFromIO)(RedisModuleIO *io) REDISMODULE_ATTR;
REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromIO)(RedisModuleIO *io) REDISMODULE_ATTR;
REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromModuleKey)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API long long (*RedisModule_Milliseconds)(void) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_DigestEndSequence)(RedisModuleDigest *md) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleDict * (*RedisModule_CreateDict)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d) REDISMODULE_ATTR;
REDISMODULE_API uint64_t (*RedisModule_DictSize)(RedisModuleDict *d) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API void * (*RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey) REDISMODULE_ATTR;
REDISMODULE_API void * (*RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleDictIter * (*RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleDictIter * (*RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_DictIteratorStop)(RedisModuleDictIter *di) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key) REDISMODULE_ATTR;
REDISMODULE_API void * (*RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr) REDISMODULE_ATTR;
REDISMODULE_API void * (*RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleServerInfoData * (*RedisModule_GetServerInfo)(RedisModuleCtx *ctx, const char *section) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_FreeServerInfo)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_ServerInfoGetField)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field) REDISMODULE_ATTR;
REDISMODULE_API const char * (*RedisModule_ServerInfoGetFieldC)(RedisModuleServerInfoData *data, const char* field) REDISMODULE_ATTR;
REDISMODULE_API long long (*RedisModule_ServerInfoGetFieldSigned)(RedisModuleServerInfoData *data, const char* field, int *out_err) REDISMODULE_ATTR;
REDISMODULE_API unsigned long long (*RedisModule_ServerInfoGetFieldUnsigned)(RedisModuleServerInfoData *data, const char* field, int *out_err) REDISMODULE_ATTR;
REDISMODULE_API double (*RedisModule_ServerInfoGetFieldDouble)(RedisModuleServerInfoData *data, const char* field, int *out_err) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetLRU)(RedisModuleKey *key, mstime_t lru_idle) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetLRU)(RedisModuleKey *key, mstime_t *lru_idle) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetLFU)(RedisModuleKey *key, long long lfu_freq) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetLFU)(RedisModuleKey *key, long long *lfu_freq) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_GetBlockedClientReadyKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleScanCursor * (*RedisModule_ScanCursorCreate)() REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ScanCursorRestart)(RedisModuleScanCursor *cursor) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ScanCursorDestroy)(RedisModuleScanCursor *cursor) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_Scan)(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ScanKey)(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata) REDISMODULE_ATTR;
/* Experimental APIs */ /* Experimental APIs */
#ifdef REDISMODULE_EXPERIMENTAL_API #ifdef REDISMODULE_EXPERIMENTAL_API
#define REDISMODULE_EXPERIMENTAL_API_VERSION 3 #define REDISMODULE_EXPERIMENTAL_API_VERSION 3
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms); REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata); REDISMODULE_API int (*RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx); REDISMODULE_API int (*RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx); REDISMODULE_API int (*RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx); REDISMODULE_API void * (*RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientHandle)(RedisModuleCtx *ctx); REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_GetBlockedClientHandle)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_AbortBlock)(RedisModuleBlockedClient *bc); REDISMODULE_API int (*RedisModule_AbortBlock)(RedisModuleBlockedClient *bc) REDISMODULE_ATTR;
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc); REDISMODULE_API RedisModuleCtx * (*RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx); REDISMODULE_API void (*RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx); REDISMODULE_API void (*RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx); REDISMODULE_API int (*RedisModule_ThreadSafeContextTryLock)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb); REDISMODULE_API void (*RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_NotifyKeyspaceEvent)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key); REDISMODULE_API int (*RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_GetNotifyKeyspaceEvents)(); REDISMODULE_API int (*RedisModule_NotifyKeyspaceEvent)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx); REDISMODULE_API int (*RedisModule_GetNotifyKeyspaceEvents)() REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback); REDISMODULE_API int (*RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len); REDISMODULE_API void (*RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags); REDISMODULE_API int (*RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len) REDISMODULE_ATTR;
char **REDISMODULE_API_FUNC(RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes); REDISMODULE_API int (*RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_FreeClusterNodesList)(char **ids); REDISMODULE_API char ** (*RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes) REDISMODULE_ATTR;
RedisModuleTimerID REDISMODULE_API_FUNC(RedisModule_CreateTimer)(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data); REDISMODULE_API void (*RedisModule_FreeClusterNodesList)(char **ids) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_StopTimer)(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data); REDISMODULE_API RedisModuleTimerID (*RedisModule_CreateTimer)(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_GetTimerInfo)(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data); REDISMODULE_API int (*RedisModule_StopTimer)(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data) REDISMODULE_ATTR;
const char *REDISMODULE_API_FUNC(RedisModule_GetMyClusterID)(void); REDISMODULE_API int (*RedisModule_GetTimerInfo)(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data) REDISMODULE_ATTR;
size_t REDISMODULE_API_FUNC(RedisModule_GetClusterSize)(void); REDISMODULE_API const char * (*RedisModule_GetMyClusterID)(void) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t len); REDISMODULE_API size_t (*RedisModule_GetClusterSize)(void) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len); REDISMODULE_API void (*RedisModule_GetRandomBytes)(unsigned char *dst, size_t len) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback); REDISMODULE_API void (*RedisModule_GetRandomHexChars)(char *dst, size_t len) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags); REDISMODULE_API void (*RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func); REDISMODULE_API void (*RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags) REDISMODULE_ATTR;
void *REDISMODULE_API_FUNC(RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname); REDISMODULE_API int (*RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func) REDISMODULE_ATTR;
RedisModuleCommandFilter *REDISMODULE_API_FUNC(RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags); REDISMODULE_API void * (*RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter); REDISMODULE_API RedisModuleCommandFilter * (*RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx); REDISMODULE_API int (*RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter) REDISMODULE_ATTR;
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos); REDISMODULE_API int (*RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg); REDISMODULE_API const RedisModuleString * (*RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg); REDISMODULE_API int (*RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos); REDISMODULE_API int (*RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data); REDISMODULE_API int (*RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode); REDISMODULE_API int (*RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid); REDISMODULE_API int (*RedisModule_ExitFromChild)(int retcode) REDISMODULE_ATTR;
float REDISMODULE_API_FUNC(RedisModule_GetUsedMemoryRatio)(); REDISMODULE_API int (*RedisModule_KillForkChild)(int child_pid) REDISMODULE_ATTR;
size_t REDISMODULE_API_FUNC(RedisModule_MallocSize)(void* ptr); REDISMODULE_API float (*RedisModule_GetUsedMemoryRatio)() REDISMODULE_ATTR;
RedisModuleUser *REDISMODULE_API_FUNC(RedisModule_CreateModuleUser)(const char *name); REDISMODULE_API size_t (*RedisModule_MallocSize)(void* ptr) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_FreeModuleUser)(RedisModuleUser *user); REDISMODULE_API RedisModuleUser * (*RedisModule_CreateModuleUser)(const char *name) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_SetModuleUserACL)(RedisModuleUser *user, const char* acl); REDISMODULE_API void (*RedisModule_FreeModuleUser)(RedisModuleUser *user) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id); REDISMODULE_API int (*RedisModule_SetModuleUserACL)(RedisModuleUser *user, const char* acl) REDISMODULE_ATTR;
int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id); REDISMODULE_API int (*RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR;
void REDISMODULE_API_FUNC(RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id); REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR;
#endif #endif
#define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF) #define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF)
/* This is included inline inside each Redis module. */ /* This is included inline inside each Redis module. */
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused)); static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR_UNUSED;
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
void *getapifuncptr = ((void**)ctx)[0]; void *getapifuncptr = ((void**)ctx)[0];
RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr; RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr;
@ -811,6 +837,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(LatencyAddSample); REDISMODULE_GET_API(LatencyAddSample);
REDISMODULE_GET_API(StringAppendBuffer); REDISMODULE_GET_API(StringAppendBuffer);
REDISMODULE_GET_API(RetainString); REDISMODULE_GET_API(RetainString);
REDISMODULE_GET_API(HoldString);
REDISMODULE_GET_API(StringCompare); REDISMODULE_GET_API(StringCompare);
REDISMODULE_GET_API(GetContextFromIO); REDISMODULE_GET_API(GetContextFromIO);
REDISMODULE_GET_API(GetKeyNameFromIO); REDISMODULE_GET_API(GetKeyNameFromIO);
@ -877,6 +904,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(GetThreadSafeContext); REDISMODULE_GET_API(GetThreadSafeContext);
REDISMODULE_GET_API(FreeThreadSafeContext); REDISMODULE_GET_API(FreeThreadSafeContext);
REDISMODULE_GET_API(ThreadSafeContextLock); REDISMODULE_GET_API(ThreadSafeContextLock);
REDISMODULE_GET_API(ThreadSafeContextTryLock);
REDISMODULE_GET_API(ThreadSafeContextUnlock); REDISMODULE_GET_API(ThreadSafeContextUnlock);
REDISMODULE_GET_API(BlockClient); REDISMODULE_GET_API(BlockClient);
REDISMODULE_GET_API(UnblockClient); REDISMODULE_GET_API(UnblockClient);

View File

@ -1609,6 +1609,16 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type)
} else if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_END) { } else if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
struct redis_stat buf; struct redis_stat buf;
if (bgsaveerr != C_OK) {
ul.unlock();
if (FCorrectThread(replica))
freeClient(replica);
else
freeClientAsync(replica);
serverLog(LL_WARNING,"SYNC failed. BGSAVE child returned an error");
continue;
}
/* If this was an RDB on disk save, we have to prepare to send /* If this was an RDB on disk save, we have to prepare to send
* the RDB from disk to the replica socket. Otherwise if this was * the RDB from disk to the replica socket. Otherwise if this was
* already an RDB -> Slaves socket transfer, used in the case of * already an RDB -> Slaves socket transfer, used in the case of
@ -1647,15 +1657,6 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type)
replica->repl_put_online_on_ack = 1; replica->repl_put_online_on_ack = 1;
replica->repl_ack_time = g_pserver->unixtime; /* Timeout otherwise. */ replica->repl_ack_time = g_pserver->unixtime; /* Timeout otherwise. */
} else { } else {
if (bgsaveerr != C_OK) {
ul.unlock();
if (FCorrectThread(replica))
freeClient(replica);
else
freeClientAsync(replica);
serverLog(LL_WARNING,"SYNC failed. BGSAVE child returned an error");
continue;
}
if ((replica->repldbfd = open(g_pserver->rdb_filename,O_RDONLY)) == -1 || if ((replica->repldbfd = open(g_pserver->rdb_filename,O_RDONLY)) == -1 ||
redis_fstat(replica->repldbfd,&buf) == -1) { redis_fstat(replica->repldbfd,&buf) == -1) {
ul.unlock(); ul.unlock();
@ -2645,6 +2646,7 @@ void syncWithMaster(connection *conn) {
* both. */ * both. */
if (err[0] != '+' && if (err[0] != '+' &&
strncmp(err,"-NOAUTH",7) != 0 && strncmp(err,"-NOAUTH",7) != 0 &&
strncmp(err,"-NOPERM",7) != 0 &&
strncmp(err,"-ERR operation not permitted",28) != 0) strncmp(err,"-ERR operation not permitted",28) != 0)
{ {
serverLog(LL_WARNING,"Error reply to PING from master: '%s'",err); serverLog(LL_WARNING,"Error reply to PING from master: '%s'",err);
@ -3049,6 +3051,9 @@ struct redisMaster *replicationAddMaster(char *ip, int port) {
} }
disconnectAllBlockedClients(); /* Clients blocked in master, now replica. */ disconnectAllBlockedClients(); /* Clients blocked in master, now replica. */
/* Update oom_score_adj */
setOOMScoreAdj(-1);
/* Force our slaves to resync with us as well. They may hopefully be able /* Force our slaves to resync with us as well. They may hopefully be able
* to partially resync with us, but we can notify the replid change. */ * to partially resync with us, but we can notify the replid change. */
if (!g_pserver->fActiveReplica) if (!g_pserver->fActiveReplica)
@ -3145,6 +3150,9 @@ void replicationUnsetMaster(redisMaster *mi) {
listDelNode(g_pserver->masters, ln); listDelNode(g_pserver->masters, ln);
freeMasterInfo(mi); freeMasterInfo(mi);
/* Update oom_score_adj */
setOOMScoreAdj(-1);
/* Fire the role change modules event. */ /* Fire the role change modules event. */
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED, moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER, REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER,
@ -3487,6 +3495,11 @@ void replicationResurrectCachedMaster(redisMaster *mi, connection *conn) {
but this client was unlinked so its OK here */ but this client was unlinked so its OK here */
mi->master->iel = serverTL - g_pserver->rgthreadvar; // martial to this thread mi->master->iel = serverTL - g_pserver->rgthreadvar; // martial to this thread
/* Fire the master link modules event. */
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
REDISMODULE_SUBEVENT_MASTER_LINK_UP,
NULL);
/* Re-add to the list of clients. */ /* Re-add to the list of clients. */
linkClient(mi->master); linkClient(mi->master);
serverAssert(connGetPrivateData(mi->master->conn) == mi->master); serverAssert(connGetPrivateData(mi->master->conn) == mi->master);

View File

@ -1078,6 +1078,7 @@ int sentinelTryConnectionSharing(sentinelRedisInstance *ri) {
releaseInstanceLink(ri->link,NULL); releaseInstanceLink(ri->link,NULL);
ri->link = match->link; ri->link = match->link;
match->link->refcount++; match->link->refcount++;
dictReleaseIterator(di);
return C_OK; return C_OK;
} }
dictReleaseIterator(di); dictReleaseIterator(di);
@ -1955,7 +1956,7 @@ void sentinelFlushConfig(void) {
int rewrite_status; int rewrite_status;
g_pserver->hz = CONFIG_DEFAULT_HZ; g_pserver->hz = CONFIG_DEFAULT_HZ;
rewrite_status = rewriteConfig(cserver.configfile); rewrite_status = rewriteConfig(cserver.configfile, 0);
g_pserver->hz = saved_hz; g_pserver->hz = saved_hz;
if (rewrite_status == -1) goto werr; if (rewrite_status == -1) goto werr;
@ -2219,8 +2220,8 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
} }
/* role:<role> */ /* role:<role> */
if (!memcmp(l,"role:master",11)) role = SRI_MASTER; if (sdslen(l) >= 11 && !memcmp(l,"role:master",11)) role = SRI_MASTER;
else if (!memcmp(l,"role:slave",10)) role = SRI_SLAVE; else if (sdslen(l) >= 10 && !memcmp(l,"role:slave",10)) role = SRI_SLAVE;
if (role == SRI_SLAVE) { if (role == SRI_SLAVE) {
/* master_host:<host> */ /* master_host:<host> */

View File

@ -63,6 +63,9 @@
#include <mutex> #include <mutex>
#include "aelocker.h" #include "aelocker.h"
#include "motd.h" #include "motd.h"
#ifdef __linux__
#include <sys/prctl.h>
#endif
int g_fTestMode = false; int g_fTestMode = false;
const char *motd_url = "http://api.keydb.dev/motd/motd_server.txt"; const char *motd_url = "http://api.keydb.dev/motd/motd_server.txt";
@ -351,6 +354,10 @@ struct redisCommand redisCommandTable[] = {
"write @list", "write @list",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},
{"lpos",lposCommand,-3,
"read-only @list",
0,NULL,1,1,1,0,0,0},
{"lrem",lremCommand,4, {"lrem",lremCommand,4,
"write @list", "write @list",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},
@ -2564,6 +2571,10 @@ void initServerConfig(void) {
for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++)
cserver.client_obuf_limits[j] = clientBufferLimitsDefaults[j]; cserver.client_obuf_limits[j] = clientBufferLimitsDefaults[j];
/* Linux OOM Score config */
for (j = 0; j < CONFIG_OOM_COUNT; j++)
g_pserver->oom_score_adj_values[j] = configOOMScoreAdjValuesDefaults[j];
/* Double constants initialization */ /* Double constants initialization */
R_Zero = 0.0; R_Zero = 0.0;
R_PosInf = 1.0/R_Zero; R_PosInf = 1.0/R_Zero;
@ -2645,7 +2656,7 @@ int restartServer(int flags, mstime_t delay) {
/* Config rewriting. */ /* Config rewriting. */
if (flags & RESTART_SERVER_CONFIG_REWRITE && if (flags & RESTART_SERVER_CONFIG_REWRITE &&
cserver.configfile && cserver.configfile &&
rewriteConfig(cserver.configfile) == -1) rewriteConfig(cserver.configfile, 0) == -1)
{ {
serverLog(LL_WARNING,"Can't restart: configuration rewrite process " serverLog(LL_WARNING,"Can't restart: configuration rewrite process "
"failed"); "failed");
@ -2690,6 +2701,59 @@ int restartServer(int flags, mstime_t delay) {
return C_ERR; /* Never reached. */ return C_ERR; /* Never reached. */
} }
static void readOOMScoreAdj(void) {
#ifdef HAVE_PROC_OOM_SCORE_ADJ
char buf[64];
int fd = open("/proc/self/oom_score_adj", O_RDONLY);
if (fd < 0) return;
if (read(fd, buf, sizeof(buf)) > 0)
g_pserver->oom_score_adj_base = atoi(buf);
close(fd);
#endif
}
/* This function will configure the current process's oom_score_adj according
* to user specified configuration. This is currently implemented on Linux
* only.
*
* A process_class value of -1 implies OOM_CONFIG_MASTER or OOM_CONFIG_REPLICA,
* depending on current role.
*/
int setOOMScoreAdj(int process_class) {
if (!g_pserver->oom_score_adj) return C_OK;
if (process_class == -1)
process_class = (listLength(g_pserver->masters) ? CONFIG_OOM_REPLICA : CONFIG_OOM_MASTER);
serverAssert(process_class >= 0 && process_class < CONFIG_OOM_COUNT);
#ifdef HAVE_PROC_OOM_SCORE_ADJ
int fd;
int val;
char buf[64];
val = g_pserver->oom_score_adj_base + g_pserver->oom_score_adj_values[process_class];
if (val > 1000) val = 1000;
if (val < -1000) val = -1000;
snprintf(buf, sizeof(buf) - 1, "%d\n", val);
fd = open("/proc/self/oom_score_adj", O_WRONLY);
if (fd < 0 || write(fd, buf, strlen(buf)) < 0) {
serverLog(LOG_WARNING, "Unable to write oom_score_adj: %s", strerror(errno));
if (fd != -1) close(fd);
return C_ERR;
}
close(fd);
return C_OK;
#else
/* Unsupported */
return C_ERR;
#endif
}
/* This function will try to raise the max number of open files accordingly to /* This function will try to raise the max number of open files accordingly to
* the configured max number of clients. It also reserves a number of file * the configured max number of clients. It also reserves a number of file
* descriptors (CONFIG_MIN_RESERVED_FDS) for extra operations of * descriptors (CONFIG_MIN_RESERVED_FDS) for extra operations of
@ -3080,7 +3144,8 @@ void initServer(void) {
g_pserver->get_ack_from_slaves = 0; g_pserver->get_ack_from_slaves = 0;
cserver.system_memory_size = zmalloc_get_memory_size(); cserver.system_memory_size = zmalloc_get_memory_size();
if (g_pserver->tls_port && tlsConfigure(&g_pserver->tls_ctx_config) == C_ERR) { if ((g_pserver->tls_port || g_pserver->tls_replication || g_pserver->tls_cluster)
&& tlsConfigure(&g_pserver->tls_ctx_config) == C_ERR) {
serverLog(LL_WARNING, "Failed to configure TLS. Check logs for more info."); serverLog(LL_WARNING, "Failed to configure TLS. Check logs for more info.");
exit(1); exit(1);
} }
@ -3641,6 +3706,38 @@ void call(client *c, int flags) {
serverTL->fixed_time_expire--; serverTL->fixed_time_expire--;
} }
/* Used when a command that is ready for execution needs to be rejected, due to
* varios pre-execution checks. it returns the appropriate error to the client.
* If there's a transaction is flags it as dirty, and if the command is EXEC,
* it aborts the transaction.
* Note: 'reply' is expected to end with \r\n */
void rejectCommand(client *c, robj *reply) {
flagTransaction(c);
if (c->cmd && c->cmd->proc == execCommand) {
execCommandAbort(c, szFromObj(reply));
} else {
/* using addReplyError* rather than addReply so that the error can be logged. */
addReplyErrorObject(c, reply);
}
}
void rejectCommandFormat(client *c, const char *fmt, ...) {
flagTransaction(c);
va_list ap;
va_start(ap,fmt);
sds s = sdscatvprintf(sdsempty(),fmt,ap);
va_end(ap);
/* Make sure there are no newlines in the string, otherwise invalid protocol
* is emitted (The args come from the user, they may contain any character). */
sdsmapchars(s, "\r\n", " ", 2);
if (c->cmd && c->cmd->proc == execCommand) {
execCommandAbort(c, s);
} else {
addReplyErrorSds(c, s);
}
sdsfree(s);
}
/* If this function gets called we already read a whole /* If this function gets called we already read a whole
* command, arguments are in the client argv/argc fields. * command, arguments are in the client argv/argc fields.
* processCommand() execute the command or prepare the * processCommand() execute the command or prepare the
@ -3672,23 +3769,30 @@ int processCommand(client *c, int callFlags) {
* such as wrong arity, bad command name and so forth. */ * such as wrong arity, bad command name and so forth. */
c->cmd = c->lastcmd = lookupCommand((sds)ptrFromObj(c->argv[0])); c->cmd = c->lastcmd = lookupCommand((sds)ptrFromObj(c->argv[0]));
if (!c->cmd) { if (!c->cmd) {
flagTransaction(c);
sds args = sdsempty(); sds args = sdsempty();
int i; int i;
for (i=1; i < c->argc && sdslen(args) < 128; i++) for (i=1; i < c->argc && sdslen(args) < 128; i++)
args = sdscatprintf(args, "`%.*s`, ", 128-(int)sdslen(args), (char*)ptrFromObj(c->argv[i])); args = sdscatprintf(args, "`%.*s`, ", 128-(int)sdslen(args), (char*)ptrFromObj(c->argv[i]));
addReplyErrorFormat(c,"unknown command `%s`, with args beginning with: %s", rejectCommandFormat(c,"unknown command `%s`, with args beginning with: %s",
(char*)ptrFromObj(c->argv[0]), args); (char*)ptrFromObj(c->argv[0]), args);
sdsfree(args); sdsfree(args);
return C_OK; return C_OK;
} else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) || } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
(c->argc < -c->cmd->arity)) { (c->argc < -c->cmd->arity)) {
flagTransaction(c); rejectCommandFormat(c,"wrong number of arguments for '%s' command",
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
c->cmd->name); c->cmd->name);
return C_OK; return C_OK;
} }
int is_write_command = (c->cmd->flags & CMD_WRITE) ||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
int is_denyoom_command = (c->cmd->flags & CMD_DENYOOM) ||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_DENYOOM));
int is_denystale_command = !(c->cmd->flags & CMD_STALE) ||
(c->cmd->proc == execCommand && (c->mstate.cmd_inv_flags & CMD_STALE));
int is_denyloading_command = !(c->cmd->flags & CMD_LOADING) ||
(c->cmd->proc == execCommand && (c->mstate.cmd_inv_flags & CMD_LOADING));
/* Check if the user is authenticated. This check is skipped in case /* Check if the user is authenticated. This check is skipped in case
* the default user is flagged as "nopass" and is active. */ * the default user is flagged as "nopass" and is active. */
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) || int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
@ -3698,8 +3802,7 @@ int processCommand(client *c, int callFlags) {
/* AUTH and HELLO and no auth modules are valid even in /* AUTH and HELLO and no auth modules are valid even in
* non-authenticated state. */ * non-authenticated state. */
if (!(c->cmd->flags & CMD_NO_AUTH)) { if (!(c->cmd->flags & CMD_NO_AUTH)) {
flagTransaction(c); rejectCommand(c,shared.noautherr);
addReply(c,shared.noautherr);
return C_OK; return C_OK;
} }
} }
@ -3710,13 +3813,12 @@ int processCommand(client *c, int callFlags) {
int acl_retval = ACLCheckCommandPerm(c,&acl_keypos); int acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
if (acl_retval != ACL_OK) { if (acl_retval != ACL_OK) {
addACLLogEntry(c,acl_retval,acl_keypos,NULL); addACLLogEntry(c,acl_retval,acl_keypos,NULL);
flagTransaction(c);
if (acl_retval == ACL_DENIED_CMD) if (acl_retval == ACL_DENIED_CMD)
addReplyErrorFormat(c, rejectCommandFormat(c,
"-NOPERM this user has no permissions to run " "-NOPERM this user has no permissions to run "
"the '%s' command or its subcommand", c->cmd->name); "the '%s' command or its subcommand", c->cmd->name);
else else
addReplyErrorFormat(c, rejectCommandFormat(c,
"-NOPERM this user has no permissions to access " "-NOPERM this user has no permissions to access "
"one of the keys used as arguments"); "one of the keys used as arguments");
return C_OK; return C_OK;
@ -3762,17 +3864,20 @@ int processCommand(client *c, int callFlags) {
* into a replica, that may be the active client, to be freed. */ * into a replica, that may be the active client, to be freed. */
if (serverTL->current_client == NULL) return C_ERR; if (serverTL->current_client == NULL) return C_ERR;
/* It was impossible to free enough memory, and the command the client int reject_cmd_on_oom = is_denyoom_command;
* is trying to execute is denied during OOM conditions or the client /* If client is in MULTI/EXEC context, queuing may consume an unlimited
* is in MULTI/EXEC context? Error. */ * amount of memory, so we want to stop that.
if (out_of_memory && * However, we never want to reject DISCARD, or even EXEC (unless it
(c->cmd->flags & CMD_DENYOOM || * contains denied commands, in which case is_denyoom_command is already
(c->flags & CLIENT_MULTI && * set. */
c->cmd->proc != execCommand && if (c->flags & CLIENT_MULTI &&
c->cmd->proc != discardCommand))) c->cmd->proc != execCommand &&
{ c->cmd->proc != discardCommand) {
flagTransaction(c); reject_cmd_on_oom = 1;
addReply(c, shared.oomerr); }
if (out_of_memory && reject_cmd_on_oom) {
rejectCommand(c, shared.oomerr);
return C_OK; return C_OK;
} }
@ -3793,17 +3898,14 @@ int processCommand(client *c, int callFlags) {
int deny_write_type = writeCommandsDeniedByDiskError(); int deny_write_type = writeCommandsDeniedByDiskError();
if (deny_write_type != DISK_ERROR_TYPE_NONE && if (deny_write_type != DISK_ERROR_TYPE_NONE &&
listLength(g_pserver->masters) == 0 && listLength(g_pserver->masters) == 0 &&
(c->cmd->flags & CMD_WRITE || (is_write_command ||c->cmd->proc == pingCommand))
c->cmd->proc == pingCommand))
{ {
flagTransaction(c);
if (deny_write_type == DISK_ERROR_TYPE_RDB) if (deny_write_type == DISK_ERROR_TYPE_RDB)
addReply(c, shared.bgsaveerr); rejectCommand(c, shared.bgsaveerr);
else else
addReplySds(c, rejectCommandFormat(c,
sdscatprintf(sdsempty(), "-MISCONF Errors writing to the AOF file: %s",
"-MISCONF Errors writing to the AOF file: %s\r\n", strerror(g_pserver->aof_last_write_errno));
strerror(g_pserver->aof_last_write_errno)));
return C_OK; return C_OK;
} }
@ -3812,11 +3914,10 @@ int processCommand(client *c, int callFlags) {
if (listLength(g_pserver->masters) == 0 && if (listLength(g_pserver->masters) == 0 &&
g_pserver->repl_min_slaves_to_write && g_pserver->repl_min_slaves_to_write &&
g_pserver->repl_min_slaves_max_lag && g_pserver->repl_min_slaves_max_lag &&
c->cmd->flags & CMD_WRITE && is_write_command &&
g_pserver->repl_good_slaves_count < g_pserver->repl_min_slaves_to_write) g_pserver->repl_good_slaves_count < g_pserver->repl_min_slaves_to_write)
{ {
flagTransaction(c); rejectCommand(c, shared.noreplicaserr);
addReply(c, shared.noreplicaserr);
return C_OK; return C_OK;
} }
@ -3824,10 +3925,9 @@ int processCommand(client *c, int callFlags) {
* accept write commands if this is our master. */ * accept write commands if this is our master. */
if (listLength(g_pserver->masters) && g_pserver->repl_slave_ro && if (listLength(g_pserver->masters) && g_pserver->repl_slave_ro &&
!(c->flags & CLIENT_MASTER) && !(c->flags & CLIENT_MASTER) &&
c->cmd->flags & CMD_WRITE) is_write_command)
{ {
flagTransaction(c); rejectCommand(c, shared.roslaveerr);
addReply(c, shared.roslaveerr);
return C_OK; return C_OK;
} }
@ -3839,7 +3939,7 @@ int processCommand(client *c, int callFlags) {
c->cmd->proc != unsubscribeCommand && c->cmd->proc != unsubscribeCommand &&
c->cmd->proc != psubscribeCommand && c->cmd->proc != psubscribeCommand &&
c->cmd->proc != punsubscribeCommand) { c->cmd->proc != punsubscribeCommand) {
addReplyErrorFormat(c, rejectCommandFormat(c,
"Can't execute '%s': only (P)SUBSCRIBE / " "Can't execute '%s': only (P)SUBSCRIBE / "
"(P)UNSUBSCRIBE / PING / QUIT are allowed in this context", "(P)UNSUBSCRIBE / PING / QUIT are allowed in this context",
c->cmd->name); c->cmd->name);
@ -3851,21 +3951,20 @@ int processCommand(client *c, int callFlags) {
* link with master. */ * link with master. */
if (FBrokenLinkToMaster() && if (FBrokenLinkToMaster() &&
g_pserver->repl_serve_stale_data == 0 && g_pserver->repl_serve_stale_data == 0 &&
!(c->cmd->flags & CMD_STALE) is_denystale_command &&
&& !(g_pserver->fActiveReplica && c->cmd->proc == syncCommand)) !(g_pserver->fActiveReplica && c->cmd->proc == syncCommand))
{ {
flagTransaction(c); rejectCommand(c, shared.masterdownerr);
addReply(c, shared.masterdownerr);
return C_OK; return C_OK;
} }
/* Loading DB? Return an error if the command has not the /* Loading DB? Return an error if the command has not the
* CMD_LOADING flag. */ * CMD_LOADING flag. */
if (g_pserver->loading && !(c->cmd->flags & CMD_LOADING)) { if (g_pserver->loading && is_denyloading_command) {
/* Active Replicas can execute read only commands, and optionally write commands */ /* Active Replicas can execute read only commands, and optionally write commands */
if (!(g_pserver->loading == LOADING_REPLICATION && g_pserver->fActiveReplica && ((c->cmd->flags & CMD_READONLY) || g_pserver->fWriteDuringActiveLoad))) if (!(g_pserver->loading == LOADING_REPLICATION && g_pserver->fActiveReplica && ((c->cmd->flags & CMD_READONLY) || g_pserver->fWriteDuringActiveLoad)))
{ {
addReply(c, shared.loadingerr); rejectCommand(c, shared.loadingerr);
return C_OK; return C_OK;
} }
} }
@ -3881,7 +3980,6 @@ int processCommand(client *c, int callFlags) {
c->cmd->proc != helloCommand && c->cmd->proc != helloCommand &&
c->cmd->proc != replconfCommand && c->cmd->proc != replconfCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != multiCommand &&
c->cmd->proc != execCommand &&
c->cmd->proc != discardCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != watchCommand && c->cmd->proc != watchCommand &&
c->cmd->proc != unwatchCommand && c->cmd->proc != unwatchCommand &&
@ -3892,8 +3990,7 @@ int processCommand(client *c, int callFlags) {
c->argc == 2 && c->argc == 2 &&
tolower(((char*)ptrFromObj(c->argv[1]))[0]) == 'k')) tolower(((char*)ptrFromObj(c->argv[1]))[0]) == 'k'))
{ {
flagTransaction(c); rejectCommand(c, shared.slowscripterr);
addReply(c, shared.slowscripterr);
return C_OK; return C_OK;
} }
@ -3946,6 +4043,15 @@ void closeListeningSockets(int unlink_unix_socket) {
} }
int prepareForShutdown(int flags) { int prepareForShutdown(int flags) {
/* When SHUTDOWN is called while the server is loading a dataset in
* memory we need to make sure no attempt is performed to save
* the dataset on shutdown (otherwise it could overwrite the current DB
* with half-read data).
*
* Also when in Sentinel mode clear the SAVE flag and force NOSAVE. */
if (g_pserver->loading || g_pserver->sentinel_mode)
flags = (flags & ~SHUTDOWN_SAVE) | SHUTDOWN_NOSAVE;
int save = flags & SHUTDOWN_SAVE; int save = flags & SHUTDOWN_SAVE;
int nosave = flags & SHUTDOWN_NOSAVE; int nosave = flags & SHUTDOWN_NOSAVE;
@ -4613,7 +4719,9 @@ sds genRedisInfoString(const char *section) {
"tracking_total_keys:%lld\r\n" "tracking_total_keys:%lld\r\n"
"tracking_total_items:%llu\r\n" "tracking_total_items:%llu\r\n"
"tracking_total_prefixes:%lld\r\n" "tracking_total_prefixes:%lld\r\n"
"unexpected_error_replies:%lld\r\n", "unexpected_error_replies:%lld\r\n"
"total_reads_processed:%lld\r\n"
"total_writes_processed:%lld\r\n",
g_pserver->stat_numconnections, g_pserver->stat_numconnections,
g_pserver->stat_numcommands, g_pserver->stat_numcommands,
getInstantaneousMetric(STATS_METRIC_COMMAND), getInstantaneousMetric(STATS_METRIC_COMMAND),
@ -4644,7 +4752,9 @@ sds genRedisInfoString(const char *section) {
(unsigned long long) trackingGetTotalKeys(), (unsigned long long) trackingGetTotalKeys(),
(unsigned long long) trackingGetTotalItems(), (unsigned long long) trackingGetTotalItems(),
(unsigned long long) trackingGetTotalPrefixes(), (unsigned long long) trackingGetTotalPrefixes(),
g_pserver->stat_unexpected_error_replies); g_pserver->stat_unexpected_error_replies,
g_pserver->stat_total_reads_processed.load(std::memory_order_relaxed),
g_pserver->stat_total_writes_processed.load(std::memory_order_relaxed));
} }
/* Replication */ /* Replication */
@ -4929,7 +5039,7 @@ void linuxMemoryWarnings(void) {
serverLog(LL_WARNING,"WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect."); serverLog(LL_WARNING,"WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.");
} }
if (THPIsEnabled()) { if (THPIsEnabled()) {
serverLog(LL_WARNING,"WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with KeyDB. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. KeyDB must be restarted after THP is disabled."); serverLog(LL_WARNING,"WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with KeyDB. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. KeyDB must be restarted after THP is disabled (set to 'madvise' or 'never').");
} }
} }
#endif /* __linux__ */ #endif /* __linux__ */
@ -5108,13 +5218,24 @@ void setupChildSignalHandlers(void) {
return; return;
} }
/* After fork, the child process will inherit the resources
* of the parent process, e.g. fd(socket or flock) etc.
* should close the resources not used by the child process, so that if the
* parent restarts it can bind/lock despite the child possibly still running. */
void closeClildUnusedResourceAfterFork() {
closeListeningSockets(0);
if (g_pserver->cluster_enabled && g_pserver->cluster_config_file_lock_fd != -1)
close(g_pserver->cluster_config_file_lock_fd); /* don't care if this fails */
}
int redisFork() { int redisFork() {
int childpid; int childpid;
long long start = ustime(); long long start = ustime();
if ((childpid = fork()) == 0) { if ((childpid = fork()) == 0) {
/* Child */ /* Child */
closeListeningSockets(0); setOOMScoreAdj(CONFIG_OOM_BGCHILD);
setupChildSignalHandlers(); setupChildSignalHandlers();
closeClildUnusedResourceAfterFork();
} else { } else {
/* Parent */ /* Parent */
g_pserver->stat_fork_time = ustime()-start; g_pserver->stat_fork_time = ustime()-start;
@ -5162,6 +5283,7 @@ void loadDataFromDisk(void) {
serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000); serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
} else if (g_pserver->rdb_filename != NULL || g_pserver->rdb_s3bucketpath != NULL) { } else if (g_pserver->rdb_filename != NULL || g_pserver->rdb_s3bucketpath != NULL) {
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
errno = 0; /* Prevent a stale value from affecting error checking */
if (rdbLoad(&rsi,RDBFLAGS_NONE) == C_OK) { if (rdbLoad(&rsi,RDBFLAGS_NONE) == C_OK) {
serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds", serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds",
(float)(ustime()-start)/1000000); (float)(ustime()-start)/1000000);
@ -5203,7 +5325,8 @@ void loadDataFromDisk(void) {
void redisOutOfMemoryHandler(size_t allocation_size) { void redisOutOfMemoryHandler(size_t allocation_size) {
serverLog(LL_WARNING,"Out Of Memory allocating %zu bytes!", serverLog(LL_WARNING,"Out Of Memory allocating %zu bytes!",
allocation_size); allocation_size);
serverPanic("Redis aborting for OUT OF MEMORY"); serverPanic("Redis aborting for OUT OF MEMORY. Allocating %zu bytes!",
allocation_size);
} }
void fuzzOutOfMemoryHandler(size_t allocation_size) { void fuzzOutOfMemoryHandler(size_t allocation_size) {
@ -5540,6 +5663,10 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
cserver.supervised = redisIsSupervised(cserver.supervised_mode);
int background = cserver.daemonize && !cserver.supervised;
if (background) daemonize();
serverLog(LL_WARNING, "oO0OoO0OoO0Oo KeyDB is starting oO0OoO0OoO0Oo"); serverLog(LL_WARNING, "oO0OoO0OoO0Oo KeyDB is starting oO0OoO0OoO0Oo");
serverLog(LL_WARNING, serverLog(LL_WARNING,
"KeyDB version=%s, bits=%d, commit=%s, modified=%d, pid=%d, just started", "KeyDB version=%s, bits=%d, commit=%s, modified=%d, pid=%d, just started",
@ -5557,14 +5684,11 @@ int main(int argc, char **argv) {
validateConfiguration(); validateConfiguration();
cserver.supervised = redisIsSupervised(cserver.supervised_mode);
int background = cserver.daemonize && !cserver.supervised;
if (background) daemonize();
for (int iel = 0; iel < MAX_EVENT_LOOPS; ++iel) for (int iel = 0; iel < MAX_EVENT_LOOPS; ++iel)
{ {
initServerThread(g_pserver->rgthreadvar+iel, iel == IDX_EVENT_LOOP_MAIN); initServerThread(g_pserver->rgthreadvar+iel, iel == IDX_EVENT_LOOP_MAIN);
} }
readOOMScoreAdj();
initServer(); initServer();
initNetworking(cserver.cthreads > 1 /* fReusePort */); initNetworking(cserver.cthreads > 1 /* fReusePort */);
@ -5623,6 +5747,10 @@ int main(int argc, char **argv) {
} else { } else {
InitServerLast(); InitServerLast();
sentinelIsRunning(); sentinelIsRunning();
if (cserver.supervised_mode == SUPERVISED_SYSTEMD) {
redisCommunicateSystemd("STATUS=Ready to accept connections\n");
redisCommunicateSystemd("READY=1\n");
}
} }
if (g_pserver->rdb_filename == nullptr) if (g_pserver->rdb_filename == nullptr)
@ -5646,6 +5774,7 @@ int main(int argc, char **argv) {
aeReleaseLock(); //Finally we can dump the lock aeReleaseLock(); //Finally we can dump the lock
moduleReleaseGIL(true); moduleReleaseGIL(true);
setOOMScoreAdj(-1);
serverAssert(cserver.cthreads > 0 && cserver.cthreads <= MAX_EVENT_LOOPS); serverAssert(cserver.cthreads > 0 && cserver.cthreads <= MAX_EVENT_LOOPS);
pthread_t rgthread[MAX_EVENT_LOOPS]; pthread_t rgthread[MAX_EVENT_LOOPS];
for (int iel = 0; iel < cserver.cthreads; ++iel) for (int iel = 0; iel < cserver.cthreads; ++iel)

View File

@ -340,6 +340,14 @@ inline bool operator!=(const void *p, const robj_sharedptr &rhs)
* in order to make sure of not over provisioning more than 128 fds. */ * in order to make sure of not over provisioning more than 128 fds. */
#define CONFIG_FDSET_INCR (CONFIG_MIN_RESERVED_FDS+96) #define CONFIG_FDSET_INCR (CONFIG_MIN_RESERVED_FDS+96)
/* OOM Score Adjustment classes. */
#define CONFIG_OOM_MASTER 0
#define CONFIG_OOM_REPLICA 1
#define CONFIG_OOM_BGCHILD 2
#define CONFIG_OOM_COUNT 3
extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
/* Hash table parameters */ /* Hash table parameters */
#define HASHTABLE_MIN_FILL 10 /* Minimal hash table fill 10% */ #define HASHTABLE_MIN_FILL 10 /* Minimal hash table fill 10% */
@ -554,6 +562,11 @@ inline bool operator!=(const void *p, const robj_sharedptr &rhs)
#define REPL_DISKLESS_LOAD_WHEN_DB_EMPTY 1 #define REPL_DISKLESS_LOAD_WHEN_DB_EMPTY 1
#define REPL_DISKLESS_LOAD_SWAPDB 2 #define REPL_DISKLESS_LOAD_SWAPDB 2
/* TLS Client Authentication */
#define TLS_CLIENT_AUTH_NO 0
#define TLS_CLIENT_AUTH_YES 1
#define TLS_CLIENT_AUTH_OPTIONAL 2
/* Sets operations codes */ /* Sets operations codes */
#define SET_OP_UNION 0 #define SET_OP_UNION 0
#define SET_OP_DIFF 1 #define SET_OP_DIFF 1
@ -622,6 +635,7 @@ inline bool operator!=(const void *p, const robj_sharedptr &rhs)
#define NOTIFY_EVICTED (1<<9) /* e */ #define NOTIFY_EVICTED (1<<9) /* e */
#define NOTIFY_STREAM (1<<10) /* t */ #define NOTIFY_STREAM (1<<10) /* t */
#define NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from NOTIFY_ALL on purpose) */ #define NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from NOTIFY_ALL on purpose) */
#define NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */ #define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */
/* Get the first bind addr or NULL */ /* Get the first bind addr or NULL */
@ -933,6 +947,9 @@ typedef struct multiState {
int cmd_flags; /* The accumulated command flags OR-ed together. int cmd_flags; /* The accumulated command flags OR-ed together.
So if at least a command has a given flag, it So if at least a command has a given flag, it
will be set in this field. */ will be set in this field. */
int cmd_inv_flags; /* Same as cmd_flags, OR-ing the ~flags. so that it
is possible to know if all the commands have a
certain flag. */
int minreplicas; /* MINREPLICAS for synchronous replication */ int minreplicas; /* MINREPLICAS for synchronous replication */
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */ time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
} multiState; } multiState;
@ -1535,6 +1552,10 @@ struct redisServer {
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */ size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */ uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */ long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
long long stat_io_reads_processed; /* Number of read events processed by IO / Main threads */
long long stat_io_writes_processed; /* Number of write events processed by IO / Main threads */
std::atomic<long long> stat_total_reads_processed; /* Total number of read events processed */
std::atomic<long long> stat_total_writes_processed; /* Total number of write events processed */
/* The following two are used to track instantaneous metrics, like /* The following two are used to track instantaneous metrics, like
* number of operations per second, network traffic. */ * number of operations per second, network traffic. */
struct { struct {
@ -1684,6 +1705,9 @@ struct redisServer {
int lfu_log_factor; /* LFU logarithmic counter factor. */ int lfu_log_factor; /* LFU logarithmic counter factor. */
int lfu_decay_time; /* LFU counter decay factor. */ int lfu_decay_time; /* LFU counter decay factor. */
long long proto_max_bulk_len; /* Protocol bulk length maximum size. */ long long proto_max_bulk_len; /* Protocol bulk length maximum size. */
int oom_score_adj_base; /* Base oom_score_adj value, as observed on startup */
int oom_score_adj_values[CONFIG_OOM_COUNT]; /* Linux oom_score_adj configuration */
int oom_score_adj; /* If true, oom_score_adj is managed */
/* Blocked clients */ /* Blocked clients */
unsigned int blocked_clients; /* # of clients executing a blocking cmd.*/ unsigned int blocked_clients; /* # of clients executing a blocking cmd.*/
unsigned int blocked_clients_by_type[BLOCKED_NUM]; unsigned int blocked_clients_by_type[BLOCKED_NUM];
@ -1741,6 +1765,7 @@ struct redisServer {
REDISMODULE_CLUSTER_FLAG_*. */ REDISMODULE_CLUSTER_FLAG_*. */
int cluster_allow_reads_when_down; /* Are reads allowed when the cluster int cluster_allow_reads_when_down; /* Are reads allowed when the cluster
is down? */ is down? */
int cluster_config_file_lock_fd; /* cluster config fd, will be flock */
/* Scripting */ /* Scripting */
lua_State *lua; /* The Lua interpreter. We use just one for all clients */ lua_State *lua; /* The Lua interpreter. We use just one for all clients */
client *lua_caller = nullptr; /* The client running EVAL right now, or NULL */ client *lua_caller = nullptr; /* The client running EVAL right now, or NULL */
@ -1932,6 +1957,7 @@ void moduleBlockedClientTimedOut(client *c);
void moduleBlockedClientPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask); void moduleBlockedClientPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask);
size_t moduleCount(void); size_t moduleCount(void);
void moduleAcquireGIL(int fServerThread); void moduleAcquireGIL(int fServerThread);
int moduleTryAcquireGIL(bool fServerThread);
void moduleReleaseGIL(int fServerThread); void moduleReleaseGIL(int fServerThread);
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid); void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
void moduleCallCommandFilters(client *c); void moduleCallCommandFilters(client *c);
@ -1995,6 +2021,8 @@ void addReplyBulkLongLong(client *c, long long ll);
void addReply(client *c, robj_roptr obj); void addReply(client *c, robj_roptr obj);
void addReplySds(client *c, sds s); void addReplySds(client *c, sds s);
void addReplyBulkSds(client *c, sds s); void addReplyBulkSds(client *c, sds s);
void addReplyErrorObject(client *c, robj *err);
void addReplyErrorSds(client *c, sds err);
void addReplyError(client *c, const char *err); void addReplyError(client *c, const char *err);
void addReplyStatus(client *c, const char *status); void addReplyStatus(client *c, const char *status);
void addReplyDouble(client *c, double d); void addReplyDouble(client *c, double d);
@ -2114,6 +2142,7 @@ void touchWatchedKey(redisDb *db, robj *key);
void touchWatchedKeysOnFlush(int dbid); void touchWatchedKeysOnFlush(int dbid);
void discardTransaction(client *c); void discardTransaction(client *c);
void flagTransaction(client *c); void flagTransaction(client *c);
void execCommandAbort(client *c, sds error);
void execCommandPropagateMulti(client *c); void execCommandPropagateMulti(client *c);
void execCommandPropagateExec(client *c); void execCommandPropagateExec(client *c);
@ -2389,6 +2418,7 @@ const char *evictPolicyToString(void);
struct redisMemOverhead *getMemoryOverheadData(void); struct redisMemOverhead *getMemoryOverheadData(void);
void freeMemoryOverheadData(struct redisMemOverhead *mh); void freeMemoryOverheadData(struct redisMemOverhead *mh);
void checkChildrenDone(void); void checkChildrenDone(void);
int setOOMScoreAdj(int process_class);
#define RESTART_SERVER_NONE 0 #define RESTART_SERVER_NONE 0
#define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */ #define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */
@ -2452,7 +2482,7 @@ void appendServerSaveParams(time_t seconds, int changes);
void resetServerSaveParams(void); void resetServerSaveParams(void);
struct rewriteConfigState; /* Forward declaration to export API. */ struct rewriteConfigState; /* Forward declaration to export API. */
void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force); void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force);
int rewriteConfig(char *path); int rewriteConfig(char *path, int force_all);
void initConfigValues(); void initConfigValues();
/* db.c -- Keyspace access API */ /* db.c -- Keyspace access API */
@ -2466,6 +2496,7 @@ expireEntry *getExpire(redisDb *db, robj_roptr key);
void setExpire(client *c, redisDb *db, robj *key, robj *subkey, long long when); void setExpire(client *c, redisDb *db, robj *key, robj *subkey, long long when);
void setExpire(client *c, redisDb *db, robj *key, expireEntry &&entry); void setExpire(client *c, redisDb *db, robj *key, expireEntry &&entry);
robj_roptr lookupKeyRead(redisDb *db, robj *key); robj_roptr lookupKeyRead(redisDb *db, robj *key);
int checkAlreadyExpired(long long when);
robj *lookupKeyWrite(redisDb *db, robj *key); robj *lookupKeyWrite(redisDb *db, robj *key);
robj_roptr lookupKeyReadOrReply(client *c, robj *key, robj *reply); robj_roptr lookupKeyReadOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply); robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
@ -2673,6 +2704,7 @@ void flushdbCommand(client *c);
void flushallCommand(client *c); void flushallCommand(client *c);
void sortCommand(client *c); void sortCommand(client *c);
void lremCommand(client *c); void lremCommand(client *c);
void lposCommand(client *c);
void rpoplpushCommand(client *c); void rpoplpushCommand(client *c);
void infoCommand(client *c); void infoCommand(client *c);
void mgetCommand(client *c); void mgetCommand(client *c);

View File

@ -532,7 +532,7 @@ void hsetCommand(client *c) {
robj *o; robj *o;
if ((c->argc % 2) == 1) { if ((c->argc % 2) == 1) {
addReplyError(c,"wrong number of arguments for HMSET"); addReplyErrorFormat(c,"wrong number of arguments for '%s' command",c->cmd->name);
return; return;
} }
@ -772,7 +772,9 @@ void genericHgetallCommand(client *c, int flags) {
hashTypeIterator *hi; hashTypeIterator *hi;
int length, count = 0; int length, count = 0;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymap[c->resp])) robj *emptyResp = (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) ?
shared.emptymap[c->resp] : shared.emptyarray;
if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyResp))
== nullptr || checkType(c,o,OBJ_HASH)) return; == nullptr || checkType(c,o,OBJ_HASH)) return;
/* We return a map if the user requested keys and values, like in the /* We return a map if the user requested keys and values, like in the

View File

@ -487,6 +487,124 @@ void ltrimCommand(client *c) {
addReply(c,shared.ok); addReply(c,shared.ok);
} }
/* LPOS key element [RANK rank] [COUNT num-matches] [MAXLEN len]
*
* The "rank" is the position of the match, so if it is 1, the first match
* is returned, if it is 2 the second match is returned and so forth.
* It is 1 by default. If negative has the same meaning but the search is
* performed starting from the end of the list.
*
* If COUNT is given, instead of returning the single element, a list of
* all the matching elements up to "num-matches" are returned. COUNT can
* be combiled with RANK in order to returning only the element starting
* from the Nth. If COUNT is zero, all the matching elements are returned.
*
* MAXLEN tells the command to scan a max of len elements. If zero (the
* default), all the elements in the list are scanned if needed.
*
* The returned elements indexes are always referring to what LINDEX
* would return. So first element from head is 0, and so forth. */
void lposCommand(client *c) {
robj *ele;
robj_roptr o;
ele = c->argv[2];
int direction = LIST_TAIL;
long rank = 1, count = -1, maxlen = 0; /* Count -1: option not given. */
/* Parse the optional arguments. */
for (int j = 3; j < c->argc; j++) {
char *opt = szFromObj(c->argv[j]);
int moreargs = (c->argc-1)-j;
if (!strcasecmp(opt,"RANK") && moreargs) {
j++;
if (getLongFromObjectOrReply(c, c->argv[j], &rank, NULL) != C_OK)
return;
if (rank == 0) {
addReplyError(c,"RANK can't be zero: use 1 to start from "
"the first match, 2 from the second, ...");
return;
}
} else if (!strcasecmp(opt,"COUNT") && moreargs) {
j++;
if (getLongFromObjectOrReply(c, c->argv[j], &count, NULL) != C_OK)
return;
if (count < 0) {
addReplyError(c,"COUNT can't be negative");
return;
}
} else if (!strcasecmp(opt,"MAXLEN") && moreargs) {
j++;
if (getLongFromObjectOrReply(c, c->argv[j], &maxlen, NULL) != C_OK)
return;
if (maxlen < 0) {
addReplyError(c,"MAXLEN can't be negative");
return;
}
} else {
addReply(c,shared.syntaxerr);
return;
}
}
/* A negative rank means start from the tail. */
if (rank < 0) {
rank = -rank;
direction = LIST_HEAD;
}
/* We return NULL or an empty array if there is no such key (or
* if we find no matches, depending on the presence of the COUNT option. */
if ((o = lookupKeyRead(c->db,c->argv[1])) == nullptr) {
if (count != -1)
addReply(c,shared.emptyarray);
else
addReply(c,shared.null[c->resp]);
return;
}
if (checkType(c,o,OBJ_LIST)) return;
/* If we got the COUNT option, prepare to emit an array. */
void *arraylenptr = NULL;
if (count != -1) arraylenptr = addReplyDeferredLen(c);
/* Seek the element. */
listTypeIterator *li;
li = listTypeInitIterator(o,direction == LIST_HEAD ? -1 : 0,direction);
listTypeEntry entry;
long llen = listTypeLength(o);
long index = 0, matches = 0, matchindex = -1, arraylen = 0;
while (listTypeNext(li,&entry) && (maxlen == 0 || index < maxlen)) {
if (listTypeEqual(&entry,ele)) {
matches++;
matchindex = (direction == LIST_TAIL) ? index : llen - index - 1;
if (matches >= rank) {
if (arraylenptr) {
arraylen++;
addReplyLongLong(c,matchindex);
if (count && matches-rank+1 >= count) break;
} else {
break;
}
}
}
index++;
matchindex = -1; /* Remember if we exit the loop without a match. */
}
listTypeReleaseIterator(li);
/* Reply to the client. Note that arraylenptr is not NULL only if
* the COUNT option was selected. */
if (arraylenptr != NULL) {
setDeferredArrayLen(c,arraylenptr,arraylen);
} else {
if (matchindex != -1)
addReplyLongLong(c,matchindex);
else
addReply(c,shared.null[c->resp]);
}
}
void lremCommand(client *c) { void lremCommand(client *c) {
robj *subject, *obj; robj *subject, *obj;
obj = c->argv[3]; obj = c->argv[3];

View File

@ -1847,6 +1847,7 @@ NULL
o = createStreamObject(); o = createStreamObject();
dbAdd(c->db,c->argv[2],o); dbAdd(c->db,c->argv[2],o);
s = (stream*)ptrFromObj(o); s = (stream*)ptrFromObj(o);
signalModifiedKey(c,c->db,c->argv[2]);
} }
streamCG *cg = streamCreateCG(s,grpname,sdslen(grpname),&id); streamCG *cg = streamCreateCG(s,grpname,sdslen(grpname),&id);
@ -1891,7 +1892,7 @@ NULL
g_pserver->dirty++; g_pserver->dirty++;
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-delconsumer", notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-delconsumer",
c->argv[2],c->db->id); c->argv[2],c->db->id);
} else if (!strcasecmp(opt,"HELP")) { } else if (c->argc == 2 && !strcasecmp(opt,"HELP")) {
addReplyHelp(c, help); addReplyHelp(c, help);
} else { } else {
addReplySubcommandSyntaxError(c); addReplySubcommandSyntaxError(c);

View File

@ -35,8 +35,8 @@
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
static int checkStringLength(client *c, long long size) { static int checkStringLength(client *c, long long size) {
if (size > 512*1024*1024) { if (!(c->flags & CLIENT_MASTER) && size > g_pserver->proto_max_bulk_len) {
addReplyError(c,"string exceeds maximum allowed size (512MB)"); addReplyError(c,"string exceeds maximum allowed size (proto-max-bulk-len)");
return C_ERR; return C_ERR;
} }
return C_OK; return C_OK;
@ -505,6 +505,12 @@ void stralgoLCS(client *c) {
const char *b = NULL; const char *b = NULL;
int getlen = 0, getidx = 0, withmatchlen = 0; int getlen = 0, getidx = 0, withmatchlen = 0;
robj_roptr obja, objb; robj_roptr obja, objb;
uint32_t arraylen = 0; /* Number of ranges emitted in the array. */
uint32_t alen, blen, *lcs, idx;
int computelcs;
sds result = NULL; /* Resulting LCS string. */
void *arraylenptr = NULL; /* Deffered length of the array for IDX. */
uint32_t arange_start, arange_end, brange_start = 0, brange_end = 0;
for (j = 2; j < (uint32_t)c->argc; j++) { for (j = 2; j < (uint32_t)c->argc; j++) {
char *opt = szFromObj(c->argv[j]); char *opt = szFromObj(c->argv[j]);
@ -518,13 +524,13 @@ void stralgoLCS(client *c) {
withmatchlen = 1; withmatchlen = 1;
} else if (!strcasecmp(opt,"MINMATCHLEN") && moreargs) { } else if (!strcasecmp(opt,"MINMATCHLEN") && moreargs) {
if (getLongLongFromObjectOrReply(c,c->argv[j+1],&minmatchlen,NULL) if (getLongLongFromObjectOrReply(c,c->argv[j+1],&minmatchlen,NULL)
!= C_OK) return; != C_OK) goto cleanup;
if (minmatchlen < 0) minmatchlen = 0; if (minmatchlen < 0) minmatchlen = 0;
j++; j++;
} else if (!strcasecmp(opt,"STRINGS") && moreargs > 1) { } else if (!strcasecmp(opt,"STRINGS") && moreargs > 1) {
if (a != NULL) { if (a != NULL) {
addReplyError(c,"Either use STRINGS or KEYS"); addReplyError(c,"Either use STRINGS or KEYS");
return; goto cleanup;
} }
a = szFromObj(c->argv[j+1]); a = szFromObj(c->argv[j+1]);
b = szFromObj(c->argv[j+2]); b = szFromObj(c->argv[j+2]);
@ -532,10 +538,21 @@ void stralgoLCS(client *c) {
} else if (!strcasecmp(opt,"KEYS") && moreargs > 1) { } else if (!strcasecmp(opt,"KEYS") && moreargs > 1) {
if (a != NULL) { if (a != NULL) {
addReplyError(c,"Either use STRINGS or KEYS"); addReplyError(c,"Either use STRINGS or KEYS");
return; goto cleanup;
} }
obja = lookupKeyRead(c->db,c->argv[j+1]); obja = lookupKeyRead(c->db,c->argv[j+1]);
objb = lookupKeyRead(c->db,c->argv[j+2]); objb = lookupKeyRead(c->db,c->argv[j+2]);
if ((obja && obja->type != OBJ_STRING) ||
(objb && objb->type != OBJ_STRING))
{
addReplyError(c,
"The specified keys must contain string values");
/* Don't cleanup the objects, we need to do that
* only after callign getDecodedObject(). */
obja = NULL;
objb = NULL;
goto cleanup;
}
obja = obja ? getDecodedObject(obja) : createStringObject("",0); obja = obja ? getDecodedObject(obja) : createStringObject("",0);
objb = objb ? getDecodedObject(objb) : createStringObject("",0); objb = objb ? getDecodedObject(objb) : createStringObject("",0);
a = szFromObj(obja); a = szFromObj(obja);
@ -543,7 +560,7 @@ void stralgoLCS(client *c) {
j += 2; j += 2;
} else { } else {
addReply(c,shared.syntaxerr); addReply(c,shared.syntaxerr);
return; goto cleanup;
} }
} }
@ -551,23 +568,23 @@ void stralgoLCS(client *c) {
if (a == NULL) { if (a == NULL) {
addReplyError(c,"Please specify two strings: " addReplyError(c,"Please specify two strings: "
"STRINGS or KEYS options are mandatory"); "STRINGS or KEYS options are mandatory");
return; goto cleanup;
} else if (getlen && getidx) { } else if (getlen && getidx) {
addReplyError(c, addReplyError(c,
"If you want both the length and indexes, please " "If you want both the length and indexes, please "
"just use IDX."); "just use IDX.");
return; goto cleanup;
} }
/* Compute the LCS using the vanilla dynamic programming technique of /* Compute the LCS using the vanilla dynamic programming technique of
* building a table of LCS(x,y) substrings. */ * building a table of LCS(x,y) substrings. */
uint32_t alen = sdslen(a); alen = sdslen(a);
uint32_t blen = sdslen(b); blen = sdslen(b);
/* Setup an uint32_t array to store at LCS[i,j] the length of the /* Setup an uint32_t array to store at LCS[i,j] the length of the
* LCS A0..i-1, B0..j-1. Note that we have a linear array here, so * LCS A0..i-1, B0..j-1. Note that we have a linear array here, so
* we index it as LCS[j+(blen+1)*j] */ * we index it as LCS[j+(blen+1)*j] */
uint32_t *lcs = (uint32_t*)zmalloc((alen+1)*(blen+1)*sizeof(uint32_t)); lcs = (uint32_t*)zmalloc((alen+1)*(blen+1)*sizeof(uint32_t));
#define LCS(A,B) lcs[(B)+((A)*(blen+1))] #define LCS(A,B) lcs[(B)+((A)*(blen+1))]
/* Start building the LCS table. */ /* Start building the LCS table. */
@ -596,20 +613,18 @@ void stralgoLCS(client *c) {
/* Store the actual LCS string in "result" if needed. We create /* Store the actual LCS string in "result" if needed. We create
* it backward, but the length is already known, we store it into idx. */ * it backward, but the length is already known, we store it into idx. */
uint32_t idx = LCS(alen,blen); idx = LCS(alen,blen);
sds result = NULL; /* Resulting LCS string. */ arange_start = alen; /* alen signals that values are not set. */
void *arraylenptr = NULL; /* Deffered length of the array for IDX. */ arange_end = 0;
uint32_t arange_start = alen, /* alen signals that values are not set. */ brange_start = 0;
arange_end = 0, brange_end = 0;
brange_start = 0,
brange_end = 0;
/* Do we need to compute the actual LCS string? Allocate it in that case. */ /* Do we need to compute the actual LCS string? Allocate it in that case. */
int computelcs = getidx || !getlen; computelcs = getidx || !getlen;
if (computelcs) result = sdsnewlen(SDS_NOINIT,idx); if (computelcs) result = sdsnewlen(SDS_NOINIT,idx);
/* Start with a deferred array if we have to emit the ranges. */ /* Start with a deferred array if we have to emit the ranges. */
uint32_t arraylen = 0; /* Number of ranges emitted in the array. */ arraylen = 0; /* Number of ranges emitted in the array. */
if (getidx) { if (getidx) {
addReplyMapLen(c,2); addReplyMapLen(c,2);
addReplyBulkCString(c,"matches"); addReplyBulkCString(c,"matches");
@ -691,10 +706,12 @@ void stralgoLCS(client *c) {
} }
/* Cleanup. */ /* Cleanup. */
if (obja) decrRefCount(obja);
if (objb) decrRefCount(objb);
sdsfree(result); sdsfree(result);
zfree(lcs); zfree(lcs);
cleanup:
if (obja) decrRefCount(obja);
if (objb) decrRefCount(objb);
return; return;
} }

View File

@ -152,6 +152,7 @@ void handleBlockedClientsTimeout(void) {
raxRemove(g_pserver->clients_timeout_table,ri.key,ri.key_len,NULL); raxRemove(g_pserver->clients_timeout_table,ri.key,ri.key_len,NULL);
raxSeek(&ri,"^",NULL,0); raxSeek(&ri,"^",NULL,0);
} }
raxStop(&ri);
} }
/* Get a timeout value from an object and store it into 'timeout'. /* Get a timeout value from an object and store it into 'timeout'.

View File

@ -360,15 +360,44 @@ void connTLSMarshalThread(connection *c) {
conn->el = serverTL->el; conn->el = serverTL->el;
} }
/* Fetch the latest OpenSSL error and store it in the connection */
static void updateTLSError(tls_connection *conn) {
conn->c.last_errno = 0;
if (conn->ssl_error) zfree(conn->ssl_error);
conn->ssl_error = (char*)zmalloc(512);
ERR_error_string_n(ERR_get_error(), conn->ssl_error, 512);
}
/* Create a new TLS connection that is already associated with
* an accepted underlying file descriptor.
*
* The socket is not ready for I/O until connAccept() was called and
* invoked the connection-level accept handler.
*
* Callers should use connGetState() and verify the created connection
* is not in an error state.
*/
connection *connCreateAcceptedTLS(int fd, int require_auth) { connection *connCreateAcceptedTLS(int fd, int require_auth) {
tls_connection *conn = (tls_connection *) connCreateTLS(); tls_connection *conn = (tls_connection *) connCreateTLS();
conn->c.fd = fd; conn->c.fd = fd;
conn->c.state = CONN_STATE_ACCEPTING; conn->c.state = CONN_STATE_ACCEPTING;
if (!require_auth) { if (!conn->ssl) {
/* We still verify certificates if provided, but don't require them. updateTLSError(conn);
*/ conn->c.state = CONN_STATE_ERROR;
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, NULL); return (connection *) conn;
}
switch (require_auth) {
case TLS_CLIENT_AUTH_NO:
SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL);
break;
case TLS_CLIENT_AUTH_OPTIONAL:
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, NULL);
break;
default: /* TLS_CLIENT_AUTH_YES, also fall-secure */
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
break;
} }
SSL_set_fd(conn->ssl, conn->c.fd); SSL_set_fd(conn->ssl, conn->c.fd);
@ -401,10 +430,7 @@ static int handleSSLReturnCode(tls_connection *conn, int ret_value, WantIOType *
break; break;
default: default:
/* Error! */ /* Error! */
conn->c.last_errno = 0; updateTLSError(conn);
if (conn->ssl_error) zfree(conn->ssl_error);
conn->ssl_error = (char*)zmalloc(512);
ERR_error_string_n(ERR_get_error(), conn->ssl_error, 512);
break; break;
} }
@ -891,6 +917,12 @@ exit:
return nread; return nread;
} }
static int connTLSGetType(connection *conn_) {
(void) conn_;
return CONN_TYPE_TLS;
}
ConnectionType CT_TLS = { ConnectionType CT_TLS = {
tlsEventHandler, tlsEventHandler,
connTLSConnect, connTLSConnect,
@ -906,6 +938,7 @@ ConnectionType CT_TLS = {
connTLSSyncRead, connTLSSyncRead,
connTLSSyncReadLine, connTLSSyncReadLine,
connTLSMarshalThread, connTLSMarshalThread,
connTLSGetType
}; };
int tlsHasPendingData() { int tlsHasPendingData() {

View File

@ -101,7 +101,7 @@ void disableTracking(client *c) {
/* Set the client 'c' to track the prefix 'prefix'. If the client 'c' is /* Set the client 'c' to track the prefix 'prefix'. If the client 'c' is
* already registered for the specified prefix, no operation is performed. */ * already registered for the specified prefix, no operation is performed. */
static void enableBcastTrackingForPrefix(client *c, const char *prefix, size_t plen) { void enableBcastTrackingForPrefix(client *c, const char *prefix, size_t plen) {
bcastState *bs = (bcastState*)raxFind(PrefixTable,(unsigned char*)prefix,plen); bcastState *bs = (bcastState*)raxFind(PrefixTable,(unsigned char*)prefix,plen);
/* If this is the first client subscribing to such prefix, create /* If this is the first client subscribing to such prefix, create
* the prefix in the table. */ * the prefix in the table. */
@ -198,12 +198,13 @@ void trackingRememberKeys(client *c) {
* *
* In case the 'proto' argument is non zero, the function will assume that * In case the 'proto' argument is non zero, the function will assume that
* 'keyname' points to a buffer of 'keylen' bytes already expressed in the * 'keyname' points to a buffer of 'keylen' bytes already expressed in the
* form of Redis RESP protocol, representing an array of keys to send * form of Redis RESP protocol. This is used for:
* to the client as value of the invalidation. This is used in BCAST mode * - In BCAST mode, to send an array of invalidated keys to all
* in order to optimized the implementation to use less CPU time. */ * applicable clients
void sendTrackingMessage(client *c, const char *keyname, size_t keylen, int proto) { * - Following a flush command, to send a single RESP NULL to indicate
* that all keys are now invalid. */
void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
std::unique_lock<fastlock> ul(c->lock); std::unique_lock<fastlock> ul(c->lock);
serverAssert(c->lock.fOwnLock());
int using_redirection = 0; int using_redirection = 0;
if (c->client_tracking_redirection) { if (c->client_tracking_redirection) {
@ -347,17 +348,19 @@ void trackingInvalidateKey(client *c, robj *keyobj) {
trackingInvalidateKeyRaw(c,szFromObj(keyobj),sdslen(szFromObj(keyobj)),1); trackingInvalidateKeyRaw(c,szFromObj(keyobj),sdslen(szFromObj(keyobj)),1);
} }
/* This function is called when one or all the Redis databases are flushed /* This function is called when one or all the Redis databases are
* (dbid == -1 in case of FLUSHALL). Caching keys are not specific for * flushed (dbid == -1 in case of FLUSHALL). Caching keys are not
* each DB but are global: currently what we do is send a special * specific for each DB but are global: currently what we do is send a
* notification to clients with tracking enabled, invalidating the caching * special notification to clients with tracking enabled, sending a
* key "", which means, "all the keys", in order to avoid flooding clients * RESP NULL, which means, "all the keys", in order to avoid flooding
* with many invalidation messages for all the keys they may hold. * clients with many invalidation messages for all the keys they may
* hold.
*/ */
void freeTrackingRadixTree(void *rt) { void freeTrackingRadixTree(void *rt) {
raxFree((rax*)rt); raxFree((rax*)rt);
} }
/* A RESP NULL is sent to indicate that all keys are invalid */
void trackingInvalidateKeysOnFlush(int dbid) { void trackingInvalidateKeysOnFlush(int dbid) {
if (g_pserver->tracking_clients) { if (g_pserver->tracking_clients) {
listNode *ln; listNode *ln;
@ -366,7 +369,7 @@ void trackingInvalidateKeysOnFlush(int dbid) {
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
client *c = (client*)listNodeValue(ln); client *c = (client*)listNodeValue(ln);
if (c->flags & CLIENT_TRACKING) { if (c->flags & CLIENT_TRACKING) {
sendTrackingMessage(c,"",1,0); sendTrackingMessage(c,szFromObj(shared.null[c->resp]),sdslen(szFromObj(shared.null[c->resp])),1);
} }
} }
} }

View File

@ -86,7 +86,7 @@
* |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes * |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
* String value with length greater than or equal to 16384 bytes. * String value with length greater than or equal to 16384 bytes.
* Only the 4 bytes following the first byte represents the length * Only the 4 bytes following the first byte represents the length
* up to 32^2-1. The 6 lower bits of the first byte are not used and * up to 2^32-1. The 6 lower bits of the first byte are not used and
* are set to zero. * are set to zero.
* IMPORTANT: The 32 bit number is stored in big endian. * IMPORTANT: The 32 bit number is stored in big endian.
* |11000000| - 3 bytes * |11000000| - 3 bytes
@ -194,7 +194,7 @@
#define ZIP_BIG_PREVLEN 254 /* Max number of bytes of the previous entry, for #define ZIP_BIG_PREVLEN 254 /* Max number of bytes of the previous entry, for
the "prevlen" field prefixing each entry, to be the "prevlen" field prefixing each entry, to be
represented with just a single byte. Otherwise represented with just a single byte. Otherwise
it is represented as FF AA BB CC DD, where it is represented as FE AA BB CC DD, where
AA BB CC DD are a 4 bytes unsigned integer AA BB CC DD are a 4 bytes unsigned integer
representing the previous entry len. */ representing the previous entry len. */

2
tests/assets/user.acl Normal file
View File

@ -0,0 +1,2 @@
user alice on allcommands allkeys >alice
user bob on -@all +@set +acl ~set* >bob

View File

@ -1,4 +1,5 @@
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
test "Create a 5 nodes cluster" { test "Create a 5 nodes cluster" {
create_cluster 5 5 create_cluster 5 5
@ -38,50 +39,79 @@ proc cluster_write_keys_with_expire {id ttl} {
$cluster close $cluster close
} }
# make sure that replica who restarts from persistence will load keys
# that have already expired, critical for correct execution of commands
# that arrive from the master
proc test_slave_load_expired_keys {aof} { proc test_slave_load_expired_keys {aof} {
test "Slave expired keys is loaded when restarted: appendonly=$aof" { test "Slave expired keys is loaded when restarted: appendonly=$aof" {
set master_id [find_non_empty_master] set master_id [find_non_empty_master]
set replica_id [get_one_of_my_replica $master_id] set replica_id [get_one_of_my_replica $master_id]
set master_dbsize [R $master_id dbsize] set master_dbsize_0 [R $master_id dbsize]
set slave_dbsize [R $replica_id dbsize] set replica_dbsize_0 [R $replica_id dbsize]
assert_equal $master_dbsize $slave_dbsize assert_equal $master_dbsize_0 $replica_dbsize_0
set data_ttl 5
cluster_write_keys_with_expire $master_id $data_ttl
after 100
set replica_dbsize_1 [R $replica_id dbsize]
assert {$replica_dbsize_1 > $slave_dbsize}
# config the replica persistency and rewrite the config file to survive restart
# note that this needs to be done before populating the volatile keys since
# that triggers and AOFRW, and we rather the AOF file to have SETEX commands
# rather than an RDB with volatile keys
R $replica_id config set appendonly $aof R $replica_id config set appendonly $aof
R $replica_id config rewrite R $replica_id config rewrite
set start_time [clock seconds] # fill with 100 keys with 3 second TTL
set end_time [expr $start_time+$data_ttl+2] set data_ttl 3
R $replica_id save cluster_write_keys_with_expire $master_id $data_ttl
set replica_dbsize_2 [R $replica_id dbsize]
assert {$replica_dbsize_2 > $slave_dbsize} # wait for replica to be in sync with master
wait_for_condition 500 10 {
[R $replica_id dbsize] eq [R $master_id dbsize]
} else {
fail "replica didn't sync"
}
set replica_dbsize_1 [R $replica_id dbsize]
assert {$replica_dbsize_1 > $replica_dbsize_0}
# make replica create persistence file
if {$aof == "yes"} {
# we need to wait for the initial AOFRW to be done, otherwise
# kill_instance (which now uses SIGTERM will fail ("Writing initial AOF, can't exit")
wait_for_condition 100 10 {
[RI $replica_id aof_rewrite_in_progress] eq 0
} else {
fail "keys didn't expire"
}
} else {
R $replica_id save
}
# kill the replica (would stay down until re-started)
kill_instance redis $replica_id kill_instance redis $replica_id
set master_port [get_instance_attrib redis $master_id port] # Make sure the master doesn't do active expire (sending DELs to the replica)
exec ../../../src/keydb-cli -h 127.0.0.1 -p $master_port debug sleep [expr $data_ttl+3] > /dev/null & R $master_id DEBUG SET-ACTIVE-EXPIRE 0
while {[clock seconds] <= $end_time} { # wait for all the keys to get logically expired
#wait for $data_ttl seconds after [expr $data_ttl*1000]
}
# start the replica again (loading an RDB or AOF file)
restart_instance redis $replica_id restart_instance redis $replica_id
wait_for_condition 200 50 { # make sure the keys are still there
[R $replica_id ping] eq {PONG}
} else {
fail "replica #$replica_id not started"
}
set replica_dbsize_3 [R $replica_id dbsize] set replica_dbsize_3 [R $replica_id dbsize]
assert {$replica_dbsize_3 > $slave_dbsize} assert {$replica_dbsize_3 > $replica_dbsize_0}
# restore settings
R $master_id DEBUG SET-ACTIVE-EXPIRE 1
# wait for the master to expire all keys and replica to get the DELs
wait_for_condition 500 10 {
[R $replica_id dbsize] eq $master_dbsize_0
} else {
fail "keys didn't expire"
}
} }
} }
test_slave_load_expired_keys no test_slave_load_expired_keys no
after 5000
test_slave_load_expired_keys yes test_slave_load_expired_keys yes

View File

@ -0,0 +1,48 @@
# Check basic transactions on a replica.
source "../tests/includes/init-tests.tcl"
test "Create a primary with a replica" {
create_cluster 1 1
}
test "Cluster should start ok" {
assert_cluster_state ok
}
set primary [Rn 0]
set replica [Rn 1]
test "Cant read from replica without READONLY" {
$primary SET a 1
catch {$replica GET a} err
assert {[string range $err 0 4] eq {MOVED}}
}
test "Can read from replica after READONLY" {
$replica READONLY
assert {[$replica GET a] eq {1}}
}
test "Can preform HSET primary and HGET from replica" {
$primary HSET h a 1
$primary HSET h b 2
$primary HSET h c 3
assert {[$replica HGET h a] eq {1}}
assert {[$replica HGET h b] eq {2}}
assert {[$replica HGET h c] eq {3}}
}
test "Can MULTI-EXEC transaction of HGET operations from replica" {
$replica MULTI
assert {[$replica HGET h a] eq {QUEUED}}
assert {[$replica HGET h b] eq {QUEUED}}
assert {[$replica HGET h c] eq {QUEUED}}
assert {[$replica EXEC] eq {1 2 3}}
}
test "MULTI-EXEC with write operations is MOVED" {
$replica MULTI
catch {$replica HSET h b 4} err
assert {[string range $err 0 4] eq {MOVED}}
}

View File

@ -19,6 +19,7 @@ set ::verbose 0
set ::valgrind 0 set ::valgrind 0
set ::tls 0 set ::tls 0
set ::pause_on_error 0 set ::pause_on_error 0
set ::dont_clean 0
set ::simulate_error 0 set ::simulate_error 0
set ::failed 0 set ::failed 0
set ::sentinel_instances {} set ::sentinel_instances {}
@ -38,7 +39,7 @@ if {[catch {cd tmp}]} {
# Execute the specified instance of the server specified by 'type', using # Execute the specified instance of the server specified by 'type', using
# the provided configuration file. Returns the PID of the process. # the provided configuration file. Returns the PID of the process.
proc exec_instance {type cfgfile dir} { proc exec_instance {type dirname cfgfile} {
if {$type eq "redis"} { if {$type eq "redis"} {
set prgname keydb-server set prgname keydb-server
} elseif {$type eq "sentinel"} { } elseif {$type eq "sentinel"} {
@ -47,13 +48,11 @@ proc exec_instance {type cfgfile dir} {
error "Unknown instance type." error "Unknown instance type."
} }
set stdout [format "%s/%s" $dir "stdout"] set errfile [file join $dirname err.txt]
set stderr [format "%s/%s" $dir "stderr"]
if {$::valgrind} { if {$::valgrind} {
set pid [exec valgrind --track-origins=yes --suppressions=../../../src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full ../../../src/${prgname} $cfgfile &] set pid [exec valgrind --track-origins=yes --suppressions=../../../src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full ../../../src/${prgname} $cfgfile 2>> $errfile &]
} else { } else {
set pid [exec ../../../src/${prgname} $cfgfile > $stdout 2> $stderr &] set pid [exec ../../../src/${prgname} $cfgfile 2>> $errfile &]
} }
return $pid return $pid
} }
@ -62,8 +61,6 @@ proc exec_instance {type cfgfile dir} {
proc spawn_instance {type base_port count {conf {}}} { proc spawn_instance {type base_port count {conf {}}} {
for {set j 0} {$j < $count} {incr j} { for {set j 0} {$j < $count} {incr j} {
set port [find_available_port $base_port $::redis_port_count] set port [find_available_port $base_port $::redis_port_count]
incr base_port
puts "Starting $type #$j at port $port"
# Create a directory for this instance. # Create a directory for this instance.
set dirname "${type}_${j}" set dirname "${type}_${j}"
@ -96,12 +93,34 @@ proc spawn_instance {type base_port count {conf {}}} {
close $cfg close $cfg
# Finally exec it and remember the pid for later cleanup. # Finally exec it and remember the pid for later cleanup.
set pid [exec_instance $type $cfgfile $dirname] set retry 100
lappend ::pids $pid while {$retry} {
set pid [exec_instance $type $dirname $cfgfile]
# Check availability # Check availability
if {[server_is_up 127.0.0.1 $port 100] == 0} {
puts "Starting $type #$j at port $port failed, try another"
incr retry -1
set port [find_available_port $base_port $::redis_port_count]
set cfg [open $cfgfile a+]
if {$::tls} {
puts $cfg "tls-port $port"
} else {
puts $cfg "port $port"
}
close $cfg
} else {
puts "Starting $type #$j at port $port"
lappend ::pids $pid
break
}
}
# Check availability finally
if {[server_is_up 127.0.0.1 $port 100] == 0} { if {[server_is_up 127.0.0.1 $port 100] == 0} {
abort_sentinel_test "Problems starting $type #$j: ping timeout" set logfile [file join $dirname log.txt]
puts [exec tail $logfile]
abort_sentinel_test "Problems starting $type #$j: ping timeout, maybe server start failed, check $logfile"
} }
# Push the instance into the right list # Push the instance into the right list
@ -127,16 +146,60 @@ proc log_crashes {} {
puts "\n*** Crash report found in $log ***" puts "\n*** Crash report found in $log ***"
set found 1 set found 1
} }
if {$found} {puts $line} if {$found} {
puts $line
incr ::failed
}
} }
} }
set logs [glob */err.txt]
foreach log $logs {
set res [find_valgrind_errors $log]
if {$res != ""} {
puts $res
incr ::failed
}
}
}
proc is_alive pid {
if {[catch {exec ps -p $pid} err]} {
return 0
} else {
return 1
}
}
proc stop_instance pid {
catch {exec kill $pid}
if {$::valgrind} {
set max_wait 60000
} else {
set max_wait 10000
}
while {[is_alive $pid]} {
incr wait 10
if {$wait >= $max_wait} {
puts "Forcing process $pid to exit..."
catch {exec kill -KILL $pid}
} elseif {$wait % 1000 == 0} {
puts "Waiting for process $pid to exit..."
}
after 10
}
} }
proc cleanup {} { proc cleanup {} {
puts "Cleaning up..." puts "Cleaning up..."
log_crashes
foreach pid $::pids { foreach pid $::pids {
catch {exec kill -9 $pid} puts "killing stale instance $pid"
stop_instance $pid
}
log_crashes
if {$::dont_clean} {
return
} }
foreach dir $::dirs { foreach dir $::dirs {
catch {exec rm -rf $dir} catch {exec rm -rf $dir}
@ -161,6 +224,8 @@ proc parse_options {} {
set ::run_matching "*${val}*" set ::run_matching "*${val}*"
} elseif {$opt eq "--pause-on-error"} { } elseif {$opt eq "--pause-on-error"} {
set ::pause_on_error 1 set ::pause_on_error 1
} elseif {$opt eq {--dont-clean}} {
set ::dont_clean 1
} elseif {$opt eq "--fail"} { } elseif {$opt eq "--fail"} {
set ::simulate_error 1 set ::simulate_error 1
} elseif {$opt eq {--valgrind}} { } elseif {$opt eq {--valgrind}} {
@ -173,9 +238,8 @@ proc parse_options {} {
-keyfile "$::tlsdir/redis.key" -keyfile "$::tlsdir/redis.key"
set ::tls 1 set ::tls 1
} elseif {$opt eq "--help"} { } elseif {$opt eq "--help"} {
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts "\nOptions:"
puts "--single <pattern> Only runs tests specified by pattern." puts "--single <pattern> Only runs tests specified by pattern."
puts "--dont-clean Keep log files on exit."
puts "--pause-on-error Pause for manual inspection on error." puts "--pause-on-error Pause for manual inspection on error."
puts "--fail Simulate a test failure." puts "--fail Simulate a test failure."
puts "--valgrind Run with valgrind." puts "--valgrind Run with valgrind."
@ -358,10 +422,16 @@ proc S {n args} {
[dict get $s link] {*}$args [dict get $s link] {*}$args
} }
# Returns a Redis instance by index.
# Example:
# [Rn 0] info
proc Rn {n} {
return [dict get [lindex $::redis_instances $n] link]
}
# Like R but to chat with Redis instances. # Like R but to chat with Redis instances.
proc R {n args} { proc R {n args} {
set r [lindex $::redis_instances $n] [Rn $n] {*}$args
[dict get $r link] {*}$args
} }
proc get_info_field {info field} { proc get_info_field {info field} {
@ -471,7 +541,7 @@ proc kill_instance {type id} {
error "You tried to kill $type $id twice." error "You tried to kill $type $id twice."
} }
exec kill -9 $pid stop_instance $pid
set_instance_attrib $type $id pid -1 set_instance_attrib $type $id pid -1
set_instance_attrib $type $id link you_tried_to_talk_with_killed_instance set_instance_attrib $type $id link you_tried_to_talk_with_killed_instance
@ -480,12 +550,12 @@ proc kill_instance {type id} {
# Wait for the port it was using to be available again, so that's not # Wait for the port it was using to be available again, so that's not
# an issue to start a new server ASAP with the same port. # an issue to start a new server ASAP with the same port.
set retry 10 set retry 100
while {[incr retry -1]} { while {[incr retry -1]} {
set port_is_free [catch {set s [socket 127.0.01 $port]}] set port_is_free [catch {set s [socket 127.0.0.1 $port]}]
if {$port_is_free} break if {$port_is_free} break
catch {close $s} catch {close $s}
after 1000 after 100
} }
if {$retry == 0} { if {$retry == 0} {
error "Port $port does not return available after killing instance." error "Port $port does not return available after killing instance."
@ -506,13 +576,15 @@ proc restart_instance {type id} {
# Execute the instance with its old setup and append the new pid # Execute the instance with its old setup and append the new pid
# file for cleanup. # file for cleanup.
set pid [exec_instance $type $cfgfile $dirname] set pid [exec_instance $type $dirname $cfgfile]
set_instance_attrib $type $id pid $pid set_instance_attrib $type $id pid $pid
lappend ::pids $pid lappend ::pids $pid
# Check that the instance is running # Check that the instance is running
if {[server_is_up 127.0.0.1 $port 100] == 0} { if {[server_is_up 127.0.0.1 $port 100] == 0} {
abort_sentinel_test "Problems starting $type #$id: ping timeout" set logfile [file join $dirname log.txt]
puts [exec tail $logfile]
abort_sentinel_test "Problems starting $type #$id: ping timeout, maybe server start failed, check $logfile"
} }
# Connect with it with a fresh link # Connect with it with a fresh link

View File

@ -52,15 +52,9 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv] assert_equal 1 [is_alive $srv]
} }
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
test "Truncated AOF loaded: we expect foo to be equal to 5" { test "Truncated AOF loaded: we expect foo to be equal to 5" {
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_done_loading $client
assert {[$client get foo] eq "5"} assert {[$client get foo] eq "5"}
} }
@ -75,15 +69,9 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv] assert_equal 1 [is_alive $srv]
} }
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
test "Truncated AOF loaded: we expect foo to be equal to 6 now" { test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_done_loading $client
assert {[$client get foo] eq "6"} assert {[$client get foo] eq "6"}
} }
} }
@ -183,11 +171,7 @@ tags {"aof"} {
test "Fixed AOF: Keyspace should contain values that were parseable" { test "Fixed AOF: Keyspace should contain values that were parseable" {
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_done_loading $client
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
assert_equal "hello" [$client get foo] assert_equal "hello" [$client get foo]
assert_equal "" [$client get bar] assert_equal "" [$client get bar]
} }
@ -207,11 +191,7 @@ tags {"aof"} {
test "AOF+SPOP: Set should have 1 member" { test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_done_loading $client
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
assert_equal 1 [$client scard set] assert_equal 1 [$client scard set]
} }
} }
@ -231,11 +211,7 @@ tags {"aof"} {
test "AOF+SPOP: Set should have 1 member" { test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_done_loading $client
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
assert_equal 1 [$client scard set] assert_equal 1 [$client scard set]
} }
} }
@ -254,11 +230,7 @@ tags {"aof"} {
test "AOF+EXPIRE: List should be empty" { test "AOF+EXPIRE: List should be empty" {
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_done_loading $client
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
assert_equal 0 [$client llen list] assert_equal 0 [$client llen list]
} }
} }

View File

@ -284,7 +284,8 @@ start_server {} {
set sync_partial_err [status $R($master_id) sync_partial_err] set sync_partial_err [status $R($master_id) sync_partial_err]
catch { catch {
$R($slave_id) config rewrite $R($slave_id) config rewrite
$R($slave_id) debug restart restart_server [expr {0-$slave_id}] true
set R($slave_id) [srv [expr {0-$slave_id}] client]
} }
# note: just waiting for connected_slaves==4 has a race condition since # note: just waiting for connected_slaves==4 has a race condition since
# we might do the check before the master realized that the slave disconnected # we might do the check before the master realized that the slave disconnected
@ -332,7 +333,8 @@ start_server {} {
catch { catch {
$R($slave_id) config rewrite $R($slave_id) config rewrite
$R($slave_id) debug restart restart_server [expr {0-$slave_id}] true
set R($slave_id) [srv [expr {0-$slave_id}] client]
} }
# Reconfigure the slave correctly again, when it's back online. # Reconfigure the slave correctly again, when it's back online.

View File

@ -25,7 +25,7 @@ start_server [list overrides [list "dir" $server_path "dbfilename" "encodings.rd
set server_path [tmpdir "server.rdb-startup-test"] set server_path [tmpdir "server.rdb-startup-test"]
start_server [list overrides [list "dir" $server_path]] { start_server [list overrides [list "dir" $server_path] keep_persistence true] {
test {Server started empty with non-existing RDB file} { test {Server started empty with non-existing RDB file} {
r debug digest r debug digest
} {0000000000000000000000000000000000000000} } {0000000000000000000000000000000000000000}
@ -33,13 +33,13 @@ start_server [list overrides [list "dir" $server_path]] {
r save r save
} }
start_server [list overrides [list "dir" $server_path]] { start_server [list overrides [list "dir" $server_path] keep_persistence true] {
test {Server started empty with empty RDB file} { test {Server started empty with empty RDB file} {
r debug digest r debug digest
} {0000000000000000000000000000000000000000} } {0000000000000000000000000000000000000000}
} }
start_server [list overrides [list "dir" $server_path]] { start_server [list overrides [list "dir" $server_path] keep_persistence true] {
test {Test RDB stream encoding} { test {Test RDB stream encoding} {
for {set j 0} {$j < 1000} {incr j} { for {set j 0} {$j < 1000} {incr j} {
if {rand() < 0.9} { if {rand() < 0.9} {
@ -64,7 +64,7 @@ set defaults {}
proc start_server_and_kill_it {overrides code} { proc start_server_and_kill_it {overrides code} {
upvar defaults defaults srv srv server_path server_path upvar defaults defaults srv srv server_path server_path
set config [concat $defaults $overrides] set config [concat $defaults $overrides]
set srv [start_server [list overrides $config]] set srv [start_server [list overrides $config keep_persistence true]]
uplevel 1 $code uplevel 1 $code
kill_server $srv kill_server $srv
} }
@ -118,16 +118,34 @@ start_server_and_kill_it [list "dir" $server_path] {
start_server {} { start_server {} {
test {Test FLUSHALL aborts bgsave} { test {Test FLUSHALL aborts bgsave} {
# 1000 keys with 1ms sleep per key shuld take 1 second
r config set rdb-key-save-delay 1000 r config set rdb-key-save-delay 1000
r debug populate 1000 r debug populate 1000
r bgsave r bgsave
assert_equal [s rdb_bgsave_in_progress] 1 assert_equal [s rdb_bgsave_in_progress] 1
r flushall r flushall
after 200 # wait half a second max
assert_equal [s rdb_bgsave_in_progress] 0 wait_for_condition 5 100 {
[s rdb_bgsave_in_progress] == 0
} else {
fail "bgsave not aborted"
}
# veirfy that bgsave failed, by checking that the change counter is still high
assert_lessthan 999 [s rdb_changes_since_last_save]
# make sure the server is still writable # make sure the server is still writable
r set x xx r set x xx
} }
test {bgsave resets the change counter} {
r config set rdb-key-save-delay 0
r bgsave
wait_for_condition 50 100 {
[s rdb_bgsave_in_progress] == 0
} else {
fail "bgsave not done"
}
assert_equal [s rdb_changes_since_last_save] 0
}
} }
test {client freed during loading} { test {client freed during loading} {
@ -137,18 +155,8 @@ test {client freed during loading} {
# 100mb of rdb, 100k keys will load in more than 1 second # 100mb of rdb, 100k keys will load in more than 1 second
r debug populate 100000 key 1000 r debug populate 100000 key 1000
catch { restart_server 0 false
r debug restart
}
set stdout [srv 0 stdout]
while 1 {
# check that the new server actually started and is ready for connections
if {[exec grep -i "Server initialized" | wc -l < $stdout] > 1} {
break
}
after 10
}
# make sure it's still loading # make sure it's still loading
assert_equal [s loading] 1 assert_equal [s loading] 1
@ -180,4 +188,4 @@ test {client freed during loading} {
# no need to keep waiting for loading to complete # no need to keep waiting for loading to complete
exec kill [srv 0 pid] exec kill [srv 0 pid]
} }
} }

View File

@ -1,14 +1,19 @@
source tests/support/cli.tcl source tests/support/cli.tcl
start_server {tags {"cli"}} { start_server {tags {"cli"}} {
proc open_cli {} { proc open_cli {{opts "-n 9"} {infile ""}} {
set ::env(TERM) dumb set ::env(TERM) dumb
set cmdline [rediscli [srv port] "-n 9"] set cmdline [rediscli [srv host] [srv port] $opts]
set fd [open "|$cmdline" "r+"] if {$infile ne ""} {
set cmdline "$cmdline < $infile"
set mode "r"
} else {
set mode "r+"
}
set fd [open "|$cmdline" $mode]
fconfigure $fd -buffering none fconfigure $fd -buffering none
fconfigure $fd -blocking false fconfigure $fd -blocking false
fconfigure $fd -translation binary fconfigure $fd -translation binary
assert_equal "redis> " [read_cli $fd]
set _ $fd set _ $fd
} }
@ -32,11 +37,14 @@ start_server {tags {"cli"}} {
} }
# Helpers to run tests in interactive mode # Helpers to run tests in interactive mode
proc format_output {output} {
set _ [string trimright [regsub -all "\r" $output ""] "\n"]
}
proc run_command {fd cmd} { proc run_command {fd cmd} {
write_cli $fd $cmd write_cli $fd $cmd
set lines [split [read_cli $fd] "\n"] set _ [format_output [read_cli $fd]]
assert_equal "redis> " [lindex $lines end]
join [lrange $lines 0 end-1] "\n"
} }
proc test_interactive_cli {name code} { proc test_interactive_cli {name code} {
@ -57,8 +65,8 @@ start_server {tags {"cli"}} {
} }
proc _run_cli {opts args} { proc _run_cli {opts args} {
set cmd [rediscli [srv port] [list -n 9 {*}$args]] set cmd [rediscli [srv host] [srv port] [list -n 9 {*}$args]]
foreach {key value} $args { foreach {key value} $opts {
if {$key eq "pipe"} { if {$key eq "pipe"} {
set cmd "sh -c \"$value | $cmd\"" set cmd "sh -c \"$value | $cmd\""
} }
@ -72,7 +80,7 @@ start_server {tags {"cli"}} {
fconfigure $fd -translation binary fconfigure $fd -translation binary
set resp [read $fd 1048576] set resp [read $fd 1048576]
close $fd close $fd
set _ $resp set _ [format_output $resp]
} }
proc run_cli {args} { proc run_cli {args} {
@ -80,11 +88,11 @@ start_server {tags {"cli"}} {
} }
proc run_cli_with_input_pipe {cmd args} { proc run_cli_with_input_pipe {cmd args} {
_run_cli [list pipe $cmd] {*}$args _run_cli [list pipe $cmd] -x {*}$args
} }
proc run_cli_with_input_file {path args} { proc run_cli_with_input_file {path args} {
_run_cli [list path $path] {*}$args _run_cli [list path $path] -x {*}$args
} }
proc test_nontty_cli {name code} { proc test_nontty_cli {name code} {
@ -101,7 +109,7 @@ start_server {tags {"cli"}} {
test_interactive_cli "INFO response should be printed raw" { test_interactive_cli "INFO response should be printed raw" {
set lines [split [run_command $fd info] "\n"] set lines [split [run_command $fd info] "\n"]
foreach line $lines { foreach line $lines {
assert [regexp {^[a-z0-9_]+:[a-z0-9_]+} $line] assert [regexp {^$|^#|^[a-z0-9_]+:.+} $line]
} }
} }
@ -121,7 +129,7 @@ start_server {tags {"cli"}} {
test_interactive_cli "Multi-bulk reply" { test_interactive_cli "Multi-bulk reply" {
r rpush list foo r rpush list foo
r rpush list bar r rpush list bar
assert_equal "1. \"foo\"\n2. \"bar\"" [run_command $fd "lrange list 0 -1"] assert_equal "1) \"foo\"\n2) \"bar\"" [run_command $fd "lrange list 0 -1"]
} }
test_interactive_cli "Parsing quotes" { test_interactive_cli "Parsing quotes" {
@ -144,36 +152,37 @@ start_server {tags {"cli"}} {
} }
test_tty_cli "Status reply" { test_tty_cli "Status reply" {
assert_equal "OK\n" [run_cli set key bar] assert_equal "OK" [run_cli set key bar]
assert_equal "bar" [r get key] assert_equal "bar" [r get key]
} }
test_tty_cli "Integer reply" { test_tty_cli "Integer reply" {
r del counter r del counter
assert_equal "(integer) 1\n" [run_cli incr counter] assert_equal "(integer) 1" [run_cli incr counter]
} }
test_tty_cli "Bulk reply" { test_tty_cli "Bulk reply" {
r set key "tab\tnewline\n" r set key "tab\tnewline\n"
assert_equal "\"tab\\tnewline\\n\"\n" [run_cli get key] assert_equal "\"tab\\tnewline\\n\"" [run_cli get key]
} }
test_tty_cli "Multi-bulk reply" { test_tty_cli "Multi-bulk reply" {
r del list r del list
r rpush list foo r rpush list foo
r rpush list bar r rpush list bar
assert_equal "1. \"foo\"\n2. \"bar\"\n" [run_cli lrange list 0 -1] assert_equal "1) \"foo\"\n2) \"bar\"" [run_cli lrange list 0 -1]
} }
test_tty_cli "Read last argument from pipe" { test_tty_cli "Read last argument from pipe" {
assert_equal "OK\n" [run_cli_with_input_pipe "echo foo" set key] assert_equal "OK" [run_cli_with_input_pipe "echo foo" set key]
assert_equal "foo\n" [r get key] assert_equal "foo\n" [r get key]
} }
test_tty_cli "Read last argument from file" { test_tty_cli "Read last argument from file" {
set tmpfile [write_tmpfile "from file"] set tmpfile [write_tmpfile "from file"]
assert_equal "OK\n" [run_cli_with_input_file $tmpfile set key] assert_equal "OK" [run_cli_with_input_file $tmpfile set key]
assert_equal "from file" [r get key] assert_equal "from file" [r get key]
file delete $tmpfile
} }
test_nontty_cli "Status reply" { test_nontty_cli "Status reply" {
@ -188,7 +197,7 @@ start_server {tags {"cli"}} {
test_nontty_cli "Bulk reply" { test_nontty_cli "Bulk reply" {
r set key "tab\tnewline\n" r set key "tab\tnewline\n"
assert_equal "tab\tnewline\n" [run_cli get key] assert_equal "tab\tnewline" [run_cli get key]
} }
test_nontty_cli "Multi-bulk reply" { test_nontty_cli "Multi-bulk reply" {
@ -207,5 +216,80 @@ start_server {tags {"cli"}} {
set tmpfile [write_tmpfile "from file"] set tmpfile [write_tmpfile "from file"]
assert_equal "OK" [run_cli_with_input_file $tmpfile set key] assert_equal "OK" [run_cli_with_input_file $tmpfile set key]
assert_equal "from file" [r get key] assert_equal "from file" [r get key]
file delete $tmpfile
}
proc test_redis_cli_rdb_dump {} {
r flushdb
set dir [lindex [r config get dir] 1]
assert_equal "OK" [r debug populate 100000 key 1000]
catch {run_cli --rdb "$dir/cli.rdb"} output
assert_match {*Transfer finished with success*} $output
file delete "$dir/dump.rdb"
file rename "$dir/cli.rdb" "$dir/dump.rdb"
assert_equal "OK" [r set should-not-exist 1]
assert_equal "OK" [r debug reload nosave]
assert_equal {} [r get should-not-exist]
}
test "Dumping an RDB" {
# Disk-based master
assert_match "OK" [r config set repl-diskless-sync no]
test_redis_cli_rdb_dump
# Disk-less master
assert_match "OK" [r config set repl-diskless-sync yes]
assert_match "OK" [r config set repl-diskless-sync-delay 0]
test_redis_cli_rdb_dump
}
test "Connecting as a replica" {
set fd [open_cli "--replica"]
wait_for_condition 500 500 {
[string match {*slave0:*state=online*} [r info]]
} else {
fail "redis-cli --replica did not connect"
}
for {set i 0} {$i < 100} {incr i} {
r set test-key test-value-$i
}
r client kill type slave
catch {
assert_match {*SET*key-a*} [read_cli $fd]
}
close_cli $fd
}
test "Piping raw protocol" {
set cmds [tmpfile "cli_cmds"]
set cmds_fd [open $cmds "w"]
puts $cmds_fd [formatCommand select 9]
puts $cmds_fd [formatCommand del test-counter]
for {set i 0} {$i < 1000} {incr i} {
puts $cmds_fd [formatCommand incr test-counter]
puts $cmds_fd [formatCommand set large-key [string repeat "x" 20000]]
}
for {set i 0} {$i < 100} {incr i} {
puts $cmds_fd [formatCommand set very-large-key [string repeat "x" 512000]]
}
close $cmds_fd
set cli_fd [open_cli "--pipe" $cmds]
fconfigure $cli_fd -blocking true
set output [read_cli $cli_fd]
assert_equal {1000} [r get test-counter]
assert_match {*All data transferred*errors: 0*replies: 2102*} $output
file delete $cmds
} }
} }

View File

@ -423,6 +423,7 @@ test {diskless loading short read} {
} }
# Start the replication process... # Start the replication process...
set loglines [count_log_lines -1]
$master config set repl-diskless-sync-delay 0 $master config set repl-diskless-sync-delay 0
$replica replicaof $master_host $master_port $replica replicaof $master_host $master_port
@ -432,29 +433,30 @@ test {diskless loading short read} {
for {set i 0} {$i < $attempts} {incr i} { for {set i 0} {$i < $attempts} {incr i} {
# wait for the replica to start reading the rdb # wait for the replica to start reading the rdb
# using the log file since the replica only responds to INFO once in 2mb # using the log file since the replica only responds to INFO once in 2mb
wait_for_log_message -1 "*Loading DB in memory*" 5 2000 1 set res [wait_for_log_messages -1 {"*Loading DB in memory*"} $loglines 2000 1]
set loglines [lindex $res 1]
# add some additional random sleep so that we kill the master on a different place each time # add some additional random sleep so that we kill the master on a different place each time
after [expr {int(rand()*100)}] after [expr {int(rand()*50)}]
# kill the replica connection on the master # kill the replica connection on the master
set killed [$master client kill type replica] set killed [$master client kill type replica]
if {[catch { set res [wait_for_log_messages -1 {"*Internal error in RDB*" "*Finished with success*" "*Successful partial resynchronization*"} $loglines 1000 1]
set res [wait_for_log_message -1 "*Internal error in RDB*" 5 100 10] if {$::verbose} { puts $res }
if {$::verbose} { set log_text [lindex $res 0]
puts $res set loglines [lindex $res 1]
} if {![string match "*Internal error in RDB*" $log_text]} {
}]} {
puts "failed triggering short read"
# force the replica to try another full sync # force the replica to try another full sync
$master multi
$master client kill type replica $master client kill type replica
$master set asdf asdf $master set asdf asdf
# the side effect of resizing the backlog is that it is flushed (16k is the min size) # the side effect of resizing the backlog is that it is flushed (16k is the min size)
$master config set repl-backlog-size [expr {16384 + $i}] $master config set repl-backlog-size [expr {16384 + $i}]
$master exec
} }
# wait for loading to stop (fail) # wait for loading to stop (fail)
wait_for_condition 100 10 { wait_for_condition 1000 1 {
[s -1 loading] eq 0 [s -1 loading] eq 0
} else { } else {
fail "Replica didn't disconnect" fail "Replica didn't disconnect"
@ -528,6 +530,7 @@ start_server {tags {"repl"}} {
# start replication # start replication
# it's enough for just one replica to be slow, and have it's write handler enabled # it's enough for just one replica to be slow, and have it's write handler enabled
# so that the whole rdb generation process is bound to that # so that the whole rdb generation process is bound to that
set loglines [count_log_lines -1]
[lindex $replicas 0] config set repl-diskless-load swapdb [lindex $replicas 0] config set repl-diskless-load swapdb
[lindex $replicas 0] config set key-load-delay 100 [lindex $replicas 0] config set key-load-delay 100
[lindex $replicas 0] replicaof $master_host $master_port [lindex $replicas 0] replicaof $master_host $master_port
@ -535,7 +538,7 @@ start_server {tags {"repl"}} {
# wait for the replicas to start reading the rdb # wait for the replicas to start reading the rdb
# using the log file since the replica only responds to INFO once in 2mb # using the log file since the replica only responds to INFO once in 2mb
wait_for_log_message -1 "*Loading DB in memory*" 8 800 10 wait_for_log_messages -1 {"*Loading DB in memory*"} $loglines 800 10
if {$measure_time} { if {$measure_time} {
set master_statfile "/proc/$master_pid/stat" set master_statfile "/proc/$master_pid/stat"
@ -551,6 +554,7 @@ start_server {tags {"repl"}} {
$master incr $all_drop $master incr $all_drop
# disconnect replicas depending on the current test # disconnect replicas depending on the current test
set loglines [count_log_lines -2]
if {$all_drop == "all" || $all_drop == "fast"} { if {$all_drop == "all" || $all_drop == "fast"} {
exec kill [srv 0 pid] exec kill [srv 0 pid]
set replicas_alive [lreplace $replicas_alive 1 1] set replicas_alive [lreplace $replicas_alive 1 1]
@ -569,13 +573,13 @@ start_server {tags {"repl"}} {
# make sure we got what we were aiming for, by looking for the message in the log file # make sure we got what we were aiming for, by looking for the message in the log file
if {$all_drop == "all"} { if {$all_drop == "all"} {
wait_for_log_message -2 "*Diskless rdb transfer, last replica dropped, killing fork child*" 12 1 1 wait_for_log_messages -2 {"*Diskless rdb transfer, last replica dropped, killing fork child*"} $loglines 1 1
} }
if {$all_drop == "no"} { if {$all_drop == "no"} {
wait_for_log_message -2 "*Diskless rdb transfer, done reading from pipe, 2 replicas still up*" 12 1 1 wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 2 replicas still up*"} $loglines 1 1
} }
if {$all_drop == "slow" || $all_drop == "fast"} { if {$all_drop == "slow" || $all_drop == "fast"} {
wait_for_log_message -2 "*Diskless rdb transfer, done reading from pipe, 1 replicas still up*" 12 1 1 wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 1 replicas still up*"} $loglines 1 1
} }
# make sure we don't have a busy loop going thought epoll_wait # make sure we don't have a busy loop going thought epoll_wait

View File

@ -22,7 +22,10 @@ TEST_MODULES = \
blockonkeys.so \ blockonkeys.so \
scan.so \ scan.so \
datatype.so \ datatype.so \
auth.so auth.so \
keyspace_events.so \
blockedclient.so
.PHONY: all .PHONY: all

View File

@ -0,0 +1,85 @@
#define REDISMODULE_EXPERIMENTAL_API
#include "redismodule.h"
#include <assert.h>
#include <stdio.h>
#include <pthread.h>
#define UNUSED(V) ((void) V)
void *sub_worker(void *arg) {
// Get Redis module context
RedisModuleCtx *ctx = (RedisModuleCtx *)arg;
// Try acquiring GIL
int res = RedisModule_ThreadSafeContextTryLock(ctx);
// GIL is already taken by the calling thread expecting to fail.
assert(res != REDISMODULE_OK);
return NULL;
}
void *worker(void *arg) {
// Retrieve blocked client
RedisModuleBlockedClient *bc = (RedisModuleBlockedClient *)arg;
// Get Redis module context
RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(bc);
// Acquire GIL
RedisModule_ThreadSafeContextLock(ctx);
// Create another thread which will try to acquire the GIL
pthread_t tid;
int res = pthread_create(&tid, NULL, sub_worker, ctx);
assert(res == 0);
// Wait for thread
pthread_join(tid, NULL);
// Release GIL
RedisModule_ThreadSafeContextUnlock(ctx);
// Reply to client
RedisModule_ReplyWithSimpleString(ctx, "OK");
// Unblock client
RedisModule_UnblockClient(bc, NULL);
// Free the Redis module context
RedisModule_FreeThreadSafeContext(ctx);
return NULL;
}
int acquire_gil(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
UNUSED(argv);
UNUSED(argc);
/* This command handler tries to acquire the GIL twice
* once in the worker thread using "RedisModule_ThreadSafeContextLock"
* second in the sub-worker thread
* using "RedisModule_ThreadSafeContextTryLock"
* as the GIL is already locked. */
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
pthread_t tid;
int res = pthread_create(&tid, NULL, worker, bc);
assert(res == 0);
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "blockedclient", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, "acquire_gil", acquire_gil, "", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}

View File

@ -0,0 +1,112 @@
/* This module is used to test the server keyspace events API.
*
* -----------------------------------------------------------------------------
*
* Copyright (c) 2020, Meir Shpilraien <meir at redislabs dot com>
* 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.
*/
#define REDISMODULE_EXPERIMENTAL_API
#include "redismodule.h"
#include <stdio.h>
#include <string.h>
/** strores all the keys on which we got 'loaded' keyspace notification **/
RedisModuleDict *loaded_event_log = NULL;
static int KeySpace_Notification(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){
REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(type);
if(strcmp(event, "loaded") == 0){
const char* keyName = RedisModule_StringPtrLen(key, NULL);
int nokey;
RedisModule_DictGetC(loaded_event_log, (void*)keyName, strlen(keyName), &nokey);
if(nokey){
RedisModule_DictSetC(loaded_event_log, (void*)keyName, strlen(keyName), RedisModule_HoldString(ctx, key));
}
}
return REDISMODULE_OK;
}
static int cmdIsKeyLoaded(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
if(argc != 2){
return RedisModule_WrongArity(ctx);
}
const char* key = RedisModule_StringPtrLen(argv[1], NULL);
int nokey;
RedisModuleString* keyStr = RedisModule_DictGetC(loaded_event_log, (void*)key, strlen(key), &nokey);
RedisModule_ReplyWithArray(ctx, 2);
RedisModule_ReplyWithLongLong(ctx, !nokey);
if(nokey){
RedisModule_ReplyWithNull(ctx);
}else{
RedisModule_ReplyWithString(ctx, keyStr);
}
return REDISMODULE_OK;
}
/* This function must be present on each Redis module. It is used in order to
* register the commands into the Redis server. */
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx,"testkeyspace",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
loaded_event_log = RedisModule_CreateDict(ctx);
if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_LOADED, KeySpace_Notification) != REDISMODULE_OK){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx,"keyspace.is_key_loaded", cmdIsKeyLoaded,"",0,0,0) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
int RedisModule_OnUnload(RedisModuleCtx *ctx) {
RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(loaded_event_log, "^", NULL, 0);
char* key;
size_t keyLen;
RedisModuleString* val;
while((key = RedisModule_DictNextC(iter, &keyLen, (void**)&val))){
RedisModule_FreeString(ctx, val);
}
RedisModule_FreeDict(ctx, loaded_event_log);
loaded_event_log = NULL;
return REDISMODULE_OK;
}

View File

@ -195,6 +195,42 @@ int test_setlfu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int test_clientinfo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
(void) argv;
(void) argc;
RedisModuleClientInfo ci = { .version = REDISMODULE_CLIENTINFO_VERSION };
if (RedisModule_GetClientInfoById(&ci, RedisModule_GetClientId(ctx)) == REDISMODULE_ERR) {
RedisModule_ReplyWithError(ctx, "failed to get client info");
return REDISMODULE_OK;
}
RedisModule_ReplyWithArray(ctx, 10);
char flags[512];
snprintf(flags, sizeof(flags) - 1, "%s:%s:%s:%s:%s:%s",
ci.flags & REDISMODULE_CLIENTINFO_FLAG_SSL ? "ssl" : "",
ci.flags & REDISMODULE_CLIENTINFO_FLAG_PUBSUB ? "pubsub" : "",
ci.flags & REDISMODULE_CLIENTINFO_FLAG_BLOCKED ? "blocked" : "",
ci.flags & REDISMODULE_CLIENTINFO_FLAG_TRACKING ? "tracking" : "",
ci.flags & REDISMODULE_CLIENTINFO_FLAG_UNIXSOCKET ? "unixsocket" : "",
ci.flags & REDISMODULE_CLIENTINFO_FLAG_MULTI ? "multi" : "");
RedisModule_ReplyWithCString(ctx, "flags");
RedisModule_ReplyWithCString(ctx, flags);
RedisModule_ReplyWithCString(ctx, "id");
RedisModule_ReplyWithLongLong(ctx, ci.id);
RedisModule_ReplyWithCString(ctx, "addr");
RedisModule_ReplyWithCString(ctx, ci.addr);
RedisModule_ReplyWithCString(ctx, "port");
RedisModule_ReplyWithLongLong(ctx, ci.port);
RedisModule_ReplyWithCString(ctx, "db");
RedisModule_ReplyWithLongLong(ctx, ci.db);
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argc);
@ -221,6 +257,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.getlfu", test_getlfu,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"test.getlfu", test_getlfu,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.clientinfo", test_clientinfo,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK; return REDISMODULE_OK;
} }

View File

@ -55,11 +55,23 @@ void scan_key_callback(RedisModuleKey *key, RedisModuleString* field, RedisModul
REDISMODULE_NOT_USED(key); REDISMODULE_NOT_USED(key);
scan_key_pd* pd = privdata; scan_key_pd* pd = privdata;
RedisModule_ReplyWithArray(pd->ctx, 2); RedisModule_ReplyWithArray(pd->ctx, 2);
RedisModule_ReplyWithString(pd->ctx, field); size_t fieldCStrLen;
if (value)
RedisModule_ReplyWithString(pd->ctx, value); // The implementation of RedisModuleString is robj with lots of encodings.
else // We want to make sure the robj that passes to this callback in
// String encoded, this is why we use RedisModule_StringPtrLen and
// RedisModule_ReplyWithStringBuffer instead of directly use
// RedisModule_ReplyWithString.
const char* fieldCStr = RedisModule_StringPtrLen(field, &fieldCStrLen);
RedisModule_ReplyWithStringBuffer(pd->ctx, fieldCStr, fieldCStrLen);
if(value){
size_t valueCStrLen;
const char* valueCStr = RedisModule_StringPtrLen(value, &valueCStrLen);
RedisModule_ReplyWithStringBuffer(pd->ctx, valueCStr, valueCStrLen);
} else {
RedisModule_ReplyWithNull(pd->ctx); RedisModule_ReplyWithNull(pd->ctx);
}
pd->nreplies++; pd->nreplies++;
} }

View File

@ -11,8 +11,8 @@ proc rediscli_tls_config {testsdir} {
} }
} }
proc rediscli {port {opts {}}} { proc rediscli {host port {opts {}}} {
set cmd [list src/keydb-cli -p $port] set cmd [list src/keydb-cli --no-motd -h $host -p $port]
lappend cmd {*}[rediscli_tls_config "tests"] lappend cmd {*}[rediscli_tls_config "tests"]
lappend cmd {*}$opts lappend cmd {*}$opts
return $cmd return $cmd

View File

@ -13,23 +13,34 @@ proc start_server_error {config_file error} {
} }
proc check_valgrind_errors stderr { proc check_valgrind_errors stderr {
set fd [open $stderr] set res [find_valgrind_errors $stderr]
set buf [read $fd] if {$res != ""} {
close $fd send_data_packet $::test_server_fd err "Valgrind error: $res\n"
if {[regexp -- { at 0x} $buf] ||
(![regexp -- {definitely lost: 0 bytes} $buf] &&
![regexp -- {no leaks are possible} $buf])} {
send_data_packet $::test_server_fd err "Valgrind error: $buf\n"
} }
} }
proc clean_persistence config {
# we may wanna keep the logs for later, but let's clean the persistence
# files right away, since they can accumulate and take up a lot of space
set config [dict get $config "config"]
set rdb [format "%s/%s" [dict get $config "dir"] "dump.rdb"]
set aof [format "%s/%s" [dict get $config "dir"] "appendonly.aof"]
catch {exec rm -rf $rdb}
catch {exec rm -rf $aof}
}
proc kill_server config { proc kill_server config {
# nothing to kill when running against external server # nothing to kill when running against external server
if {$::external} return if {$::external} return
# nevermind if its already dead # nevermind if its already dead
if {![is_alive $config]} { return } if {![is_alive $config]} {
# Check valgrind errors if needed
if {$::valgrind} {
check_valgrind_errors [dict get $config stderr]
}
return
}
set pid [dict get $config pid] set pid [dict get $config pid]
# check for leaks # check for leaks
@ -137,7 +148,19 @@ proc server_is_up {host port retrynum} {
# doesn't really belong here, but highly coupled to code in start_server # doesn't really belong here, but highly coupled to code in start_server
proc tags {tags code} { proc tags {tags code} {
# If we 'tags' contain multiple tags, quoted and seperated by spaces,
# we want to get rid of the quotes in order to have a proper list
set tags [string map { \" "" } $tags]
set ::tags [concat $::tags $tags] set ::tags [concat $::tags $tags]
# We skip unwanted tags
foreach tag $::denytags {
if {[lsearch $::tags $tag] >= 0} {
incr ::num_aborted
send_data_packet $::test_server_fd ignore "Tag: $tag"
set ::tags [lrange $::tags 0 end-[llength $tags]]
return
}
}
uplevel 1 $code uplevel 1 $code
set ::tags [lrange $::tags 0 end-[llength $tags]] set ::tags [lrange $::tags 0 end-[llength $tags]]
} }
@ -153,7 +176,96 @@ proc create_server_config_file {filename config} {
close $fp close $fp
} }
proc spawn_server {config_file stdout stderr} {
if {$::valgrind} {
set pid [exec valgrind --track-origins=yes --trace-children=yes --suppressions=[pwd]/src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/keydb-server $config_file >> $stdout 2>> $stderr &]
} elseif ($::stack_logging) {
set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/keydb-server $config_file >> $stdout 2>> $stderr &]
} else {
set pid [exec src/keydb-server $config_file >> $stdout 2>> $stderr &]
}
if {$::wait_server} {
set msg "server started PID: $pid. press any key to continue..."
puts $msg
read stdin 1
}
# Tell the test server about this new instance.
send_data_packet $::test_server_fd server-spawned $pid
return $pid
}
# Wait for actual startup, return 1 if port is busy, 0 otherwise
proc wait_server_started {config_file stdout pid} {
set checkperiod 100; # Milliseconds
set maxiter [expr {120*1000/$checkperiod}] ; # Wait up to 2 minutes.
set port_busy 0
while 1 {
if {[regexp -- " PID: $pid" [exec cat $stdout]]} {
break
}
after $checkperiod
incr maxiter -1
if {$maxiter == 0} {
start_server_error $config_file "No PID detected in log $stdout"
puts "--- LOG CONTENT ---"
puts [exec cat $stdout]
puts "-------------------"
break
}
# Check if the port is actually busy and the server failed
# for this reason.
if {[regexp {Could not create server TCP} [exec cat $stdout]]} {
set port_busy 1
break
}
}
return $port_busy
}
proc start_server {options {code undefined}} { proc start_server {options {code undefined}} {
# setup defaults
set baseconfig "default.conf"
set overrides {}
set tags {}
set keep_persistence false
# parse options
foreach {option value} $options {
switch $option {
"config" {
set baseconfig $value
}
"overrides" {
set overrides $value
}
"tags" {
# If we 'tags' contain multiple tags, quoted and seperated by spaces,
# we want to get rid of the quotes in order to have a proper list
set tags [string map { \" "" } $value]
set ::tags [concat $::tags $tags]
}
"keep_persistence" {
set keep_persistence $value
}
default {
error "Unknown option $option"
}
}
}
# We skip unwanted tags
foreach tag $::denytags {
if {[lsearch $::tags $tag] >= 0} {
incr ::num_aborted
send_data_packet $::test_server_fd ignore "Tag: $tag"
set ::tags [lrange $::tags 0 end-[llength $tags]]
return
}
}
# If we are running against an external server, we just push the # If we are running against an external server, we just push the
# host/port pair in the stack the first time # host/port pair in the stack the first time
if {$::external} { if {$::external} {
@ -165,31 +277,30 @@ proc start_server {options {code undefined}} {
dict set srv "client" $client dict set srv "client" $client
$client select 9 $client select 9
set config {}
dict set config "port" $::port
dict set srv "config" $config
# append the server to the stack # append the server to the stack
lappend ::servers $srv lappend ::servers $srv
} }
uplevel 1 $code r flushall
return if {[catch {set retval [uplevel 1 $code]} error]} {
} if {$::durable} {
set msg [string range $error 10 end]
lappend details $msg
lappend details $::errorInfo
lappend ::tests_failed $details
# setup defaults incr ::num_failed
set baseconfig "default.conf" send_data_packet $::test_server_fd err [join $details "\n"]
set overrides {} } else {
set tags {} # Re-raise, let handler up the stack take care of this.
error $error $::errorInfo
# parse options }
foreach {option value} $options {
switch $option {
"config" {
set baseconfig $value }
"overrides" {
set overrides $value }
"tags" {
set tags $value
set ::tags [concat $::tags $value] }
default {
error "Unknown option $option" }
} }
set ::tags [lrange $::tags 0 end-[llength $tags]]
return
} }
set data [split [exec cat "tests/assets/$baseconfig"] "\n"] set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
@ -242,6 +353,13 @@ proc start_server {options {code undefined}} {
set stdout [format "%s/%s" [dict get $config "dir"] "stdout"] set stdout [format "%s/%s" [dict get $config "dir"] "stdout"]
set stderr [format "%s/%s" [dict get $config "dir"] "stderr"] set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
# if we're inside a test, write the test name to the server log file
if {[info exists ::cur_test]} {
set fd [open $stdout "a+"]
puts $fd "### Starting server for test $::cur_test"
close $fd
}
# We need a loop here to retry with different ports. # We need a loop here to retry with different ports.
set server_started 0 set server_started 0
while {$server_started == 0} { while {$server_started == 0} {
@ -251,44 +369,10 @@ proc start_server {options {code undefined}} {
send_data_packet $::test_server_fd "server-spawning" "port $port" send_data_packet $::test_server_fd "server-spawning" "port $port"
if {$::valgrind} { set pid [spawn_server $config_file $stdout $stderr]
set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/keydb-server $config_file > $stdout 2> $stderr &]
} elseif ($::stack_logging) {
set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/keydb-server $config_file > $stdout 2> $stderr &]
} else {
set pid [exec src/keydb-server $config_file > $stdout 2> $stderr &]
}
# Tell the test server about this new instance.
send_data_packet $::test_server_fd server-spawned $pid
# check that the server actually started # check that the server actually started
# ugly but tries to be as fast as possible... set port_busy [wait_server_started $config_file $stdout $pid]
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
# Wait for actual startup
set checkperiod 100; # Milliseconds
set maxiter [expr {120*1000/100}] ; # Wait up to 2 minutes.
set port_busy 0
while {![info exists _pid]} {
regexp {PID:\s(\d+)} [exec cat $stdout] _ _pid
after $checkperiod
incr maxiter -1
if {$maxiter == 0} {
start_server_error $config_file "No PID detected in log $stdout"
puts "--- LOG CONTENT ---"
puts [exec cat $stdout]
puts "-------------------"
break
}
# Check if the port is actually busy and the server failed
# for this reason.
if {[regexp {Could not create server TCP} [exec cat $stdout]]} {
set port_busy 1
break
}
}
# Sometimes we have to try a different port, even if we checked # Sometimes we have to try a different port, even if we checked
# for availability. Other test clients may grab the port before we # for availability. Other test clients may grab the port before we
@ -302,9 +386,15 @@ proc start_server {options {code undefined}} {
dict set config port $port dict set config port $port
} }
create_server_config_file $config_file $config create_server_config_file $config_file $config
# Truncate log so wait_server_started will not be looking at
# output of the failed server.
close [open $stdout "w"]
continue; # Try again continue; # Try again
} }
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
if {$code ne "undefined"} { if {$code ne "undefined"} {
set serverisup [server_is_up $::host $port $retrynum] set serverisup [server_is_up $::host $port $retrynum]
} else { } else {
@ -348,12 +438,6 @@ proc start_server {options {code undefined}} {
error_and_quit $config_file $line error_and_quit $config_file $line
} }
if {$::wait_server} {
set msg "server started PID: [dict get $srv "pid"]. press any key to continue..."
puts $msg
read stdin 1
}
while 1 { while 1 {
# check that the server actually started and is ready for connections # check that the server actually started and is ready for connections
if {[exec grep -i "Ready to accept" | wc -l < $stdout] > 0} { if {[exec grep -i "Ready to accept" | wc -l < $stdout] > 0} {
@ -373,6 +457,12 @@ proc start_server {options {code undefined}} {
if {[catch { uplevel 1 $code } error]} { if {[catch { uplevel 1 $code } error]} {
set backtrace $::errorInfo set backtrace $::errorInfo
# fetch srv back from the server list, in case it was restarted by restart_server (new PID)
set srv [lindex $::servers end]
# pop the server object
set ::servers [lrange $::servers 0 end-1]
# Kill the server without checking for leaks # Kill the server without checking for leaks
dict set srv "skipleaks" 1 dict set srv "skipleaks" 1
kill_server $srv kill_server $srv
@ -387,9 +477,23 @@ proc start_server {options {code undefined}} {
} }
puts "" puts ""
error $error $backtrace if {$::durable} {
set msg [string range $error 10 end]
lappend details $msg
lappend details $backtrace
lappend ::tests_failed $details
incr ::num_failed
send_data_packet $::test_server_fd err [join $details "\n"]
} else {
# Re-raise, let handler up the stack take care of this.
error $error $backtrace
}
} }
# fetch srv back from the server list, in case it was restarted by restart_server (new PID)
set srv [lindex $::servers end]
# Don't do the leak check when no tests were run # Don't do the leak check when no tests were run
if {$num_tests == $::num_tests} { if {$num_tests == $::num_tests} {
dict set srv "skipleaks" 1 dict set srv "skipleaks" 1
@ -400,8 +504,51 @@ proc start_server {options {code undefined}} {
set ::tags [lrange $::tags 0 end-[llength $tags]] set ::tags [lrange $::tags 0 end-[llength $tags]]
kill_server $srv kill_server $srv
if {!$keep_persistence} {
clean_persistence $srv
}
set _ ""
} else { } else {
set ::tags [lrange $::tags 0 end-[llength $tags]] set ::tags [lrange $::tags 0 end-[llength $tags]]
set _ $srv set _ $srv
} }
} }
proc restart_server {level wait_ready} {
set srv [lindex $::servers end+$level]
kill_server $srv
set stdout [dict get $srv "stdout"]
set stderr [dict get $srv "stderr"]
set config_file [dict get $srv "config_file"]
# if we're inside a test, write the test name to the server log file
if {[info exists ::cur_test]} {
set fd [open $stdout "a+"]
puts $fd "### Restarting server for test $::cur_test"
close $fd
}
set prev_ready_count [exec grep -i "Ready to accept" | wc -l < $stdout]
set pid [spawn_server $config_file $stdout $stderr]
# check that the server actually started
wait_server_started $config_file $stdout $pid
# update the pid in the servers list
dict set srv "pid" $pid
# re-set $srv in the servers list
lset ::servers end+$level $srv
if {$wait_ready} {
while 1 {
# check that the server actually started and is ready for connections
if {[exec grep -i "Ready to accept" | wc -l < $stdout] > $prev_ready_count} {
break
}
after 10
}
}
reconnect $level
}

View File

@ -4,6 +4,7 @@ set ::num_failed 0
set ::num_skipped 0 set ::num_skipped 0
set ::num_aborted 0 set ::num_aborted 0
set ::tests_failed {} set ::tests_failed {}
set ::cur_test ""
proc fail {msg} { proc fail {msg} {
error "assertion:$msg" error "assertion:$msg"
@ -99,16 +100,7 @@ proc wait_for_condition {maxtries delay e _else_ elsescript} {
} }
} }
proc test {name code {okpattern undefined}} { proc test {name code {okpattern undefined} {options undefined}} {
# abort if tagged with a tag to deny
foreach tag $::denytags {
if {[lsearch $::tags $tag] >= 0} {
incr ::num_aborted
send_data_packet $::test_server_fd ignore $name
return
}
}
# abort if test name in skiptests # abort if test name in skiptests
if {[lsearch $::skiptests $name] >= 0} { if {[lsearch $::skiptests $name] >= 0} {
incr ::num_skipped incr ::num_skipped
@ -143,16 +135,39 @@ proc test {name code {okpattern undefined}} {
set details {} set details {}
lappend details "$name in $::curfile" lappend details "$name in $::curfile"
# set a cur_test global to be logged into new servers that are spown
# and log the test name in all existing servers
set prev_test $::cur_test
set ::cur_test "$name in $::curfile"
if {!$::external} {
foreach srv $::servers {
set stdout [dict get $srv stdout]
set fd [open $stdout "a+"]
puts $fd "### Starting test $::cur_test"
close $fd
}
}
send_data_packet $::test_server_fd testing $name send_data_packet $::test_server_fd testing $name
if {[catch {set retval [uplevel 1 $code]} error]} { if {[catch {set retval [uplevel 1 $code]} error]} {
if {[string match "assertion:*" $error]} { set assertion [string match "assertion:*" $error]
if {$assertion || $::durable} {
set msg [string range $error 10 end] set msg [string range $error 10 end]
lappend details $msg lappend details $msg
if {!$assertion} {
lappend details $::errorInfo
}
lappend ::tests_failed $details lappend ::tests_failed $details
incr ::num_failed incr ::num_failed
send_data_packet $::test_server_fd err [join $details "\n"] send_data_packet $::test_server_fd err [join $details "\n"]
if {$::stop_on_failure} {
puts "Test error (last server port:[srv port], log:[srv stdout]), press enter to teardown the test."
flush stdout
gets stdin
}
} else { } else {
# Re-raise, let handler up the stack take care of this. # Re-raise, let handler up the stack take care of this.
error $error $::errorInfo error $error $::errorInfo
@ -177,4 +192,5 @@ proc test {name code {okpattern undefined}} {
send_data_packet $::test_server_fd err "Detected a memory leak in test '$name': $output" send_data_packet $::test_server_fd err "Detected a memory leak in test '$name': $output"
} }
} }
set ::cur_test $prev_test
} }

View File

@ -106,25 +106,61 @@ proc wait_for_ofs_sync {r1 r2} {
} }
} }
proc wait_for_log_message {srv_idx pattern last_lines maxtries delay} { proc wait_done_loading r {
wait_for_condition 50 100 {
[catch {$r ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
}
# count current log lines in server's stdout
proc count_log_lines {srv_idx} {
set _ [exec wc -l < [srv $srv_idx stdout]]
}
# verify pattern exists in server's sdtout after a certain line number
proc verify_log_message {srv_idx pattern from_line} {
incr from_line
set result [exec tail -n +$from_line < [srv $srv_idx stdout]]
if {![string match $pattern $result]} {
error "assertion:expected message not found in log file: $pattern"
}
}
# wait for pattern to be found in server's stdout after certain line number
# return value is a list containing the line that matched the pattern and the line number
proc wait_for_log_messages {srv_idx patterns from_line maxtries delay} {
set retry $maxtries set retry $maxtries
set next_line [expr $from_line + 1] ;# searching form the line after
set stdout [srv $srv_idx stdout] set stdout [srv $srv_idx stdout]
while {$retry} { while {$retry} {
set result [exec tail -$last_lines < $stdout] set result [exec tail -n +$next_line < $stdout]
set result [split $result "\n"] set result [split $result "\n"]
foreach line $result { foreach line $result {
if {[string match $pattern $line]} { foreach pattern $patterns {
return $line if {[string match $pattern $line]} {
return [list $line $next_line]
}
} }
incr next_line
} }
incr retry -1 incr retry -1
after $delay after $delay
} }
if {$retry == 0} { if {$retry == 0} {
fail "log message of '$pattern' not found" fail "log message of '$patterns' not found in $stdout after line: $from_line till line: [expr $next_line -1]"
} }
} }
# write line to server log file
proc write_log_line {srv_idx msg} {
set logfile [srv $srv_idx stdout]
set fd [open $logfile "a+"]
puts $fd "### $msg"
close $fd
}
# Random integer between 0 and max (excluded). # Random integer between 0 and max (excluded).
proc randomInt {max} { proc randomInt {max} {
expr {int(rand()*$max)} expr {int(rand()*$max)}
@ -403,6 +439,29 @@ proc colorstr {color str} {
} }
} }
proc find_valgrind_errors {stderr} {
set fd [open $stderr]
set buf [read $fd]
close $fd
# Look for stack trace (" at 0x") and other errors (Invalid, Mismatched, etc).
# Look for "Warnings", but not the "set address range perms". These don't indicate any real concern.
# Look for the absense of a leak free summary (happens when redis isn't terminated properly).
if {[regexp -- { at 0x} $buf] ||
[regexp -- {^(?=.*Warning)(?:(?!set address range perms).)*$} $buf] ||
[regexp -- {Invalid} $buf] ||
[regexp -- {Mismatched} $buf] ||
[regexp -- {uninitialized} $buf] ||
[regexp -- {has a fishy} $buf] ||
[regexp -- {overlap} $buf] ||
(![regexp -- {definitely lost: 0 bytes} $buf] &&
![regexp -- {no leaks are possible} $buf])} {
return $buf
}
return ""
}
# Execute a background process writing random data for the specified number # Execute a background process writing random data for the specified number
# of seconds to the specified Redis instance. # of seconds to the specified Redis instance.
proc start_write_load {host port seconds} { proc start_write_load {host port seconds} {
@ -442,3 +501,28 @@ proc start_bg_complex_data {host port db ops} {
proc stop_bg_complex_data {handle} { proc stop_bg_complex_data {handle} {
catch {exec /bin/kill -9 $handle} catch {exec /bin/kill -9 $handle}
} }
proc populate {num prefix size} {
set rd [redis_deferring_client]
for {set j 0} {$j < $num} {incr j} {
$rd set $prefix$j [string repeat A $size]
}
for {set j 0} {$j < $num} {incr j} {
$rd read
}
$rd close
}
proc get_child_pid {idx} {
set pid [srv $idx pid]
if {[string match {*Darwin*} [exec uname -a]]} {
set fd [open "|pgrep -P $pid" "r"]
set child_pid [string trim [lindex [split [read $fd] \n] 0]]
} else {
set fd [open "|ps --ppid $pid -o pid" "r"]
set child_pid [string trim [lindex [split [read $fd] \n] 1]]
}
close $fd
return $child_pid
}

View File

@ -38,6 +38,7 @@ set ::all_tests {
unit/rreplay unit/rreplay
unit/cron unit/cron
unit/replication unit/replication
unit/latency-monitor
integration/block-repl integration/block-repl
integration/replication integration/replication
integration/replication-2 integration/replication-2
@ -53,6 +54,7 @@ set ::all_tests {
integration/psync2 integration/psync2
integration/psync2-reg integration/psync2-reg
integration/psync2-pingoff integration/psync2-pingoff
integration/redis-cli
unit/pubsub unit/pubsub
unit/slowlog unit/slowlog
unit/scripting unit/scripting
@ -71,6 +73,7 @@ set ::all_tests {
unit/pendingquerybuf unit/pendingquerybuf
unit/tls unit/tls
unit/tracking unit/tracking
unit/oom-score-adj
} }
# Index to the next test to run in the ::all_tests list. # Index to the next test to run in the ::all_tests list.
set ::next_test 0 set ::next_test 0
@ -81,12 +84,14 @@ set ::baseport 21111; # initial port for spawned redis servers
set ::portcount 8000; # we don't wanna use more than 10000 to avoid collision with cluster bus ports set ::portcount 8000; # we don't wanna use more than 10000 to avoid collision with cluster bus ports
set ::traceleaks 0 set ::traceleaks 0
set ::valgrind 0 set ::valgrind 0
set ::durable 0
set ::tls 0 set ::tls 0
set ::stack_logging 0 set ::stack_logging 0
set ::verbose 0 set ::verbose 0
set ::quiet 0 set ::quiet 0
set ::denytags {} set ::denytags {}
set ::skiptests {} set ::skiptests {}
set ::skipunits {}
set ::allowtags {} set ::allowtags {}
set ::only_tests {} set ::only_tests {}
set ::single_tests {} set ::single_tests {}
@ -202,6 +207,21 @@ proc redis_deferring_client {args} {
return $client return $client
} }
proc redis_client {args} {
set level 0
if {[llength $args] > 0 && [string is integer [lindex $args 0]]} {
set level [lindex $args 0]
set args [lrange $args 1 end]
}
# create client that defers reading reply
set client [redis [srv $level "host"] [srv $level "port"] 0 $::tls]
# select the right db and read the response (OK)
$client select 9
return $client
}
# Provide easy access to INFO properties. Same semantic as "proc r". # Provide easy access to INFO properties. Same semantic as "proc r".
proc s {args} { proc s {args} {
set level 0 set level 0
@ -352,8 +372,8 @@ proc read_from_test_client fd {
puts $err puts $err
lappend ::failed_tests $err lappend ::failed_tests $err
set ::active_clients_task($fd) "(ERR) $data" set ::active_clients_task($fd) "(ERR) $data"
if {$::stop_on_failure} { if {$::stop_on_failure} {
puts -nonewline "(Test stopped, press enter to continue)" puts -nonewline "(Test stopped, press enter to resume the tests)"
flush stdout flush stdout
gets stdin gets stdin
} }
@ -415,6 +435,12 @@ proc lpop {listVar {count 1}} {
set ele set ele
} }
proc lremove {listVar value} {
upvar 1 $listVar var
set idx [lsearch -exact $var $value]
set var [lreplace $var $idx $idx]
}
# A new client is idle. Remove it from the list of active clients and # A new client is idle. Remove it from the list of active clients and
# if there are still test units to run, launch them. # if there are still test units to run, launch them.
proc signal_idle_client fd { proc signal_idle_client fd {
@ -507,6 +533,7 @@ proc send_data_packet {fd status data} {
proc print_help_screen {} { proc print_help_screen {} {
puts [join { puts [join {
"--valgrind Run the test over valgrind." "--valgrind Run the test over valgrind."
"--durable suppress test crashes and keep running"
"--stack-logging Enable OSX leaks/malloc stack logging." "--stack-logging Enable OSX leaks/malloc stack logging."
"--accurate Run slow randomized tests for more iterations." "--accurate Run slow randomized tests for more iterations."
"--quiet Don't show individual tests." "--quiet Don't show individual tests."
@ -514,11 +541,13 @@ proc print_help_screen {} {
"--list-tests List all the available test units." "--list-tests List all the available test units."
"--only <test> Just execute the specified test by test name. this option can be repeated." "--only <test> Just execute the specified test by test name. this option can be repeated."
"--skip-till <unit> Skip all units until (and including) the specified one." "--skip-till <unit> Skip all units until (and including) the specified one."
"--skipunit <unit> Skip one unit."
"--clients <num> Number of test clients (default 16)." "--clients <num> Number of test clients (default 16)."
"--timeout <sec> Test timeout in seconds (default 10 min)." "--timeout <sec> Test timeout in seconds (default 10 min)."
"--force-failure Force the execution of a test that always fails." "--force-failure Force the execution of a test that always fails."
"--config <k> <v> Extra config file argument." "--config <k> <v> Extra config file argument."
"--skipfile <file> Name of a file containing test names that should be skipped (one per line)." "--skipfile <file> Name of a file containing test names that should be skipped (one per line)."
"--skiptest <name> Name of a file containing test names that should be skipped (one per line)."
"--dont-clean Don't delete redis log files after the run." "--dont-clean Don't delete redis log files after the run."
"--stop Blocks once the first test fails." "--stop Blocks once the first test fails."
"--loop Execute the specified set of tests forever." "--loop Execute the specified set of tests forever."
@ -556,6 +585,9 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
set file_data [read $fp] set file_data [read $fp]
close $fp close $fp
set ::skiptests [split $file_data "\n"] set ::skiptests [split $file_data "\n"]
} elseif {$opt eq {--skiptest}} {
lappend ::skiptests $arg
incr j
} elseif {$opt eq {--valgrind}} { } elseif {$opt eq {--valgrind}} {
set ::valgrind 1 set ::valgrind 1
} elseif {$opt eq {--stack-logging}} { } elseif {$opt eq {--stack-logging}} {
@ -594,6 +626,9 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} elseif {$opt eq {--only}} { } elseif {$opt eq {--only}} {
lappend ::only_tests $arg lappend ::only_tests $arg
incr j incr j
} elseif {$opt eq {--skipunit}} {
lappend ::skipunits $arg
incr j
} elseif {$opt eq {--skip-till}} { } elseif {$opt eq {--skip-till}} {
set ::skip_till $arg set ::skip_till $arg
incr j incr j
@ -611,6 +646,8 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} elseif {$opt eq {--clients}} { } elseif {$opt eq {--clients}} {
set ::numclients $arg set ::numclients $arg
incr j incr j
} elseif {$opt eq {--durable}} {
set ::durable 1
} elseif {$opt eq {--dont-clean}} { } elseif {$opt eq {--dont-clean}} {
set ::dont_clean 1 set ::dont_clean 1
} elseif {$opt eq {--wait-server}} { } elseif {$opt eq {--wait-server}} {
@ -636,13 +673,23 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} }
} }
# If --skil-till option was given, we populate the list of single tests set filtered_tests {}
# Set the filtered tests to be the short list (single_tests) if exists.
# Otherwise, we start filtering all_tests
if {[llength $::single_tests] > 0} {
set filtered_tests $::single_tests
} else {
set filtered_tests $::all_tests
}
# If --skip-till option was given, we populate the list of single tests
# to run with everything *after* the specified unit. # to run with everything *after* the specified unit.
if {$::skip_till != ""} { if {$::skip_till != ""} {
set skipping 1 set skipping 1
foreach t $::all_tests { foreach t $::all_tests {
if {$skipping == 0} { if {$skipping == 1} {
lappend ::single_tests $t lremove filtered_tests $t
} }
if {$t == $::skip_till} { if {$t == $::skip_till} {
set skipping 0 set skipping 0
@ -654,10 +701,20 @@ if {$::skip_till != ""} {
} }
} }
# If --skipunits option was given, we populate the list of single tests
# to run with everything *not* in the skipunits list.
if {[llength $::skipunits] > 0} {
foreach t $::all_tests {
if {[lsearch $::skipunits $t] != -1} {
lremove filtered_tests $t
}
}
}
# Override the list of tests with the specific tests we want to run # Override the list of tests with the specific tests we want to run
# in case there was some filter, that is --single or --skip-till options. # in case there was some filter, that is --single, -skipunit or --skip-till options.
if {[llength $::single_tests] > 0} { if {[llength $filtered_tests] < [llength $::all_tests]} {
set ::all_tests $::single_tests set ::all_tests $filtered_tests
} }
proc attach_to_replication_stream {} { proc attach_to_replication_stream {} {

View File

@ -255,4 +255,48 @@ start_server {tags {"acl"}} {
r ACL setuser default on r ACL setuser default on
set e set e
} {*NOAUTH*} } {*NOAUTH*}
test {ACL HELP should not have unexpected options} {
catch {r ACL help xxx} e
assert_match "*Unknown subcommand or wrong number of arguments*" $e
}
}
set server_path [tmpdir "server.acl"]
exec cp -f tests/assets/user.acl $server_path
start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"]] {
# user alice on allcommands allkeys >alice
# user bob on -@all +@set +acl ~set* >bob
test "Alice: can excute all command" {
r AUTH alice alice
assert_equal "alice" [r acl whoami]
r SET key value
}
test "Bob: just excute @set and acl command" {
r AUTH bob bob
assert_equal "bob" [r acl whoami]
assert_equal "3" [r sadd set 1 2 3]
catch {r SET key value} e
set e
} {*NOPERM*}
test "ACL load and save" {
r ACL setuser eve +get allkeys >eve on
r ACL save
# ACL load will free user and kill clients
r ACL load
catch {r ACL LIST} e
assert_match {*I/O error*} $e
reconnect
r AUTH alice alice
r SET key value
r AUTH eve eve
r GET key
catch {r SET key value} e
set e
} {*NOPERM*}
} }

View File

@ -36,7 +36,18 @@ start_server {tags {"dump"}} {
assert {$ttl >= 2900 && $ttl <= 3100} assert {$ttl >= 2900 && $ttl <= 3100}
r get foo r get foo
} {bar} } {bar}
test {RESTORE with ABSTTL in the past} {
r set foo bar
set encoded [r dump foo]
set now [clock milliseconds]
r debug set-active-expire 0
r restore foo [expr $now-3000] $encoded absttl REPLACE
catch {r debug object foo} e
r debug set-active-expire 1
set e
} {ERR no such key}
test {RESTORE can set LRU} { test {RESTORE can set LRU} {
r set foo bar r set foo bar
set encoded [r dump foo] set encoded [r dump foo]

View File

@ -90,6 +90,7 @@ start_server {tags {"introspection"}} {
bio_cpulist bio_cpulist
aof_rewrite_cpulist aof_rewrite_cpulist
bgsave_cpulist bgsave_cpulist
active-replica
} }
if {!$::tls} { if {!$::tls} {
@ -135,4 +136,28 @@ start_server {tags {"introspection"}} {
} }
} }
# Do a force-all config rewrite and make sure we're able to parse
# it.
test {CONFIG REWRITE sanity} {
# Capture state of config before
set configs {}
foreach {k v} [r config get *] {
dict set configs $k $v
}
# Rewrite entire configuration, restart and confirm the
# server is able to parse it and start.
assert_equal [r debug config-rewrite-force-all] "OK"
restart_server 0 0
assert_equal [r ping] "PONG"
# Verify no changes were introduced
dict for {k v} $configs {
assert_equal $v [lindex [r config get $k] 1] $k
}
}
# Config file at this point is at a wierd state, and includes all
# known keywords. Might be a good idea to avoid adding tests here.
} }

View File

@ -50,15 +50,26 @@ start_server {tags {"latency-monitor"}} {
test {LATENCY of expire events are correctly collected} { test {LATENCY of expire events are correctly collected} {
r config set latency-monitor-threshold 20 r config set latency-monitor-threshold 20
r flushdb
if {$::valgrind} {set count 100000} else {set count 1000000}
r eval { r eval {
local i = 0 local i = 0
while (i < 1000000) do while (i < tonumber(ARGV[1])) do
redis.call('sadd','mybigkey',i) redis.call('sadd',KEYS[1],i)
i = i+1 i = i+1
end end
} 0 } 1 mybigkey $count
r pexpire mybigkey 1 r pexpire mybigkey 50
after 500 wait_for_condition 5 100 {
[r dbsize] == 0
} else {
fail "key wasn't expired"
}
assert_match {*expire-cycle*} [r latency latest] assert_match {*expire-cycle*} [r latency latest]
} }
test {LATENCY HELP should not have unexpected options} {
catch {r LATENCY help xxx} e
assert_match "*Unknown subcommand or wrong number of arguments*" $e
}
} }

View File

@ -0,0 +1,11 @@
# source tests/support/util.tcl
set testmodule [file normalize tests/modules/blockedclient.so]
start_server {tags {"modules"}} {
r module load $testmodule
test {Locked GIL acquisition} {
assert_match "OK" [r acquire_gil]
}
}

View File

@ -114,6 +114,21 @@ tags "modules" {
test {Test master link down hook} { test {Test master link down hook} {
r client kill type master r client kill type master
assert_equal [r hooks.event_count masterlink-down] 1 assert_equal [r hooks.event_count masterlink-down] 1
wait_for_condition 50 100 {
[string match {*master_link_status:up*} [r info replication]]
} else {
fail "Replica didn't reconnect"
}
assert_equal [r hooks.event_count masterlink-down] 1
assert_equal [r hooks.event_count masterlink-up] 2
}
wait_for_condition 50 10 {
[string match {*master_link_status:up*} [r info replication]]
} else {
fail "Can't turn the instance into a replica"
} }
$replica replicaof no one $replica replicaof no one
@ -125,8 +140,8 @@ tags "modules" {
} }
test {Test replica-offline hook} { test {Test replica-offline hook} {
assert_equal [r -1 hooks.event_count replica-online] 1 assert_equal [r -1 hooks.event_count replica-online] 2
assert_equal [r -1 hooks.event_count replica-offline] 1 assert_equal [r -1 hooks.event_count replica-offline] 2
} }
# get the replica stdout, to be used by the next test # get the replica stdout, to be used by the next test
set replica_stdout [srv 0 stdout] set replica_stdout [srv 0 stdout]

View File

@ -0,0 +1,22 @@
set testmodule [file normalize tests/modules/keyspace_events.so]
tags "modules" {
start_server [list overrides [list loadmodule "$testmodule"]] {
test {Test loaded key space event} {
r set x 1
r hset y f v
r lpush z 1 2 3
r sadd p 1 2 3
r zadd t 1 f1 2 f2
r xadd s * f v
r debug reload
assert_equal {1 x} [r keyspace.is_key_loaded x]
assert_equal {1 y} [r keyspace.is_key_loaded y]
assert_equal {1 z} [r keyspace.is_key_loaded z]
assert_equal {1 p} [r keyspace.is_key_loaded p]
assert_equal {1 t} [r keyspace.is_key_loaded t]
assert_equal {1 s} [r keyspace.is_key_loaded s]
}
}
}

View File

@ -67,4 +67,23 @@ start_server {tags {"modules"}} {
assert { $was_set == 0 } assert { $was_set == 0 }
} }
test {test module clientinfo api} {
# Test basic sanity and SSL flag
set info [r test.clientinfo]
set ssl_flag [expr $::tls ? {"ssl:"} : {":"}]
assert { [dict get $info db] == 9 }
assert { [dict get $info flags] == "${ssl_flag}::::" }
# Test MULTI flag
r multi
r test.clientinfo
set info [lindex [r exec] 0]
assert { [dict get $info flags] == "${ssl_flag}::::multi" }
# Test TRACKING flag
r client tracking on
set info [r test.clientinfo]
assert { [dict get $info flags] == "${ssl_flag}::tracking::" }
}
} }

View File

@ -16,6 +16,11 @@ start_server {tags {"modules"}} {
r hmset hh f1 v1 f2 v2 r hmset hh f1 v1 f2 v2
lsort [r scan.scan_key hh] lsort [r scan.scan_key hh]
} {{f1 v1} {f2 v2}} } {{f1 v1} {f2 v2}}
test {Module scan hash dict with int value} {
r hmset hh1 f1 1
lsort [r scan.scan_key hh1]
} {{f1 1}}
test {Module scan hash dict} { test {Module scan hash dict} {
r config set hash-max-ziplist-entries 2 r config set hash-max-ziplist-entries 2

View File

@ -12,7 +12,7 @@ tags "modules" {
test {modules global are lost without aux} { test {modules global are lost without aux} {
set server_path [tmpdir "server.module-testrdb"] set server_path [tmpdir "server.module-testrdb"]
start_server [list overrides [list loadmodule "$testmodule" "dir" $server_path]] { start_server [list overrides [list loadmodule "$testmodule" "dir" $server_path] keep_persistence true] {
r testrdb.set.before global1 r testrdb.set.before global1
assert_equal "global1" [r testrdb.get.before] assert_equal "global1" [r testrdb.get.before]
} }
@ -23,7 +23,7 @@ tags "modules" {
test {modules are able to persist globals before and after} { test {modules are able to persist globals before and after} {
set server_path [tmpdir "server.module-testrdb"] set server_path [tmpdir "server.module-testrdb"]
start_server [list overrides [list loadmodule "$testmodule 2" "dir" $server_path]] { start_server [list overrides [list loadmodule "$testmodule 2" "dir" $server_path] keep_persistence true] {
r testrdb.set.before global1 r testrdb.set.before global1
r testrdb.set.after global2 r testrdb.set.after global2
assert_equal "global1" [r testrdb.get.before] assert_equal "global1" [r testrdb.get.before]
@ -38,7 +38,7 @@ tags "modules" {
test {modules are able to persist globals just after} { test {modules are able to persist globals just after} {
set server_path [tmpdir "server.module-testrdb"] set server_path [tmpdir "server.module-testrdb"]
start_server [list overrides [list loadmodule "$testmodule 1" "dir" $server_path]] { start_server [list overrides [list loadmodule "$testmodule 1" "dir" $server_path] keep_persistence true] {
r testrdb.set.after global2 r testrdb.set.after global2
assert_equal "global2" [r testrdb.get.after] assert_equal "global2" [r testrdb.get.after]
} }
@ -67,6 +67,7 @@ tags "modules" {
} }
# Start the replication process... # Start the replication process...
set loglines [count_log_lines -1]
$master config set repl-diskless-sync-delay 0 $master config set repl-diskless-sync-delay 0
$replica replicaof $master_host $master_port $replica replicaof $master_host $master_port
@ -76,29 +77,30 @@ tags "modules" {
for {set i 0} {$i < $attempts} {incr i} { for {set i 0} {$i < $attempts} {incr i} {
# wait for the replica to start reading the rdb # wait for the replica to start reading the rdb
# using the log file since the replica only responds to INFO once in 2mb # using the log file since the replica only responds to INFO once in 2mb
wait_for_log_message -1 "*Loading DB in memory*" 5 2000 1 set res [wait_for_log_messages -1 {"*Loading DB in memory*"} $loglines 2000 1]
set loglines [lindex $res 1]
# add some additional random sleep so that we kill the master on a different place each time # add some additional random sleep so that we kill the master on a different place each time
after [expr {int(rand()*100)}] after [expr {int(rand()*50)}]
# kill the replica connection on the master # kill the replica connection on the master
set killed [$master client kill type replica] set killed [$master client kill type replica]
if {[catch { set res [wait_for_log_messages -1 {"*Internal error in RDB*" "*Finished with success*" "*Successful partial resynchronization*"} $loglines 1000 1]
set res [wait_for_log_message -1 "*Internal error in RDB*" 5 100 10] if {$::verbose} { puts $res }
if {$::verbose} { set log_text [lindex $res 0]
puts $res set loglines [lindex $res 1]
} if {![string match "*Internal error in RDB*" $log_text]} {
}]} {
puts "failed triggering short read"
# force the replica to try another full sync # force the replica to try another full sync
$master multi
$master client kill type replica $master client kill type replica
$master set asdf asdf $master set asdf asdf
# the side effect of resizing the backlog is that it is flushed (16k is the min size) # the side effect of resizing the backlog is that it is flushed (16k is the min size)
$master config set repl-backlog-size [expr {16384 + $i}] $master config set repl-backlog-size [expr {16384 + $i}]
$master exec
} }
# wait for loading to stop (fail) # wait for loading to stop (fail)
wait_for_condition 100 10 { wait_for_condition 1000 1 {
[s -1 loading] eq 0 [s -1 loading] eq 0
} else { } else {
fail "Replica didn't disconnect" fail "Replica didn't disconnect"

View File

@ -325,74 +325,183 @@ start_server {tags {"multi"}} {
# check that if MULTI arrives during timeout, it is either refused, or # check that if MULTI arrives during timeout, it is either refused, or
# allowed to pass, and we don't end up executing half of the transaction # allowed to pass, and we don't end up executing half of the transaction
set rd1 [redis_deferring_client] set rd1 [redis_deferring_client]
set rd2 [redis_deferring_client] set r2 [redis_client]
r config set lua-time-limit 10 r config set lua-time-limit 10
r set xx 1 r set xx 1
$rd1 eval {while true do end} 0 $rd1 eval {while true do end} 0
after 200 after 200
catch { $rd2 multi; $rd2 read } e catch { $r2 multi; } e
catch { $rd2 incr xx; $rd2 read } e catch { $r2 incr xx; } e
r script kill r script kill
after 200 ; # Give some time to Lua to call the hook again... after 200 ; # Give some time to Lua to call the hook again...
catch { $rd2 incr xx; $rd2 read } e catch { $r2 incr xx; } e
catch { $rd2 exec; $rd2 read } e catch { $r2 exec; } e
assert_match {EXECABORT*previous errors*} $e
set xx [r get xx] set xx [r get xx]
# make sure that either the whole transcation passed or none of it (we actually expect none) # make sure that either the whole transcation passed or none of it (we actually expect none)
assert { $xx == 1 || $xx == 3} assert { $xx == 1 || $xx == 3}
# check that the connection is no longer in multi state # check that the connection is no longer in multi state
$rd2 ping asdf set pong [$r2 ping asdf]
set pong [$rd2 read]
assert_equal $pong "asdf" assert_equal $pong "asdf"
$rd1 close; $r2 close
} }
test {EXEC and script timeout} { test {EXEC and script timeout} {
# check that if EXEC arrives during timeout, we don't end up executing # check that if EXEC arrives during timeout, we don't end up executing
# half of the transaction, and also that we exit the multi state # half of the transaction, and also that we exit the multi state
set rd1 [redis_deferring_client] set rd1 [redis_deferring_client]
set rd2 [redis_deferring_client] set r2 [redis_client]
r config set lua-time-limit 10 r config set lua-time-limit 10
r set xx 1 r set xx 1
catch { $rd2 multi; $rd2 read } e catch { $r2 multi; } e
catch { $rd2 incr xx; $rd2 read } e catch { $r2 incr xx; } e
$rd1 eval {while true do end} 0 $rd1 eval {while true do end} 0
after 200 after 200
catch { $rd2 incr xx; $rd2 read } e catch { $r2 incr xx; } e
catch { $rd2 exec; $rd2 read } e catch { $r2 exec; } e
assert_match {EXECABORT*BUSY*} $e
r script kill r script kill
after 200 ; # Give some time to Lua to call the hook again... after 200 ; # Give some time to Lua to call the hook again...
set xx [r get xx] set xx [r get xx]
# make sure that either the whole transcation passed or none of it (we actually expect none) # make sure that either the whole transcation passed or none of it (we actually expect none)
assert { $xx == 1 || $xx == 3} assert { $xx == 1 || $xx == 3}
# Discard the transaction since EXEC likely got -BUSY error
# so the client is still in MULTI state.
catch { $rd2 discard ;$rd2 read } e
# check that the connection is no longer in multi state # check that the connection is no longer in multi state
$rd2 ping asdf set pong [$r2 ping asdf]
set pong [$rd2 read]
assert_equal $pong "asdf" assert_equal $pong "asdf"
$rd1 close; $r2 close
} }
test {MULTI-EXEC body and script timeout} { test {MULTI-EXEC body and script timeout} {
# check that we don't run an imcomplete transaction due to some commands # check that we don't run an imcomplete transaction due to some commands
# arriving during busy script # arriving during busy script
set rd1 [redis_deferring_client] set rd1 [redis_deferring_client]
set rd2 [redis_deferring_client] set r2 [redis_client]
r config set lua-time-limit 10 r config set lua-time-limit 10
r set xx 1 r set xx 1
catch { $rd2 multi; $rd2 read } e catch { $r2 multi; } e
catch { $rd2 incr xx; $rd2 read } e catch { $r2 incr xx; } e
$rd1 eval {while true do end} 0 $rd1 eval {while true do end} 0
after 200 after 200
catch { $rd2 incr xx; $rd2 read } e catch { $r2 incr xx; } e
r script kill r script kill
after 200 ; # Give some time to Lua to call the hook again... after 200 ; # Give some time to Lua to call the hook again...
catch { $rd2 exec; $rd2 read } e catch { $r2 exec; } e
assert_match {EXECABORT*previous errors*} $e
set xx [r get xx] set xx [r get xx]
# make sure that either the whole transcation passed or none of it (we actually expect none) # make sure that either the whole transcation passed or none of it (we actually expect none)
assert { $xx == 1 || $xx == 3} assert { $xx == 1 || $xx == 3}
# check that the connection is no longer in multi state # check that the connection is no longer in multi state
$rd2 ping asdf set pong [$r2 ping asdf]
set pong [$rd2 read]
assert_equal $pong "asdf" assert_equal $pong "asdf"
$rd1 close; $r2 close
}
test {just EXEC and script timeout} {
# check that if EXEC arrives during timeout, we don't end up executing
# actual commands during busy script, and also that we exit the multi state
set rd1 [redis_deferring_client]
set r2 [redis_client]
r config set lua-time-limit 10
r set xx 1
catch { $r2 multi; } e
catch { $r2 incr xx; } e
$rd1 eval {while true do end} 0
after 200
catch { $r2 exec; } e
assert_match {EXECABORT*BUSY*} $e
r script kill
after 200 ; # Give some time to Lua to call the hook again...
set xx [r get xx]
# make we didn't execute the transaction
assert { $xx == 1}
# check that the connection is no longer in multi state
set pong [$r2 ping asdf]
assert_equal $pong "asdf"
$rd1 close; $r2 close
}
test {exec with write commands and state change} {
# check that exec that contains write commands fails if server state changed since they were queued
set r1 [redis_client]
r set xx 1
r multi
r incr xx
$r1 config set min-replicas-to-write 2
catch {r exec} e
assert_match {*EXECABORT*NOREPLICAS*} $e
set xx [r get xx]
# make sure that the INCR wasn't executed
assert { $xx == 1}
$r1 config set min-replicas-to-write 0
$r1 close;
}
test {exec with read commands and stale replica state change} {
# check that exec that contains read commands fails if server state changed since they were queued
r config set replica-serve-stale-data no
set r1 [redis_client]
r set xx 1
# check that GET is disallowed on stale replica, even if the replica becomes stale only after queuing.
r multi
r get xx
$r1 replicaof localhsot 0
catch {r exec} e
assert_match {*EXECABORT*MASTERDOWN*} $e
# check that PING is allowed
r multi
r ping
$r1 replicaof localhsot 0
set pong [r exec]
assert {$pong == "PONG"}
# check that when replica is not stale, GET is allowed
# while we're at it, let's check that multi is allowed on stale replica too
r multi
$r1 replicaof no one
r get xx
set xx [r exec]
# make sure that the INCR was executed
assert { $xx == 1 }
$r1 close;
}
test {EXEC with only read commands should not be rejected when OOM} {
set r2 [redis_client]
r set x value
r multi
r get x
r ping
# enforcing OOM
$r2 config set maxmemory 1
# finish the multi transaction with exec
assert { [r exec] == {value PONG} }
# releasing OOM
$r2 config set maxmemory 0
$r2 close
}
test {EXEC with at least one use-memory command should fail} {
set r2 [redis_client]
r multi
r set x 1
r get x
# enforcing OOM
$r2 config set maxmemory 1
# finish the multi transaction with exec
catch {r exec} e
assert_match {EXECABORT*OOM*} $e
# releasing OOM
$r2 config set maxmemory 0
$r2 close
} }
} }

View File

@ -0,0 +1,72 @@
set system_name [string tolower [exec uname -s]]
set user_id [exec id -u]
if {$system_name eq {linux}} {
start_server {tags {"oom-score-adj"}} {
proc get_oom_score_adj {{pid ""}} {
if {$pid == ""} {
set pid [srv 0 pid]
}
set fd [open "/proc/$pid/oom_score_adj" "r"]
set val [gets $fd]
close $fd
return $val
}
test {CONFIG SET oom-score-adj works as expected} {
set base [get_oom_score_adj]
# Enable oom-score-adj, check defaults
r config set oom-score-adj-values "10 20 30"
r config set oom-score-adj yes
assert_equal [get_oom_score_adj] [expr $base + 10] case1
# Modify current class
r config set oom-score-adj-values "15 20 30"
assert_equal [get_oom_score_adj] [expr $base + 15] case2
# Check replica class
r replicaof localhost 1
assert_equal [get_oom_score_adj] [expr $base + 20] case3
r replicaof no one
assert_equal [get_oom_score_adj] [expr $base + 15] case4
# Check child process
r set key-a value-a
r config set rdb-key-save-delay 1000000
r bgsave
set child_pid [get_child_pid 0]
assert {[get_oom_score_adj $child_pid] == [expr $base + 30]}
}
# Failed oom-score-adj tests can only run unprivileged
if {$user_id != 0} {
test {CONFIG SET oom-score-adj handles configuration failures} {
# Bad config
r config set oom-score-adj no
r config set oom-score-adj-values "-1000 -1000 -1000"
# Make sure it fails
catch {r config set oom-score-adj yes} e
assert_match {*Failed to set*} $e
# Make sure it remains off
assert {[r config get oom-score-adj] == "oom-score-adj no"}
# Fix config
r config set oom-score-adj-values "0 100 100"
r config set oom-score-adj yes
# Make sure it fails
catch {r config set oom-score-adj-values "-1000 -1000 -1000"} e
assert_match {*Failed*} $e
# Make sure previous values remain
assert {[r config get oom-score-adj-values] == {oom-score-adj-values {0 100 100}}}
}
}
}
}

View File

@ -21,7 +21,19 @@ start_server {tags {"tls"}} {
catch {$s PING} e catch {$s PING} e
assert_match {PONG} $e assert_match {PONG} $e
r CONFIG SET tls-auth-clients optional
set s [redis [srv 0 host] [srv 0 port]]
::tls::import [$s channel]
catch {$s PING} e
assert_match {PONG} $e
r CONFIG SET tls-auth-clients yes r CONFIG SET tls-auth-clients yes
set s [redis [srv 0 host] [srv 0 port]]
::tls::import [$s channel]
catch {$s PING} e
assert_match {*error*} $e
} }
test {TLS: Verify tls-protocols behaves as expected} { test {TLS: Verify tls-protocols behaves as expected} {

View File

@ -130,15 +130,18 @@ start_server {tags {"incr"}} {
format $err format $err
} {WRONGTYPE*} } {WRONGTYPE*}
test {INCRBYFLOAT does not allow NaN or Infinity} { # On some platforms strtold("+inf") with valgrind returns a non-inf result
r set foo 0 if {!$::valgrind} {
set err {} test {INCRBYFLOAT does not allow NaN or Infinity} {
catch {r incrbyfloat foo +inf} err r set foo 0
set err set err {}
# p.s. no way I can force NaN to test it from the API because catch {r incrbyfloat foo +inf} err
# there is no way to increment / decrement by infinity nor to set err
# perform divisions. # p.s. no way I can force NaN to test it from the API because
} {ERR*would produce*} # there is no way to increment / decrement by infinity nor to
# perform divisions.
} {ERR*would produce*}
}
test {INCRBYFLOAT decrement} { test {INCRBYFLOAT decrement} {
r set foo 1 r set foo 1

View File

@ -9,6 +9,56 @@ start_server {
} { } {
source "tests/unit/type/list-common.tcl" source "tests/unit/type/list-common.tcl"
test {LPOS basic usage} {
r DEL mylist
r RPUSH mylist a b c 1 2 3 c c
assert {[r LPOS mylist a] == 0}
assert {[r LPOS mylist c] == 2}
}
test {LPOS RANK (positive and negative rank) option} {
assert {[r LPOS mylist c RANK 1] == 2}
assert {[r LPOS mylist c RANK 2] == 6}
assert {[r LPOS mylist c RANK 4] eq ""}
assert {[r LPOS mylist c RANK -1] == 7}
assert {[r LPOS mylist c RANK -2] == 6}
}
test {LPOS COUNT option} {
assert {[r LPOS mylist c COUNT 0] == {2 6 7}}
assert {[r LPOS mylist c COUNT 1] == {2}}
assert {[r LPOS mylist c COUNT 2] == {2 6}}
assert {[r LPOS mylist c COUNT 100] == {2 6 7}}
}
test {LPOS COUNT + RANK option} {
assert {[r LPOS mylist c COUNT 0 RANK 2] == {6 7}}
assert {[r LPOS mylist c COUNT 2 RANK -1] == {7 6}}
}
test {LPOS non existing key} {
assert {[r LPOS mylistxxx c COUNT 0 RANK 2] eq {}}
}
test {LPOS no match} {
assert {[r LPOS mylist x COUNT 2 RANK -1] eq {}}
assert {[r LPOS mylist x RANK -1] eq {}}
}
test {LPOS MAXLEN} {
assert {[r LPOS mylist a COUNT 0 MAXLEN 1] == {0}}
assert {[r LPOS mylist c COUNT 0 MAXLEN 1] == {}}
assert {[r LPOS mylist c COUNT 0 MAXLEN 3] == {2}}
assert {[r LPOS mylist c COUNT 0 MAXLEN 3 RANK -1] == {7 6}}
assert {[r LPOS mylist c COUNT 0 MAXLEN 7 RANK 2] == {6}}
}
test {LPOS when RANK is greater than matches} {
r DEL mylist
r LPUSH l a
assert {[r LPOS mylist b COUNT 10 RANK 5] eq {}}
}
test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - ziplist} { test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - ziplist} {
# first lpush then rpush # first lpush then rpush
assert_equal 1 [r lpush myziplist1 aa] assert_equal 1 [r lpush myziplist1 aa]

View File

@ -469,3 +469,10 @@ start_server {tags {"stream"} overrides {appendonly yes aof-use-rdb-preamble no}
assert {[dict get [r xinfo stream mystream] last-generated-id] == "2-2"} assert {[dict get [r xinfo stream mystream] last-generated-id] == "2-2"}
} }
} }
start_server {tags {"stream"}} {
test {XGROUP HELP should not have unexpected options} {
catch {r XGROUP help xxx} e
assert_match "*Unknown subcommand or wrong number of arguments*" $e
}
}

View File

@ -33,7 +33,7 @@ start_server {} {
} }
test {WAIT should not acknowledge 1 additional copy if slave is blocked} { test {WAIT should not acknowledge 1 additional copy if slave is blocked} {
set cmd [rediscli $slave_port "-h $slave_host debug sleep 5"] set cmd [rediscli $slave_host $slave_port "debug sleep 5"]
exec {*}$cmd > /dev/null 2> /dev/null & exec {*}$cmd > /dev/null 2> /dev/null &
after 1000 ;# Give keydb-cli the time to execute the command. after 1000 ;# Give keydb-cli the time to execute the command.
$master set foo 0 $master set foo 0

View File

@ -38,7 +38,11 @@ then
PORT=$((PORT+1)) PORT=$((PORT+1))
HOSTS="$HOSTS $CLUSTER_HOST:$PORT" HOSTS="$HOSTS $CLUSTER_HOST:$PORT"
done done
$BIN_PATH/keydb-cli --cluster create $HOSTS --cluster-replicas $REPLICAS OPT_ARG=""
if [ "$2" == "-f" ]; then
OPT_ARG="--cluster-yes"
fi
$BIN_PATH/keydb-cli --cluster create $HOSTS --cluster-replicas $REPLICAS $OPT_ARG
exit 0 exit 0
fi fi
@ -104,7 +108,7 @@ fi
echo "Usage: $0 [start|create|stop|watch|tail|clean|call]" echo "Usage: $0 [start|create|stop|watch|tail|clean|call]"
echo "start -- Launch Redis Cluster instances." echo "start -- Launch Redis Cluster instances."
echo "create -- Create a cluster using keydb-cli --cluster create." echo "create [-f] -- Create a cluster using keydb-cli --cluster create."
echo "stop -- Stop Redis Cluster instances." echo "stop -- Stop Redis Cluster instances."
echo "watch -- Show CLUSTER NODES output (first 30 lines) of first node." echo "watch -- Show CLUSTER NODES output (first 30 lines) of first node."
echo "tail <id> -- Run tail -f of instance at base port + ID." echo "tail <id> -- Run tail -f of instance at base port + ID."

View File

@ -54,7 +54,7 @@ def commands
require "json" require "json"
require "uri" require "uri"
url = URI.parse "https://raw.githubusercontent.com/antirez/keydb-doc/master/commands.json" url = URI.parse "https://raw.githubusercontent.com/redis/redis-doc/master/commands.json"
client = Net::HTTP.new url.host, url.port client = Net::HTTP.new url.host, url.port
client.use_ssl = true client.use_ssl = true
response = client.get url.path response = client.get url.path

View File

@ -1,15 +0,0 @@
#!/bin/sh
if [ $# != "1" ]
then
echo "Usage: ./mkrelease.sh <git-ref>"
exit 1
fi
TAG=$1
TARNAME="keydb-${TAG}.tar"
echo "Generating /tmp/${TARNAME}"
cd ~/hack/redis
git archive $TAG --prefix keydb-${TAG}/ > /tmp/$TARNAME || exit 1
echo "Gizipping the archive"
rm -f /tmp/$TARNAME.gz
gzip -9 /tmp/$TARNAME

View File

@ -1,6 +0,0 @@
#!/bin/bash
echo "Uploading..."
scp /tmp/keydb-${1}.tar.gz antirez@antirez.com:/var/virtual/download.redis.io/httpdocs/releases/
echo "Updating web site... (press any key if it is a stable release, or Ctrl+C)"
read x
ssh antirez@antirez.com "cd /var/virtual/download.redis.io/httpdocs; ./update.sh ${1}"

View File

@ -1,26 +0,0 @@
#!/bin/sh
if [ $# != "1" ]
then
echo "Usage: ${0} <git-ref>"
exit 1
fi
TAG=$1
TARNAME="keydb-${TAG}.tar.gz"
DOWNLOADURL="http://download.redis.io/releases/${TARNAME}"
ssh antirez@metal "export TERM=xterm;
cd /tmp;
rm -rf test_release_tmp_dir;
cd test_release_tmp_dir;
rm -f $TARNAME;
rm -rf keydb-${TAG};
wget $DOWNLOADURL;
tar xvzf $TARNAME;
cd keydb-${TAG};
make;
./runtest;
./runtest-sentinel;
if [ -x runtest-cluster ]; then
./runtest-cluster;
fi"

View File

@ -1,8 +0,0 @@
#!/bin/bash
SHA=$(curl -s http://download.redis.io/releases/keydb-${1}.tar.gz | shasum -a 256 | cut -f 1 -d' ')
ENTRY="hash keydb-${1}.tar.gz sha256 $SHA http://download.redis.io/releases/keydb-${1}.tar.gz"
echo $ENTRY >> ~/hack/keydb-hashes/README
vi ~/hack/keydb-hashes/README
echo "Press any key to commit, Ctrl-C to abort)."
read yes
(cd ~/hack/keydb-hashes; git commit -a -m "${1} hash."; git push)

View File

@ -1,35 +0,0 @@
#!/usr/bin/env tclsh
if {[llength $::argv] != 2 && [llength $::argv] != 3} {
puts "Usage: $::argv0 <branch> <version> \[<num-commits>\]"
exit 1
}
set branch [lindex $::argv 0]
set ver [lindex $::argv 1]
if {[llength $::argv] == 3} {
set count [lindex ::$argv 2]
} else {
set count 100
}
set template {
================================================================================
Redis %ver% Released %date%
================================================================================
Upgrade urgency <URGENCY>: <DESCRIPTION>
}
set template [string trim $template]
append template "\n\n"
set date [clock format [clock seconds]]
set template [string map [list %ver% $ver %date% $date] $template]
append template [exec git log $branch~$count..$branch "--format=format:%an in commit %h:%n %s" --shortstat]
#Older, more verbose version.
#
#append template [exec git log $branch~30..$branch "--format=format:+-------------------------------------------------------------------------------%n| %s%n| By %an, %ai%n+--------------------------------------------------------------------------------%nhttps://github.com/antirez/redis/commit/%H%n%n%b" --stat]
puts $template

View File

@ -20,6 +20,8 @@ Description=Redis data structure server
Documentation=https://redis.io/documentation Documentation=https://redis.io/documentation
#Before=your_application.service another_example_application.service #Before=your_application.service another_example_application.service
#AssertPathExists=/var/lib/redis #AssertPathExists=/var/lib/redis
Wants=network-online.target
After=network-online.target
[Service] [Service]
ExecStart=/usr/local/bin/redis-server --supervised systemd --daemonize no ExecStart=/usr/local/bin/redis-server --supervised systemd --daemonize no