Merge tag '6.2.2' into unstable

Former-commit-id: 93ebb31b17adec5d406d2e30a5b9ea71c07fce5c
This commit is contained in:
John Sully 2021-05-21 05:54:39 +00:00
commit ea6a0f214b
121 changed files with 4083 additions and 1304 deletions

308
.github/workflows/daily.yml vendored Normal file
View File

@ -0,0 +1,308 @@
name: Daily
on:
pull_request:
branches:
# any PR to a release branch.
- '[0-9].[0-9]'
schedule:
- cron: '0 0 * * *'
jobs:
test-ubuntu-jemalloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST'
- name: test
run: |
sudo apt-get install tcl8.6
./runtest --accurate --verbose --dump-logs
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
- name: unittest
run: ./src/redis-server test all
test-ubuntu-libc-malloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: make MALLOC=libc
- name: test
run: |
sudo apt-get install tcl8.6
./runtest --accurate --verbose --dump-logs
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-ubuntu-no-malloc-usable-size:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: make MALLOC=libc CFLAGS=-DNO_MALLOC_USABLE_SIZE
- name: test
run: |
sudo apt-get install tcl8.6
./runtest --accurate --verbose --dump-logs
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-ubuntu-32bit:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
sudo apt-get update && sudo apt-get install libc6-dev-i386
make 32bit REDIS_CFLAGS='-Werror -DREDIS_TEST'
- name: test
run: |
sudo apt-get install tcl8.6
./runtest --accurate --verbose --dump-logs
- name: module api test
run: |
make -C tests/modules 32bit # the script below doesn't have an argument, we must build manually ahead of time
./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
- name: unittest
run: ./src/redis-server test all
test-ubuntu-tls:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
make BUILD_TLS=yes
- name: test
run: |
sudo apt-get install tcl8.6 tcl-tls
./utils/gen-test-certs.sh
./runtest --accurate --verbose --tls --dump-logs
./runtest --accurate --verbose --dump-logs
- 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-ubuntu-io-threads:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
make
- name: test
run: |
sudo apt-get install tcl8.6 tcl-tls
./runtest --config io-threads 4 --config io-threads-do-reads yes --accurate --verbose --tags network --dump-logs
- name: cluster tests
run: |
./runtest-cluster --config io-threads 4 --config io-threads-do-reads yes
test-valgrind:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: make valgrind REDIS_CFLAGS='-Werror -DREDIS_TEST'
- name: test
run: |
sudo apt-get update
sudo apt-get install tcl8.6 valgrind -y
./runtest --valgrind --verbose --clients 1 --dump-logs
- name: module api test
run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1
- name: unittest
run: |
valgrind --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.txt ./src/redis-server test all
if grep -q 0x err.txt; then cat err.txt; exit 1; fi
test-valgrind-no-malloc-usable-size:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: make valgrind CFLAGS="-DNO_MALLOC_USABLE_SIZE"
- name: test
run: |
sudo apt-get update
sudo apt-get install tcl8.6 valgrind -y
./runtest --valgrind --verbose --clients 1 --dump-logs
- name: module api test
run: ./runtest-moduleapi --valgrind --no-latency --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 gcc make
make
- name: test
run: |
yum -y install which tcl
./runtest --accurate --verbose --dump-logs
- 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 --dump-logs
./runtest --accurate --verbose --dump-logs
- 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 --no-latency --dump-logs
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-freebsd:
runs-on: macos-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: test
uses: vmactions/freebsd-vm@v0.1.2
with:
usesh: true
sync: rsync
prepare: pkg install -y bash gmake lang/tcl86
run: >
gmake &&
./runtest --accurate --verbose --no-latency --dump-logs &&
MAKE=gmake ./runtest-moduleapi --verbose &&
./runtest-sentinel &&
./runtest-cluster
test-alpine-jemalloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
container: alpine:latest
steps:
- uses: actions/checkout@v2
- name: make
run: |
apk add build-base
make REDIS_CFLAGS='-Werror'
- name: test
run: |
apk add tcl procps
./runtest --accurate --verbose --dump-logs
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-alpine-libc-malloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
container: alpine:latest
steps:
- uses: actions/checkout@v2
- name: make
run: |
apk add build-base
make REDIS_CFLAGS='-Werror' USE_JEMALLOC=no CFLAGS=-DUSE_MALLOC_USABLE_SIZE
- name: test
run: |
apk add tcl procps
./runtest --accurate --verbose --dump-logs
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster

532
00-RELEASENOTES Normal file
View File

@ -0,0 +1,532 @@
Redis 6.2 release notes
=======================
--------------------------------------------------------------------------------
Upgrade urgency levels:
LOW: No need to upgrade unless there are new features you want to use.
MODERATE: Program an upgrade of the server, but it's not urgent.
HIGH: There is a critical bug that may affect a subset of users. Upgrade!
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
SECURITY: There are security fixes in the release.
--------------------------------------------------------------------------------
================================================================================
Redis 6.2.2 Released Mon April 19 19:00:00 IST 2021
================================================================================
Upgrade urgency: HIGH, if you're using ACL and pub/sub, CONFIG REWRITE, or
suffering from performance regression. see below.
Bug fixes for regressions in previous releases of Redis 6.2:
* Fix BGSAVE, AOFRW, and replication slowdown due to child reporting CoW (#8645)
* Fix short busy loop when timer event is about to fire (#8764)
* Fix default user, overwritten and reset users losing pubsub channel permissions (#8723)
* Fix config rewrite with an empty `save` config resulsing in default `save` values (#8719)
* Fix not starting on alpine/libmusl without IPv6 (#8655)
* Fix issues with propagation and MULTI/EXEC in modules (#8617)
Several issues around nested calls and thread safe contexts
Bug fixes that are only applicable to previous releases of Redis 6.2:
* ACL Pub/Sub channels permission handling for save/load scenario (#8794)
* Fix early rejection of PUBLISH inside MULTI-EXEC transaction (#8534)
* Fix missing SLOWLOG records for blocked commands (#8632)
* Allow RESET command during busy scripts (#8629)
* Fix some error replies were not counted on stats (#8659)
Bug fixes:
* Add a timeout mechanism for replicas stuck in fullsync (#8762)
* Process HELLO command even if the default user has no permissions (#8633)
* Client issuing a long running script and using a pipeline, got disconnected (#8715)
* Fix script kill to work also on scripts that use `pcall` (#8661)
* Fix list-compress-depth may compress more node than required (#8311)
* Fix redis-cli handling of rediss:// URL scheme (#8705)
* Cluster: Skip unnecessary check which may prevent failure detection (#8585)
* Cluster: Fix hang manual failover when replica just started (#8651)
* Sentinel: Fix info-refresh time field before sentinel get first response (#8567)
* Sentinel: Fix possible crash on failed connection attempt (#8627)
* Systemd: Send the readiness notification when a replica is ready to accept connections (#8409)
Command behavior changes:
* ZADD: fix wrong reply when INCR used with GT/LT which blocked the update (#8717)
It was responding with the incremented value rather than nil
* XAUTOCLAIM: fix response to return the next available id as the cursor (#8725)
Previous behavior was retuning the last one which was already scanned
* XAUTOCLAIM: fix JUSTID to prevent incrementing delivery_count (#8724)
New config options:
* Add cluster-allow-replica-migration config option (#5285)
* Add replica-announced config option (#8653)
* Add support for plaintext clients in TLS cluster (#8587)
* Add support for reading encrypted keyfiles (#8644)
Improvements:
* Fix performance regression in BRPOP on Redis 6.0 (#8689)
* Avoid adding slowlog entries for config with sensitive data (#8584)
* Improve redis-cli non-binary safe string handling (#8566)
* Optimize CLUSTER SLOTS reply (#8541)
* Handle remaining fsync errors (#8419)
Info fields and introspection changes:
* Strip % sign from current_fork_perc info field (#8628)
* Fix RSS memory info on FreeBSD (#8620)
* Fix client_recent_max_input/output_buffer in 'INFO CLIENTS' when all clients drop (#8588)
* Fix invalid master_link_down_since_seconds in info replication (#8785)
Platform and deployment-related changes:
* Fix FreeBSD <12.x builds (#8603)
Modules:
* Add macros for RedisModule_log logging levels (#4246)
* Add RedisModule_GetAbsExpire / RedisModule_SetAbsExpire (#8564)
* Add a module type for key space notification (#8759)
* Set module eviction context flag only in masters (#8631)
* Fix unusable RedisModule_IsAOFClient API (#8596)
* Fix missing EXEC on modules propagation after failed EVAL execution (#8654)
* Fix edge-case when a module client is unblocked (#8618)
================================================================================
Redis 6.2.1 Released Mon Mar 1 17:51:36 IST 2021
================================================================================
Upgrade urgency: LOW.
Here is a comprehensive list of changes in this release compared to 6.2.0,
each one includes the PR number that added it, so you can get more details
at https://github.com/redis/redis/pull/<number>
Bug fixes:
* Fix sanitize-dump-payload for stream with deleted records (#8568)
* Prevent client-query-buffer-limit config from being set to lower than 1mb (#8557)
Improvements:
* Make port, tls-port and bind config options modifiable at runtime (#8510)
Platform and deployment-related changes:
* Fix compilation error on non-glibc systems if jemalloc is not used (#8533)
* Improved memory consumption and memory usage tracking on FreeBSD (#8545)
* Fix compilation on ARM64 MacOS with jemalloc (#8458)
Modules:
* New Module API for getting user name of a client (#8508)
* Optimize RM_Call by utilizing a shared reusable client (#8516)
* Fix crash running CLIENT INFO via RM_Call (#8560)
================================================================================
Redis 6.2.0 GA Released Tue Feb 22 14:00:00 IST 2021
================================================================================
Upgrade urgency: SECURITY if you use 32bit build of redis (see bellow), MODERATE
if you used earlier versions of Redis 6.2, LOW otherwise.
Integer overflow on 32-bit systems (CVE-2021-21309):
Redis 4.0 or newer uses a configurable limit for the maximum supported bulk
input size. By default, it is 512MB which is a safe value for all platforms.
If the limit is significantly increased, receiving a large request from a client
may trigger several integer overflow scenarios, which would result with buffer
overflow and heap corruption.
Here is a comprehensive list of changes in this release compared to 6.2 RC3,
each one includes the PR number that added it, so you can get more details
at https://github.com/redis/redis/pull/<number>
Bug fixes:
* Avoid 32-bit overflows when proto-max-bulk-len is set high (#8522)
* Fix broken protocol in client tracking tracking-redir-broken message (#8456)
* Avoid unsafe field name characters in INFO commandstats, errorstats, modules (#8492)
* XINFO able to access expired keys during CLIENT PAUSE WRITE (#8436)
* Fix allowed length for REPLCONF ip-address, needed due to Sentinel's support for hostnames (#8517)
* Fix broken protocol in redis-benchmark when used with -a or --dbnum (#8486)
* XADD counts deleted records too when considering switching to a new listpack (#8390)
Bug fixes that are only applicable to previous releases of Redis 6.2:
* Fixes in GEOSEARCH bybox (accuracy and mismatch between width and height) (#8445)
* Fix risk of OOM panic in HRANDFIELD, ZRANDMEMBER commands with huge negative count (#8429)
* Fix duplicate replicas issue in Sentinel, needed due to hostname support (#8481)
* Fix Sentinel configuration rewrite, an improvement of #8271 (#8480)
Command behavior changes:
* SRANDMEMBER uses RESP3 array type instead of set type (#8504)
* EXPIRE, EXPIREAT, SETEX, GETEX: Return error when provided expire time overflows (#8287)
Other behavior changes:
* Remove ACL subcommand validation if fully added command exists. (#8483)
Improvements:
* Optimize sorting in GEORADIUS / GEOSEARCH with COUNT (#8326)
* Optimize HRANDFIELD and ZRANDMEMBER case 4 when ziplist encoded (#8444)
* Optimize in-place replacement of elements in HSET, HINCRBY, LSET (#8493)
* Remove redundant list to store pubsub patterns (#8472)
* Add --insecure option to command line tools (#8416)
Info fields and introspection changes:
* Add INFO fields to track progress of BGSAVE, AOFRW, replication (#8414)
Modules:
* RM_ZsetRem: Delete key if empty, the bug could leave empty zset keys (#8453)
* RM_HashSet: Add COUNT_ALL flag and set errno (#8446)
================================================================================
Redis 6.2 RC3 Released Tue Feb 1 14:00:00 IST 2021
================================================================================
Upgrade urgency LOW: This is the third Release Candidate of Redis 6.2.
Here is a comprehensive list of changes in this release compared to 6.2 RC2,
each one includes the PR number that added it, so you can get more details
at https://github.com/redis/redis/pull/<number>
New commands / args:
* Add HRANDFIELD and ZRANDMEMBER commands (#8297)
* Add FAILOVER command (#8315)
* Add GETEX, GETDEL commands (#8327)
* Add PXAT/EXAT arguments to SET command (#8327)
* Add SYNC arg to FLUSHALL and FLUSHDB, and ASYNC/SYNC arg to SCRIPT FLUSH (#8258)
Sentinel:
* Add hostname support to Sentinel (#8282)
* Prevent file descriptors from leaking into Sentinel scripts (#8242)
* Fix config file line order dependency and config rewrite sequence (#8271)
New configuration options:
* Add set-proc-title config option to disable changes to the process title (#3623)
* Add proc-title-template option to control what's shown in the process title (#8397)
* Add lazyfree-lazy-user-flush config option to control FLUSHALL, FLUSHDB and SCRIPT FLUSH (#8258)
Bug fixes:
* AOF: recover from last write error by turning on/off appendonly config (#8030)
* Exit on fsync error when the AOF fsync policy is 'always' (#8347)
* Avoid assertions (on older kernels) when testing arm64 CoW bug (#8405)
* CONFIG REWRITE should honor umask settings (#8371)
* Fix firstkey,lastkey,step in COMMAND command for some commands (#8367)
Special considerations:
* Fix misleading description of the save configuration directive (#8337)
Improvements:
* A way to get RDB file via replication without excessive replication buffers (#8303)
* Optimize performance of clusterGenNodesDescription for large clusters (#8182)
Info fields and introspection changes:
* SLOWLOG and LATENCY monitor include unblocking time of blocked commands (#7491)
Modules:
* Add modules API for streams (#8288)
* Add event for fork child birth and termination (#8289)
* Add RM_BlockedClientMeasureTime* etc, to track background processing in commandstats (#7491)
* Fix bug in v6.2, wrong value passed to the new unlink callback (#8381)
* Fix bug in v6.2, modules blocked on keys unblock on commands like LPUSH (#8356)
================================================================================
Redis 6.2 RC2 Released Tue Jan 12 16:17:20 IST 2021
================================================================================
Upgrade urgency LOW: This is the second Release Candidate of Redis 6.2.
IMPORTANT: If you're running Redis on ARM64 or a big-endian system, upgrade may
have significant implications. Please be sure to read the notes below.
Here is a comprehensive list of changes in this release compared to 6.2 RC1,
each one includes the PR number that added it, so you can get more details
at https://github.com/redis/redis/pull/<number>
New commands / args:
* Add the REV, BYLEX and BYSCORE arguments to ZRANGE, and the ZRANGESTORE command (#7844)
* Add the XAUTOCLAIM command (#7973)
* Add the MINID trimming strategy and the LIMIT argument to XADD and XTRIM (#8169)
* Add the ANY argument to GEOSEARCH and GEORADIUS (#8259)
* Add the CH, NX, XX arguments to GEOADD (#8227)
* Add the COUNT argument to LPOP and RPOP (#8179)
* Add the WRITE argument to CLIENT PAUSE for pausing write commands exclusively (#8170)
* Change the proto-ver argument of HELLO to optional (#7377)
* Add the CLIENT TRACKINGINFO subcommand (#7309)
Command behavior changes:
* CLIENT TRACKING yields an error when given overlapping BCAST prefixes (#8176)
* SWAPDB invalidates WATCHed keys (#8239)
* SORT command behaves differently when used on a writable replica (#8283)
Other behavior changes:
* Avoid propagating MULTI/EXEC for read-only transactions (#8216)
* Remove the read-only flag from TIME, ECHO, ROLE, LASTSAVE (#8216)
* Fix the command flags of PFDEBUG (#8222)
* Tracking clients will no longer receive unnecessary key invalidation messages after FLUSHDB (#8039)
* Sentinel: Fix missing updates to the config file after SENTINEL SET command (#8229)
Bug fixes with compatibility implications (bugs introduced in Redis 6.0):
* Fix RDB CRC64 checksum on big-endian systems (#8270)
If you're using big-endian please consider the compatibility implications with
RESTORE, replication and persistence.
* Fix wrong order of key/value in Lua's map response (#8266)
If your scripts use redis.setresp() or return a map (new in Redis 6.0), please
consider the implications.
Bug fixes that are only applicable to previous releases of Redis 6.2:
* Resolve rare assertions in active defragmentation while loading (#8284, #8281)
Bug fixes:
* Fix the selection of a random element from large hash tables (#8133)
* Fix an issue where a forked process deletes the parent's pidfile (#8231)
* Fix crashes when enabling io-threads-do-reads (#8230)
* Fix a crash in redis-cli after executing cluster backup (#8267)
* Fix redis-benchmark to use an IP address for the first cluster node (#8154)
* Fix saving of strings larger than 2GB into RDB files (#8306)
Additional improvements:
* Improve replication handshake time (#8214)
* Release client tracking table memory asynchronously in cases where the DB is also freed asynchronously (#8039)
* Avoid wasteful transient memory allocation in certain cases (#8286, #5954)
* Handle binary string values by the 'requirepass' and 'masterauth' configs (#8200)
Platform and deployment-related changes:
* Install redis-check-rdb and redis-check-aof as symlinks to redis-server (#5745)
* Add a check for an ARM64 Linux kernel bug (#8224)
Due to the potential severity of this issue, Redis will refuse to run on
affected platforms by default.
Info fields and introspection changes:
* Add the errorstats section to the INFO command (#8217)
* Add the failed_calls and rejected_calls fields INFO's commandstats section (#8217)
* Report child copy-on-write metrics continuously (#8264)
Module API changes:
* Add the RedisModule_SendChildCOWInfo API (#8264)
* Add the may-replicate command flag (#8170)
================================================================================
Redis 6.2 RC1 Released Mon Dec 14 11:50:00 IST 2020
================================================================================
Upgrade urgency LOW: This is the first Release Candidate of Redis 6.2.
Introduction to the Redis 6.2 release
=====================================
This release is the first significant Redis release managed by the core team
under the new project governance model.
Redis 6.2 includes many new commands and improvements, but no big features. It
mainly makes Redis more complete and addresses issues that have been requested
by many users frequently or for a long time.
Many of these changes were not eligible for 6.0.x for several reasons:
1. They are not backward compatible, which is always the case with new or
extended commands (that cannot be replicated to an older replica).
2. They require a longer release-candidate test cycle.
Here is a comprehensive list of changes in this release compared to 6.0.9,
each one includes the PR number that added it, so you can get more details
at https://github.com/redis/redis/pull/<number>
New commands / args:
* Add SMISMEMBER command that checks multiple members (#7615)
* Add ZMSCORE command that returns an array of scores (#7593)
* Add LMOVE and BLMOVE commands that pop and push arbitrarily (#6929)
* Add RESET command that resets client connection state (#7982)
* Add COPY command that copies keys (#7953)
* Add ZDIFF and ZDIFFSTORE commands (#7961)
* Add ZINTER and ZUNION commands (#7794)
* Add GEOSEARCH/GEOSEARCHSTORE commands for bounding box spatial queries (#8094)
* Add GET parameter to SET command, for more powerful GETSET (#7852)
* Add exclusive range query to XPENDING (#8130)
* Add exclusive range query to X[REV]RANGE (#8072)
* Add GT and LT options to ZADD for conditional score updates (#7818)
* Add CLIENT INFO and CLIENT LIST for specific ids (#8113)
* Add IDLE argument to XPENDING command (#7972)
* Add local address to CLIENT LIST, and a CLIENT KILL filter. (#7913)
* Add NOMKSTREAM option to XADD command (#7910)
* Add command introspection to Sentinel (#7940)
* Add SENTINEL MYID subcommand (#7858)
New features:
* Dump payload sanitization: prevent corrupt payload causing crashes (#7807)
Has flags to enable full O(N) validation (disabled by default).
* ACL patterns for Pub/Sub channels (#7993)
* Support ACL for Sentinel mode (#7888)
* Support getting configuration from both stdin and file at the same time (#7893)
Lets you avoid storing secrets on the disk.
New features in CLI tools:
* redis-cli RESP3 push support (#7609)
* redis-cli cluster import support source and target that require auth (#7994)
* redis-cli URIs able to provide user name in addition to password (#8048)
* redis-cli/redis-benchmark allow specifying the prefered ciphers/ciphersuites (#8005)
* redis-cli add -e option to exit with code when command execution fails (#8136)
Command behavior changes:
* EXISTS should not alter LRU (#8016)
In Redis 5.0 and 6.0 it would have touched the LRU/LFU of the key.
* OBJECT should not reveal logically expired keys (#8016)
Will now behave the same TYPE or any other non-DEBUG command.
* Improve db id range check for SELECT and MOVE (#8085)
Changes the error message text on a wrong db index.
* Modify AUTH / HELLO error message (#7648)
Changes the error message text when the user isn't found or is disabled.
* BITOPS length limited to proto_max_bulk_len rather than 512MB (#8096)
The limit is now configurable like in SETRANGE, and APPEND.
* GEORADIUS[BYMEMBER] can fail with -OOM if Redis is over the memory limit (#8107)
Other behavior changes:
* Optionally (default) fail to start if requested bind address is not available (#7936)
If you rely on Redis starting successfully even if one of the bind addresses
is not available, you'll need to tune the new config.
* Limit the main db dictionaries expansion to prevent key eviction (#7954)
In the past big dictionary rehashing could result in massive data eviction.
Now this rehashing is delayed (up to a limit), which can result in performance
loss due to hash collisions.
* CONFIG REWRITE is atomic and safer, but requires write access to the config file's folder (#7824, #8051)
This change was already present in 6.0.9, but was missing from the release
notes.
* A new incremental eviction mechanism that reduces latency on eviction spikes (#7653)
In pathological cases this can cause memory to grow uncontrolled and may require
specific tuning.
* Not resetting "save" config when Redis is started with command line arguments. (#7092)
In case you provide command line arguments without "save" and count on it
being disabled, Now the defaults "save" config will kick in.
* Update memory metrics for INFO during loading (#7690)
* When "supervised" config is enabled, it takes precedence over "daemonize". (#8036)
* Assertion and panic, print crash log without generating SIGSEGV (#7585)
* Added crash log report on SIGABRT, instead of silently exiting (#8004)
* Disable THP (Transparent Huge Pages) if enabled (#7381)
If you deliberately enabled it, you'll need to config Redis to keep it.
Bug fixes:
* Handle output buffer limits for module blocked clients (#8141)
Could result in a module sending reply to a blocked client to go beyond the
limit.
* Fix setproctitle related crashes. (#8150, #8088)
Caused various crashes on startup, mainly on Apple M1 chips or under
instrumentation.
* A module doing RM_Call could cause replicas to get nested MULTI (#8097).
* Backup/restore cluster mode keys to slots map for repl-diskless-load=swapdb (#8108)
In cluster mode with repl-diskless-load, when loading failed, slot map
wouldn't have been restored.
* Fix oom-score-adj-values range, and bug when used in config file (#8046)
Enabling setting this in the config file in a line after enabling it, would
have been buggy.
* Reset average ttl when empty databases (#8106)
Just causing misleading metric in INFO
* Disable rehash when Redis has child process (#8007)
This could have caused excessive CoW during BGSAVE, replication or AOFRW.
* Further improved ACL algorithm for picking categories (#7966)
Output of ACL GETUSER is now more similar to the one provided by ACL SETUSER.
* Fix bug with module GIL being released prematurely (#8061)
Could in theory (and rarely) cause multi-threaded modules to corrupt memory.
* Fix cluster redirect for module command with no firstkey. (#7539)
* Reduce effect of client tracking causing feedback loop in key eviction (#8100)
* Kill disk-based fork child when all replicas drop and 'save' is not enabled (#7819)
* Rewritten commands (modified for propagation) are logged as their original command (#8006)
* Fix cluster access to unaligned memory (SIGBUS on old ARM) #7958
* If diskless repl child is killed, make sure to reap the child pid (#7742)
* Broadcast a PONG message when slot's migration is over, may reduce MOVED responses (#7571)
Other improvements:
* TLS Support in redis-benchmark (#7959)
* Accelerate diskless master connections, and general re-connections (#6271)
* Run active defrag while blocked / loading (#7726)
* Performance and memory reporting improvement - sds take control of its internal fragmentation (#7875)
* Speedup cluster failover. (#7948)
Platform / toolchain support related improvements:
* Optionally (not by default) use H/W Monotonic clock for faster time sampling (#7644)
* Remove the requirements for C11 and _Atomic supporting compiler (#7707)
This would allow to more easily build and use Redis on older systems and
compilers again.
* Fix crash log registers output on ARM. (#8020)
* Raspberry build fix. (#8095)
* Setting process title support for Haiku. (#8060)
* DragonFlyBSD RSS memory sampling support. (#8023)
New configuration options:
* Enable configuring OpenSSL using the standard openssl.cnf (#8143)
* oom-score-adj-values config can now take absolute values (besides relative ones) (#8046)
* TLS: Add different client cert support. (#8076)
* Note that a few other changes listed above added their config options.
Info fields and introspection changes:
* Add INFO fields to track diskless and disk-based replication progress (#7981)
* Add INFO field for main thread cpu time, and scrape system time. (#8132)
* Add total_forks to INFO STATS (#8155)
* Add maxclients and cluster_connections to INFO CLIENTS (#7979)
* Add tracking bcast flag and client redirection in client list (#7995)
* Fixed INFO client_recent_max_input_buffer includes argv array (#8065, see #7874)
* Note that a few other changes listed above added their info fields.
Module API changes:
* Add CTX_FLAGS_DENY_BLOCKING as a unified the way to know if blocking is allowed (#8025)
* Add data type callbacks for lazy free effort, and unlink (#7912)
* Add data type callback for COPY command (#8112)
* Add callbacks for defrag support. (#8149)
* Add module event for repl-diskless-load swapdb (#8153)
Module related fixes:
* Moved RMAPI_FUNC_SUPPORTED so that it's usable (#8037)
* Improve timer accuracy (#7987)
* Allow '\0' inside of result of RM_CreateStringPrintf (#6260)
Thanks to all the users and developers who made this release possible.
We'll follow up with more RC releases, until the code looks production ready
and we don't get reports of serious issues for a while.
A special thank you for the amount of work put into this release by:
- Oran Agra
- Yossi Gottlieb
- Viktor Söderqvist
- Yang Bodong
- Filipe Oliveira
- Guy Benoish
- Itamar Haber
- Madelyn Olson
- Wang Yuan
- Felipe Machado
- Wen Hui
- Tatsuya Arisawa
- Jonah H. Harris
- Raghav Muddur
- Jim Brunner
- Yaacov Hazan
- Allen Farris
- Chen Yang
- Nitai Caro
- sundb
- Meir Shpilraien
- maohuazhu
- Valentino Geron
- Zhao Zhao
- Qu Chen
- George Prekas
- Tyson Andre
- Uri Yagelnik
- Michael Grunder
- Huang Zw
- alexronke-channeladvisor
- Andy Pan
- Wu Yunlong
- Wei Kukey
- Yoav Steinberg
- Greg Femec
- Uri Shachar
- Nykolas Laurentino de Lima
- xhe
- zhenwei pi
- David CARLIER
Migrating from 6.0 to 6.2
=========================
Redis 6.2 is mostly a strict superset of 6.0, you should not have any problem
upgrading your application from 6.0 to 6.2. However there are some small changes
of behavior listed above, please make sure you are not badly affected by any of
them.
Specifically these sections:
* Command behavior changes
* Other behavior changes
--------------------------------------------------------------------------------
Cheers,
The Redis team

View File

@ -150,6 +150,11 @@ tcp-keepalive 300
# #
# tls-cert-file keydb.crt # tls-cert-file keydb.crt
# tls-key-file keydb.key # tls-key-file keydb.key
#
# If the key file is encrypted using a passphrase, it can be included here
# as well.
#
# tls-key-file-pass secret
# Normally Redis uses the same certificate for both server functions (accepting # Normally Redis uses the same certificate for both server functions (accepting
# connections) and client functions (replicating from a master, establishing # connections) and client functions (replicating from a master, establishing
@ -162,6 +167,11 @@ tcp-keepalive 300
# #
# tls-client-cert-file client.crt # tls-client-cert-file client.crt
# tls-client-key-file client.key # tls-client-key-file client.key
#
# If the key file is encrypted using a passphrase, it can be included here
# as well.
#
# tls-client-key-file-pass secret
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange: # Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
# #
@ -659,6 +669,18 @@ repl-disable-tcp-nodelay no
# By default the priority is 100. # By default the priority is 100.
replica-priority 100 replica-priority 100
# -----------------------------------------------------------------------------
# By default, Redis Sentinel includes all replicas in its reports. A replica
# can be excluded from Redis Sentinel's announcements. An unannounced replica
# will be ignored by the 'sentinel replicas <master>' command and won't be
# exposed to Redis Sentinel's clients.
#
# This option does not change the behavior of replica-priority. Even with
# replica-announced set to 'no', the replica can be promoted to master. To
# prevent this behavior, set replica-priority to 0.
#
# replica-announced yes
# It is possible for a master to stop accepting writes if there are less than # It is possible for a master to stop accepting writes if there are less than
# N replicas connected, having a lag less or equal than M seconds. # N replicas connected, having a lag less or equal than M seconds.
# #
@ -897,7 +919,7 @@ acllog-max-len 128
# order to provide better out-of-the-box Pub/Sub security. Therefore, it is # order to provide better out-of-the-box Pub/Sub security. Therefore, it is
# recommended that you explicitly define Pub/Sub permissions for all users # recommended that you explicitly define Pub/Sub permissions for all users
# rather then rely on implicit default values. Once you've set explicit # rather then rely on implicit default values. Once you've set explicit
# Pub/Sub for all exisitn users, you should uncomment the following line. # Pub/Sub for all existing users, you should uncomment the following line.
# #
# acl-pubsub-default resetchannels # acl-pubsub-default resetchannels
@ -1227,7 +1249,7 @@ disable-thp yes
# If the AOF is enabled on startup Redis will load the AOF, that is the file # If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees. # with the better durability guarantees.
# #
# Please check http://redis.io/topics/persistence for more information. # Please check https://redis.io/topics/persistence for more information.
appendonly no appendonly no
@ -1442,12 +1464,21 @@ lua-time-limit 5000
# master in your cluster. # master in your cluster.
# #
# Default is 1 (replicas migrate only if their masters remain with at least # Default is 1 (replicas migrate only if their masters remain with at least
# one replica). To disable migration just set it to a very large value. # one replica). To disable migration just set it to a very large value or
# set cluster-allow-replica-migration to 'no'.
# A value of 0 can be set but is useful only for debugging and dangerous # A value of 0 can be set but is useful only for debugging and dangerous
# in production. # in production.
# #
# cluster-migration-barrier 1 # cluster-migration-barrier 1
# Turning off this option allows to use less automatic cluster configuration.
# It both disables migration to orphaned masters and migration from masters
# that became empty.
#
# Default is 'yes' (allow automatic migrations).
#
# cluster-allow-replica-migration yes
# By default Redis Cluster nodes stop accepting queries if they detect there # By default Redis Cluster nodes stop accepting queries if they detect there
# is at least a hash slot uncovered (no available node is serving it). # is at least a hash slot uncovered (no available node is serving it).
# This way if the cluster is partially down (for example a range of hash slots # This way if the cluster is partially down (for example a range of hash slots
@ -1472,7 +1503,7 @@ lua-time-limit 5000
# cluster-replica-no-failover no # cluster-replica-no-failover no
# In order to setup your cluster make sure to read the documentation # In order to setup your cluster make sure to read the documentation
# available at http://redis.io web site. # available at https://redis.io web site.
########################## CLUSTER DOCKER/NAT support ######################## ########################## CLUSTER DOCKER/NAT support ########################
@ -1482,16 +1513,21 @@ lua-time-limit 5000
# #
# In order to make Redis Cluster working in such environments, a static # In order to make Redis Cluster working in such environments, a static
# configuration where each node knows its public address is needed. The # configuration where each node knows its public address is needed. The
# following two options are used for this scope, and are: # following four options are used for this scope, and are:
# #
# * cluster-announce-ip # * cluster-announce-ip
# * cluster-announce-port # * cluster-announce-port
# * cluster-announce-tls-port
# * cluster-announce-bus-port # * cluster-announce-bus-port
# #
# Each instructs the node about its address, client port, and cluster message # Each instructs the node about its address, client ports (for connections
# bus port. The information is then published in the header of the bus packets # without and with TLS) and cluster message bus port. The information is then
# so that other nodes will be able to correctly map the address of the node # published in the header of the bus packets so that other nodes will be able to
# publishing the information. # correctly map the address of the node publishing the information.
#
# If cluster-tls is set to yes and cluster-announce-tls-port is omitted or set
# to zero, then cluster-announce-port refers to the TLS port. Note also that
# cluster-announce-tls-port has no effect if cluster-tls is set to no.
# #
# If the above options are not used, the normal Redis Cluster auto-detection # If the above options are not used, the normal Redis Cluster auto-detection
# will be used instead. # will be used instead.
@ -1504,7 +1540,8 @@ lua-time-limit 5000
# Example: # Example:
# #
# cluster-announce-ip 10.1.1.5 # cluster-announce-ip 10.1.1.5
# cluster-announce-port 6379 # cluster-announce-tls-port 6379
# cluster-announce-port 0
# cluster-announce-bus-port 6380 # cluster-announce-bus-port 6380
################################## SLOW LOG ################################### ################################## SLOW LOG ###################################
@ -1555,7 +1592,7 @@ latency-monitor-threshold 0
############################# EVENT NOTIFICATION ############################## ############################# EVENT NOTIFICATION ##############################
# Redis can notify Pub/Sub clients about events happening in the key space. # Redis can notify Pub/Sub clients about events happening in the key space.
# This feature is documented at http://redis.io/topics/notifications # This feature is documented at https://redis.io/topics/notifications
# #
# For instance if keyspace events notification is enabled, and a client # For instance if keyspace events notification is enabled, and a client
# performs a DEL operation on key "foo" stored in the Database 0, two # performs a DEL operation on key "foo" stored in the Database 0, two
@ -1578,8 +1615,9 @@ latency-monitor-threshold 0
# x Expired events (events generated every time a key expires) # x Expired events (events generated every time a key expires)
# e Evicted events (events generated when a key is evicted for maxmemory) # e Evicted events (events generated when a key is evicted for maxmemory)
# t Stream commands # t Stream commands
# d Module key type events
# m Key-miss events (Note: It is not included in the 'A' class) # m Key-miss events (Note: It is not included in the 'A' class)
# A Alias for g$lshzxet, so that the "AKE" string means all the events # A Alias for g$lshzxetd, so that the "AKE" string means all the events
# (Except key-miss events which are excluded from 'A' due to their # (Except key-miss events which are excluded from 'A' due to their
# unique nature). # unique nature).
# #

View File

@ -301,6 +301,17 @@ endif
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS) FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS)
endif endif
ifndef V
define MAKE_INSTALL
@printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$(1)$(ENDCOLOR) 1>&2
@$(INSTALL) $(1) $(2)
endef
else
define MAKE_INSTALL
$(INSTALL) $(1) $(2)
endef
endif
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
REDIS_CXX=$(QUIET_CC)$(CXX) $(FINAL_CXXFLAGS) REDIS_CXX=$(QUIET_CC)$(CXX) $(FINAL_CXXFLAGS)
KEYDB_AS=$(QUIET_CC) as --64 -g KEYDB_AS=$(QUIET_CC) as --64 -g
@ -402,9 +413,6 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ) $(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS) $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS)
dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c mt19937-64.c
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d) DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
-include $(DEP) -include $(DEP)
@ -421,7 +429,7 @@ DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ
$(KEYDB_AS) $< -o $@ $(KEYDB_AS) $< -o $@
clean: clean:
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov KeyDB.info lcov-html Makefile.dep dict-benchmark rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov KeyDB.info lcov-html Makefile.dep
rm -f $(DEP) rm -f $(DEP)
.PHONY: clean .PHONY: clean
@ -476,9 +484,9 @@ src/help.h:
install: all install: all
@mkdir -p $(INSTALL_BIN) @mkdir -p $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN) $(call MAKE_INSTALL,$(REDIS_SERVER_NAME),$(INSTALL_BIN))
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN) $(call MAKE_INSTALL,$(REDIS_BENCHMARK_NAME),$(INSTALL_BIN))
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN) $(call MAKE_INSTALL,$(REDIS_CLI_NAME),$(INSTALL_BIN))
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME)
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME)
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)

View File

@ -248,7 +248,7 @@ user *ACLCreateUser(const char *name, size_t namelen) {
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL; if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
user *u = (user*)zmalloc(sizeof(*u), MALLOC_LOCAL); user *u = (user*)zmalloc(sizeof(*u), MALLOC_LOCAL);
u->name = sdsnewlen(name,namelen); u->name = sdsnewlen(name,namelen);
u->flags = USER_FLAG_DISABLED | g_pserver->acl_pubusub_default; u->flags = USER_FLAG_DISABLED | g_pserver->acl_pubsub_default;
u->allowed_subcommands = NULL; u->allowed_subcommands = NULL;
u->passwords = listCreate(); u->passwords = listCreate();
u->patterns = listCreate(); u->patterns = listCreate();
@ -655,6 +655,7 @@ sds ACLDescribeUser(user *u) {
if (u->flags & USER_FLAG_ALLCHANNELS) { if (u->flags & USER_FLAG_ALLCHANNELS) {
res = sdscatlen(res,"&* ",3); res = sdscatlen(res,"&* ",3);
} else { } else {
res = sdscatlen(res,"resetchannels ",14);
listRewind(u->channels,&li); listRewind(u->channels,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
sds thispat = (sds)listNodeValue(ln); sds thispat = (sds)listNodeValue(ln);
@ -1003,6 +1004,8 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK); serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK);
serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK); serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK);
serverAssert(ACLSetUser(u,"resetchannels",-1) == C_OK); serverAssert(ACLSetUser(u,"resetchannels",-1) == C_OK);
if (g_pserver->acl_pubsub_default & USER_FLAG_ALLCHANNELS)
serverAssert(ACLSetUser(u,"allchannels",-1) == C_OK);
serverAssert(ACLSetUser(u,"off",-1) == C_OK); serverAssert(ACLSetUser(u,"off",-1) == C_OK);
serverAssert(ACLSetUser(u,"sanitize-payload",-1) == C_OK); serverAssert(ACLSetUser(u,"sanitize-payload",-1) == C_OK);
serverAssert(ACLSetUser(u,"-@all",-1) == C_OK); serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
@ -1183,9 +1186,9 @@ int ACLCheckCommandPerm(client *c, int *keyidxptr) {
/* If there is no associated user, the connection can run anything. */ /* If there is no associated user, the connection can run anything. */
if (u == NULL) return ACL_OK; if (u == NULL) return ACL_OK;
/* Check if the user can execute this command. */ /* Check if the user can execute this command or if the command
if (!(u->flags & USER_FLAG_ALLCOMMANDS) && * doesn't need to be authenticated (hello, auth). */
c->cmd->proc != authCommand) if (!(u->flags & USER_FLAG_ALLCOMMANDS) && !(c->cmd->flags & CMD_NO_AUTH))
{ {
/* If the bit is not set we have to check further, in case the /* If the bit is not set we have to check further, in case the
* command is allowed just with that specific subcommand. */ * command is allowed just with that specific subcommand. */
@ -1363,6 +1366,22 @@ int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr)
} }
/* Check whether the command is ready to be exceuted by ACLCheckCommandPerm.
* If check passes, then check whether pub/sub channels of the command is
* ready to be executed by ACLCheckPubsubPerm */
int ACLCheckAllPerm(client *c, int *idxptr) {
int acl_retval = ACLCheckCommandPerm(c,idxptr);
if (acl_retval != ACL_OK)
return acl_retval;
if (c->cmd->proc == publishCommand)
acl_retval = ACLCheckPubsubPerm(c,1,1,0,idxptr);
else if (c->cmd->proc == subscribeCommand)
acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,0,idxptr);
else if (c->cmd->proc == psubscribeCommand)
acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,1,idxptr);
return acl_retval;
}
/* ============================================================================= /* =============================================================================
* ACL loading / saving functions * ACL loading / saving functions
* ==========================================================================*/ * ==========================================================================*/
@ -1876,6 +1895,10 @@ void addACLLogEntry(client *c, int reason, int argpos, sds username) {
void aclCommand(client *c) { void aclCommand(client *c) {
char *sub = szFromObj(c->argv[1]); char *sub = szFromObj(c->argv[1]);
if (!strcasecmp(sub,"setuser") && c->argc >= 3) { if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
/* Consider information about passwords or permissions
* to be sensitive, which will be the arguments for this
* subcommand. */
preventCommandLogging(c);
sds username = szFromObj(c->argv[2]); sds username = szFromObj(c->argv[2]);
/* Check username validity. */ /* Check username validity. */
if (ACLStringHasSpaces(username,sdslen(username))) { if (ACLStringHasSpaces(username,sdslen(username))) {

View File

@ -491,7 +491,7 @@ extern "C" int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
return AE_ERR; /* NO event with the specified ID found */ return AE_ERR; /* NO event with the specified ID found */
} }
/* How many milliseconds until the first timer should fire. /* How many microseconds until the first timer should fire.
* If there are no timers, -1 is returned. * If there are no timers, -1 is returned.
* *
* Note that's O(N) since time events are unsorted. * Note that's O(N) since time events are unsorted.
@ -500,7 +500,7 @@ extern "C" int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
* Much better but still insertion or deletion of timers is O(N). * Much better but still insertion or deletion of timers is O(N).
* 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)). * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
*/ */
static long msUntilEarliestTimer(aeEventLoop *eventLoop) { static int64_t usUntilEarliestTimer(aeEventLoop *eventLoop) {
aeTimeEvent *te = eventLoop->timeEventHead; aeTimeEvent *te = eventLoop->timeEventHead;
if (te == NULL) return -1; if (te == NULL) return -1;
@ -512,8 +512,7 @@ static long msUntilEarliestTimer(aeEventLoop *eventLoop) {
} }
monotime now = getMonotonicUs(); monotime now = getMonotonicUs();
return (now >= earliest->when) return (now >= earliest->when) ? 0 : earliest->when - now;
? 0 : (long)((earliest->when - now) / 1000);
} }
/* Process time events */ /* Process time events */
@ -674,14 +673,14 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j; int j;
struct timeval tv, *tvp; struct timeval tv, *tvp;
long msUntilTimer = -1; int64_t usUntilTimer = -1;
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
msUntilTimer = msUntilEarliestTimer(eventLoop); usUntilTimer = usUntilEarliestTimer(eventLoop);
if (msUntilTimer >= 0) { if (usUntilTimer >= 0) {
tv.tv_sec = msUntilTimer / 1000; tv.tv_sec = usUntilTimer / 1000000;
tv.tv_usec = (msUntilTimer % 1000) * 1000; tv.tv_usec = usUntilTimer % 1000000;
tvp = &tv; tvp = &tv;
} else { } else {
/* If we have to check for events but need to return /* If we have to check for events but need to return

View File

@ -115,7 +115,7 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
int retval, numevents = 0; int retval, numevents = 0;
retval = epoll_wait(state->epfd,state->events,eventLoop->setsize, retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1); tvp ? (tvp->tv_sec*1000 + (tvp->tv_usec + 999)/1000) : -1);
if (retval > 0) { if (retval > 0) {
int j; int j;

View File

@ -186,27 +186,6 @@ int anetDisableTcpNoDelay(char *err, int fd)
return anetSetTcpNoDelay(err, fd, 0); return anetSetTcpNoDelay(err, fd, 0);
} }
int anetSetSendBuffer(char *err, int fd, int buffsize)
{
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
{
anetSetError(err, "setsockopt SO_SNDBUF: %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpKeepAlive(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
/* Set the socket send timeout (SO_SNDTIMEO socket option) to the specified /* Set the socket send timeout (SO_SNDTIMEO socket option) to the specified
* number of milliseconds, or disable it if the 'ms' argument is zero. */ * number of milliseconds, or disable it if the 'ms' argument is zero. */
int anetSendTimeout(char *err, int fd, long long ms) { int anetSendTimeout(char *err, int fd, long long ms) {
@ -392,23 +371,11 @@ end:
} }
} }
int anetTcpConnect(char *err, const char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
}
int anetTcpNonBlockConnect(char *err, const char *addr, int port) int anetTcpNonBlockConnect(char *err, const char *addr, int port)
{ {
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK); return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
} }
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
const char *source_addr)
{
return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK);
}
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
const char *source_addr) const char *source_addr)
{ {
@ -444,46 +411,6 @@ int anetUnixGenericConnect(char *err, const char *path, int flags)
return s; return s;
} }
int anetUnixConnect(char *err, const char *path)
{
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
}
int anetUnixNonBlockConnect(char *err, const char *path)
{
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
}
/* Like read(2) but make sure 'count' is read before to return
* (unless error or EOF condition is encountered) */
int anetRead(int fd, char *buf, int count)
{
ssize_t nread, totlen = 0;
while(totlen != count) {
nread = read(fd,buf,count-totlen);
if (nread == 0) return totlen;
if (nread == -1) return -1;
totlen += nread;
buf += nread;
}
return totlen;
}
/* Like write(2) but make sure 'count' is written before to return
* (unless error is encountered) */
int anetWrite(int fd, char *buf, int count)
{
ssize_t nwritten, totlen = 0;
while(totlen != count) {
nwritten = write(fd,buf,count-totlen);
if (nwritten == 0) return totlen;
if (nwritten == -1) return -1;
totlen += nwritten;
buf += nwritten;
}
return totlen;
}
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) { static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
if (bind(s,sa,len) == -1) { if (bind(s,sa,len) == -1) {
anetSetError(err, "bind: %s", strerror(errno)); anetSetError(err, "bind: %s", strerror(errno));

View File

@ -57,26 +57,19 @@ extern "C" {
#define FD_TO_PEER_NAME 0 #define FD_TO_PEER_NAME 0
#define FD_TO_SOCK_NAME 1 #define FD_TO_SOCK_NAME 1
int anetTcpConnect(char *err, const char *addr, int port);
int anetTcpNonBlockConnect(char *err, const char *addr, int port); int anetTcpNonBlockConnect(char *err, const char *addr, int port);
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr); int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
int anetUnixConnect(char *err, const char *path);
int anetUnixNonBlockConnect(char *err, const char *path);
int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags); int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags);
int anetTcpServer(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen); int anetTcpServer(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen);
int anetTcp6Server(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen); int anetTcp6Server(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen);
int anetUnixServer(char *err, char *path, mode_t perm, int backlog); int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port); int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
int anetUnixAccept(char *err, int serversock); int anetUnixAccept(char *err, int serversock);
int anetWrite(int fd, char *buf, int count);
int anetNonBlock(char *err, int fd); int anetNonBlock(char *err, int fd);
int anetBlock(char *err, int fd); int anetBlock(char *err, int fd);
int anetCloexec(int fd); int anetCloexec(int fd);
int anetEnableTcpNoDelay(char *err, int fd); int anetEnableTcpNoDelay(char *err, int fd);
int anetDisableTcpNoDelay(char *err, int fd); int anetDisableTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
int anetSendTimeout(char *err, int fd, long long ms); int anetSendTimeout(char *err, int fd, long long ms);
int anetRecvTimeout(char *err, int fd, long long ms); int anetRecvTimeout(char *err, int fd, long long ms);
int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type); int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type);

View File

@ -233,7 +233,7 @@ void killAppendOnlyChild(void) {
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld", serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
(long) g_pserver->child_pid); (long) g_pserver->child_pid);
if (kill(g_pserver->child_pid,SIGUSR1) != -1) { if (kill(g_pserver->child_pid,SIGUSR1) != -1) {
while(wait3(&statloc,0,NULL) != g_pserver->child_pid); while(waitpid(-1, &statloc, 0) != g_pserver->child_pid);
} }
/* Reset the buffer accumulating changes while the child saves. */ /* Reset the buffer accumulating changes while the child saves. */
aofRewriteBufferReset(); aofRewriteBufferReset();
@ -249,9 +249,12 @@ void killAppendOnlyChild(void) {
void stopAppendOnly(void) { void stopAppendOnly(void) {
serverAssert(g_pserver->aof_state != AOF_OFF); serverAssert(g_pserver->aof_state != AOF_OFF);
flushAppendOnlyFile(1); flushAppendOnlyFile(1);
redis_fsync(g_pserver->aof_fd); if (redis_fsync(g_pserver->aof_fd) == -1) {
serverLog(LL_WARNING,"Fail to fsync the AOF file: %s",strerror(errno));
} else {
g_pserver->aof_fsync_offset = g_pserver->aof_current_size; g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
g_pserver->aof_last_fsync = g_pserver->unixtime; g_pserver->aof_last_fsync = g_pserver->unixtime;
}
close(g_pserver->aof_fd); close(g_pserver->aof_fd);
g_pserver->aof_fd = -1; g_pserver->aof_fd = -1;
@ -305,6 +308,15 @@ int startAppendOnly(void) {
g_pserver->aof_last_fsync = g_pserver->unixtime; g_pserver->aof_last_fsync = g_pserver->unixtime;
g_pserver->aof_fd = newfd; g_pserver->aof_fd = newfd;
/* If AOF fsync error in bio job, we just ignore it and log the event. */
int aof_bio_fsync_status;
atomicGet(g_pserver->aof_bio_fsync_status, aof_bio_fsync_status);
if (aof_bio_fsync_status == C_ERR) {
serverLog(LL_WARNING,
"AOF reopen, just ignore the AOF fsync error in bio job");
atomicSet(g_pserver->aof_bio_fsync_status,C_OK);
}
/* If AOF was in error state, we just ignore it and log the event. */ /* If AOF was in error state, we just ignore it and log the event. */
if (g_pserver->aof_last_write_status == C_ERR) { if (g_pserver->aof_last_write_status == C_ERR) {
serverLog(LL_WARNING,"AOF reopen, just ignore the last error."); serverLog(LL_WARNING,"AOF reopen, just ignore the last error.");
@ -1695,7 +1707,7 @@ int rewriteAppendOnlyFile(char *filename) {
if (write(g_pserver->aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr; if (write(g_pserver->aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr;
if (anetNonBlock(NULL,g_pserver->aof_pipe_read_ack_from_parent) != ANET_OK) if (anetNonBlock(NULL,g_pserver->aof_pipe_read_ack_from_parent) != ANET_OK)
goto werr; goto werr;
/* We read the ACK from the server using a 10 seconds timeout. Normally /* We read the ACK from the server using a 5 seconds timeout. Normally
* it should reply ASAP, but just in case we lose its reply, we are sure * it should reply ASAP, but just in case we lose its reply, we are sure
* the child will eventually get terminated. */ * the child will eventually get terminated. */
if (syncRead(g_pserver->aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 || if (syncRead(g_pserver->aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 ||

View File

@ -220,7 +220,23 @@ void *bioProcessBackgroundJobs(void *arg) {
if (type == BIO_CLOSE_FILE) { if (type == BIO_CLOSE_FILE) {
close(job->fd); close(job->fd);
} else if (type == BIO_AOF_FSYNC) { } else if (type == BIO_AOF_FSYNC) {
redis_fsync(job->fd); /* The fd may be closed by main thread and reused for another
* socket, pipe, or file. We just ignore these errno because
* aof fsync did not really fail. */
if (redis_fsync(job->fd) == -1 &&
errno != EBADF && errno != EINVAL)
{
int last_status;
atomicGet(g_pserver->aof_bio_fsync_status,last_status);
atomicSet(g_pserver->aof_bio_fsync_status,C_ERR);
atomicSet(g_pserver->aof_bio_fsync_errno,errno);
if (last_status == C_OK) {
serverLog(LL_WARNING,
"Fail to fsync the AOF file: %s",strerror(errno));
}
} else {
atomicSet(g_pserver->aof_bio_fsync_status,C_OK);
}
} else if (type == BIO_LAZY_FREE) { } else if (type == BIO_LAZY_FREE) {
job->free_fn(job->free_args); job->free_fn(job->free_args);
} else { } else {

View File

@ -108,12 +108,11 @@ void blockClient(client *c, int btype) {
void updateStatsOnUnblock(client *c, long blocked_us, long reply_us){ void updateStatsOnUnblock(client *c, long blocked_us, long reply_us){
const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us; const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us;
c->lastcmd->microseconds += total_cmd_duration; c->lastcmd->microseconds += total_cmd_duration;
/* Log the command into the Slow log if needed. */ /* Log the command into the Slow log if needed. */
if (!(c->lastcmd->flags & CMD_SKIP_SLOWLOG)) { slowlogPushCurrentCommand(c, c->lastcmd, total_cmd_duration);
slowlogPushEntryIfNeeded(c,c->argv,c->argc,total_cmd_duration);
/* Log the reply duration event. */ /* Log the reply duration event. */
latencyAddSampleIfNeeded("command-unblocking",reply_us/1000); latencyAddSampleIfNeeded("command-unblocking",reply_us/1000);
}
} }
/* This function is called in the beforeSleep() function of the event loop /* This function is called in the beforeSleep() function of the event loop
@ -134,7 +133,7 @@ void processUnblockedClients(int iel) {
listDelNode(unblocked_clients,ln); listDelNode(unblocked_clients,ln);
AssertCorrectThread(c); AssertCorrectThread(c);
fastlock_lock(&c->lock); std::unique_lock<fastlock> ul(c->lock);
c->flags &= ~CLIENT_UNBLOCKED; c->flags &= ~CLIENT_UNBLOCKED;
/* Process remaining data in the input buffer, unless the client /* Process remaining data in the input buffer, unless the client
@ -151,7 +150,6 @@ void processUnblockedClients(int iel) {
processInputBuffer(c, CMD_CALL_FULL); processInputBuffer(c, CMD_CALL_FULL);
} }
} }
fastlock_unlock(&c->lock);
} }
} }
@ -203,6 +201,16 @@ void unblockClient(client *c) {
} else { } else {
serverPanic("Unknown btype in unblockClient()."); serverPanic("Unknown btype in unblockClient().");
} }
/* Reset the client for a new query since, for blocking commands
* we do not do it immediately after the command returns (when the
* client got blocked) in order to be still able to access the argument
* vector from module callbacks and updateStatsOnUnblock. */
if (c->btype != BLOCKED_PAUSE) {
freeClientOriginalArgv(c);
resetClient(c);
}
/* Clear the flags, and put the client in the unblocked list so that /* Clear the flags, and put the client in the unblocked list so that
* we'll process new commands in its query buffer ASAP. */ * we'll process new commands in its query buffer ASAP. */
g_pserver->blocked_clients--; g_pserver->blocked_clients--;
@ -246,7 +254,7 @@ void disconnectAllBlockedClients(void) {
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
client *c = (client*)listNodeValue(ln); client *c = (client*)listNodeValue(ln);
fastlock_lock(&c->lock); std::unique_lock<fastlock> ul(c->lock);
if (c->flags & CLIENT_BLOCKED) { if (c->flags & CLIENT_BLOCKED) {
/* PAUSED clients are an exception, when they'll be unblocked, the /* PAUSED clients are an exception, when they'll be unblocked, the
* command processing will start from scratch, and the command will * command processing will start from scratch, and the command will
@ -261,7 +269,6 @@ void disconnectAllBlockedClients(void) {
unblockClient(c); unblockClient(c);
c->flags |= CLIENT_CLOSE_AFTER_REPLY; c->flags |= CLIENT_CLOSE_AFTER_REPLY;
} }
fastlock_unlock(&c->lock);
} }
} }
@ -298,7 +305,6 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
* freed by the next unblockClient() * freed by the next unblockClient()
* call. */ * call. */
if (dstkey) incrRefCount(dstkey); if (dstkey) incrRefCount(dstkey);
unblockClient(receiver);
monotime replyTimer; monotime replyTimer;
elapsedStart(&replyTimer); elapsedStart(&replyTimer);
@ -311,6 +317,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
listTypePush(o,value,wherefrom); listTypePush(o,value,wherefrom);
} }
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer)); updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
unblockClient(receiver);
if (dstkey) decrRefCount(dstkey); if (dstkey) decrRefCount(dstkey);
decrRefCount(value); decrRefCount(value);
@ -355,11 +362,11 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
int where = (receiver->lastcmd && int where = (receiver->lastcmd &&
receiver->lastcmd->proc == bzpopminCommand) receiver->lastcmd->proc == bzpopminCommand)
? ZSET_MIN : ZSET_MAX; ? ZSET_MIN : ZSET_MAX;
unblockClient(receiver);
monotime replyTimer; monotime replyTimer;
elapsedStart(&replyTimer); elapsedStart(&replyTimer);
genericZpopCommand(receiver,&rl->key,1,where,1,NULL); genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer)); updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
unblockClient(receiver);
zcard--; zcard--;
/* Replicate the command. */ /* Replicate the command. */
@ -492,6 +499,10 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
void serveClientsBlockedOnKeyByModule(readyList *rl) { void serveClientsBlockedOnKeyByModule(readyList *rl) {
dictEntry *de; dictEntry *de;
/* Optimization: If no clients are in type BLOCKED_MODULE,
* we can skip this loop. */
if (!g_pserver->blocked_clients_by_type[BLOCKED_MODULE]) return;
/* We serve clients in the same order they blocked for /* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */ * this key, from the first blocked to the last. */
de = dictFind(rl->db->blocking_keys,rl->key); de = dictFind(rl->db->blocking_keys,rl->key);
@ -575,7 +586,7 @@ void handleClientsBlockedOnKeys(void) {
* way we can lookup an object multiple times (BLMOVE does * way we can lookup an object multiple times (BLMOVE does
* that) without the risk of it being freed in the second * that) without the risk of it being freed in the second
* lookup, invalidating the first one. * lookup, invalidating the first one.
* See https://github.com/antirez/redis/pull/6554. */ * See https://github.com/redis/redis/pull/6554. */
serverTL->fixed_time_expire++; serverTL->fixed_time_expire++;
updateCachedTime(0); updateCachedTime(0);

View File

@ -33,6 +33,7 @@
typedef struct { typedef struct {
size_t keys; size_t keys;
size_t cow; size_t cow;
monotime cow_updated;
double progress; double progress;
childInfoType information_type; /* Type of information */ childInfoType information_type; /* Type of information */
} child_info_data; } child_info_data;
@ -69,17 +70,38 @@ void closeChildInfoPipe(void) {
void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress, const char *pname) { void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress, const char *pname) {
if (g_pserver->child_info_pipe[1] == -1) return; if (g_pserver->child_info_pipe[1] == -1) return;
child_info_data data = {0}; /* zero everything, including padding to sattisfy valgrind */ static monotime cow_updated = 0;
data.information_type = info_type; static uint64_t cow_update_cost = 0;
data.keys = keys; static size_t cow = 0;
data.cow = zmalloc_get_private_dirty(-1);
data.progress = progress;
if (data.cow) { child_info_data data = {0}; /* zero everything, including padding to satisfy valgrind */
/* When called to report current info, we need to throttle down CoW updates as they
* can be very expensive. To do that, we measure the time it takes to get a reading
* and schedule the next reading to happen not before time*CHILD_COW_COST_FACTOR
* passes. */
monotime now = getMonotonicUs();
if (info_type != CHILD_INFO_TYPE_CURRENT_INFO ||
!cow_updated ||
now - cow_updated > cow_update_cost * CHILD_COW_DUTY_CYCLE)
{
cow = zmalloc_get_private_dirty(-1);
cow_updated = getMonotonicUs();
cow_update_cost = cow_updated - now;
if (cow) {
serverLog((info_type == CHILD_INFO_TYPE_CURRENT_INFO) ? LL_VERBOSE : LL_NOTICE, serverLog((info_type == CHILD_INFO_TYPE_CURRENT_INFO) ? LL_VERBOSE : LL_NOTICE,
"%s: %zu MB of memory used by copy-on-write", "%s: %zu MB of memory used by copy-on-write",
pname, data.cow/(1024*1024)); pname, data.cow / (1024 * 1024));
} }
}
data.information_type = info_type;
data.keys = keys;
data.cow = cow;
data.cow_updated = cow_updated;
data.progress = progress;
ssize_t wlen = sizeof(data); ssize_t wlen = sizeof(data);
@ -89,9 +111,10 @@ void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress,
} }
/* Update Child info. */ /* Update Child info. */
void updateChildInfo(childInfoType information_type, size_t cow, size_t keys, double progress) { void updateChildInfo(childInfoType information_type, size_t cow, monotime cow_updated, size_t keys, double progress) {
if (information_type == CHILD_INFO_TYPE_CURRENT_INFO) { if (information_type == CHILD_INFO_TYPE_CURRENT_INFO) {
g_pserver->stat_current_cow_bytes = cow; g_pserver->stat_current_cow_bytes = cow;
g_pserver->stat_current_cow_updated = cow_updated;
g_pserver->stat_current_save_keys_processed = keys; g_pserver->stat_current_save_keys_processed = keys;
if (progress != -1) g_pserver->stat_module_progress = progress; if (progress != -1) g_pserver->stat_module_progress = progress;
} else if (information_type == CHILD_INFO_TYPE_AOF_COW_SIZE) { } else if (information_type == CHILD_INFO_TYPE_AOF_COW_SIZE) {
@ -107,7 +130,7 @@ void updateChildInfo(childInfoType information_type, size_t cow, size_t keys, do
* if complete data read into the buffer, * if complete data read into the buffer,
* data is stored into *buffer, and returns 1. * data is stored into *buffer, and returns 1.
* otherwise, the partial data is left in the buffer, waiting for the next read, and returns 0. */ * otherwise, the partial data is left in the buffer, waiting for the next read, and returns 0. */
int readChildInfo(childInfoType *information_type, size_t *cow, size_t *keys, double* progress) { int readChildInfo(childInfoType *information_type, size_t *cow, monotime *cow_updated, size_t *keys, double* progress) {
/* We are using here a static buffer in combination with the server.child_info_nread to handle short reads */ /* We are using here a static buffer in combination with the server.child_info_nread to handle short reads */
static child_info_data buffer; static child_info_data buffer;
ssize_t wlen = sizeof(buffer); ssize_t wlen = sizeof(buffer);
@ -124,6 +147,7 @@ int readChildInfo(childInfoType *information_type, size_t *cow, size_t *keys, do
if (g_pserver->child_info_nread == wlen) { if (g_pserver->child_info_nread == wlen) {
*information_type = buffer.information_type; *information_type = buffer.information_type;
*cow = buffer.cow; *cow = buffer.cow;
*cow_updated = buffer.cow_updated;
*keys = buffer.keys; *keys = buffer.keys;
*progress = buffer.progress; *progress = buffer.progress;
return 1; return 1;
@ -137,12 +161,13 @@ void receiveChildInfo(void) {
if (g_pserver->child_info_pipe[0] == -1) return; if (g_pserver->child_info_pipe[0] == -1) return;
size_t cow; size_t cow;
monotime cow_updated;
size_t keys; size_t keys;
double progress; double progress;
childInfoType information_type; childInfoType information_type;
/* Drain the pipe and update child info so that we get the final message. */ /* Drain the pipe and update child info so that we get the final message. */
while (readChildInfo(&information_type, &cow, &keys, &progress)) { while (readChildInfo(&information_type, &cow, &cow_updated, &keys, &progress)) {
updateChildInfo(information_type, cow, keys, progress); updateChildInfo(information_type, cow, cow_updated, keys, progress);
} }
} }

View File

@ -55,7 +55,7 @@ void clusterSendFail(char *nodename);
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request); void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
void clusterUpdateState(void); void clusterUpdateState(void);
int clusterNodeGetSlotBit(clusterNode *n, int slot); int clusterNodeGetSlotBit(clusterNode *n, int slot);
sds clusterGenNodesDescription(int filter); sds clusterGenNodesDescription(int filter, int use_pport);
clusterNode *clusterLookupNode(const char *name); clusterNode *clusterLookupNode(const char *name);
int clusterNodeAddSlave(clusterNode *master, clusterNode *slave); int clusterNodeAddSlave(clusterNode *master, clusterNode *slave);
int clusterAddSlot(clusterNode *n, int slot); int clusterAddSlot(clusterNode *n, int slot);
@ -198,6 +198,9 @@ int clusterLoadConfig(char *filename) {
* base port. */ * base port. */
n->cport = busp ? atoi(busp) : n->port + CLUSTER_PORT_INCR; n->cport = busp ? atoi(busp) : n->port + CLUSTER_PORT_INCR;
/* The plaintext port for client in a TLS cluster (n->pport) is not
* stored in nodes.conf. It is received later over the bus protocol. */
/* Parse flags */ /* Parse flags */
p = s = argv[2]; p = s = argv[2];
while(p) { while(p) {
@ -353,7 +356,7 @@ int clusterSaveConfig(int do_fsync) {
/* Get the nodes description and concatenate our "vars" directive to /* Get the nodes description and concatenate our "vars" directive to
* save currentEpoch and lastVoteEpoch. */ * save currentEpoch and lastVoteEpoch. */
ci = clusterGenNodesDescription(CLUSTER_NODE_HANDSHAKE); ci = clusterGenNodesDescription(CLUSTER_NODE_HANDSHAKE, 0);
ci = sdscatprintf(ci,"vars currentEpoch %llu lastVoteEpoch %llu\n", ci = sdscatprintf(ci,"vars currentEpoch %llu lastVoteEpoch %llu\n",
(unsigned long long) g_pserver->cluster->currentEpoch, (unsigned long long) g_pserver->cluster->currentEpoch,
(unsigned long long) g_pserver->cluster->lastVoteEpoch); (unsigned long long) g_pserver->cluster->lastVoteEpoch);
@ -372,7 +375,7 @@ int clusterSaveConfig(int do_fsync) {
if (write(fd,ci,sdslen(ci)) != (ssize_t)sdslen(ci)) goto err; if (write(fd,ci,sdslen(ci)) != (ssize_t)sdslen(ci)) goto err;
if (do_fsync) { if (do_fsync) {
g_pserver->cluster->todo_before_sleep &= ~CLUSTER_TODO_FSYNC_CONFIG; g_pserver->cluster->todo_before_sleep &= ~CLUSTER_TODO_FSYNC_CONFIG;
fsync(fd); if (fsync(fd) == -1) goto err;
} }
/* Truncate the file if needed to remove the final \n padding that /* Truncate the file if needed to remove the final \n padding that
@ -452,6 +455,26 @@ int clusterLockConfig(char *filename) {
return C_OK; return C_OK;
} }
/* Derives our ports to be announced in the cluster bus. */
void deriveAnnouncedPorts(int *announced_port, int *announced_pport,
int *announced_cport) {
int port = g_pserver->tls_cluster ? g_pserver->tls_port : g_pserver->port;
/* Default announced ports. */
*announced_port = port;
*announced_pport = g_pserver->tls_cluster ? g_pserver->port : 0;
*announced_cport = port + CLUSTER_PORT_INCR;
/* Config overriding announced ports. */
if (g_pserver->tls_cluster && g_pserver->cluster_announce_tls_port) {
*announced_port = g_pserver->cluster_announce_tls_port;
*announced_pport = g_pserver->cluster_announce_port;
} else if (g_pserver->cluster_announce_port) {
*announced_port = g_pserver->cluster_announce_port;
}
if (g_pserver->cluster_announce_bus_port) {
*announced_cport = g_pserver->cluster_announce_bus_port;
}
}
/* Some flags (currently just the NOFAILOVER flag) may need to be updated /* Some flags (currently just the NOFAILOVER flag) may need to be updated
* in the "myself" node based on the current configuration of the node, * in the "myself" node based on the current configuration of the node,
* that may change at runtime via CONFIG SET. This function changes the * that may change at runtime via CONFIG SET. This function changes the
@ -546,14 +569,9 @@ void clusterInit(void) {
memset(g_pserver->cluster->slots_keys_count,0, memset(g_pserver->cluster->slots_keys_count,0,
sizeof(g_pserver->cluster->slots_keys_count)); sizeof(g_pserver->cluster->slots_keys_count));
/* Set myself->port / cport to my listening ports, we'll just need to /* Set myself->port/cport/pport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */ * discover the IP address via MEET messages. */
myself->port = port; deriveAnnouncedPorts(&myself->port, &myself->pport, &myself->cport);
myself->cport = port+CLUSTER_PORT_INCR;
if (g_pserver->cluster_announce_port)
myself->port = g_pserver->cluster_announce_port;
if (g_pserver->cluster_announce_bus_port)
myself->cport = g_pserver->cluster_announce_bus_port;
g_pserver->cluster->mf_end = 0; g_pserver->cluster->mf_end = 0;
resetManualFailover(); resetManualFailover();
@ -820,6 +838,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
memset(node->ip,0,sizeof(node->ip)); memset(node->ip,0,sizeof(node->ip));
node->port = 0; node->port = 0;
node->cport = 0; node->cport = 0;
node->pport = 0;
node->fail_reports = listCreate(); node->fail_reports = listCreate();
node->voted_time = 0; node->voted_time = 0;
node->orphaned_time = 0; node->orphaned_time = 0;
@ -1526,6 +1545,7 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
if (node->link) freeClusterLink(node->link); if (node->link) freeClusterLink(node->link);
memcpy(node->ip,g->ip,NET_IP_STR_LEN); memcpy(node->ip,g->ip,NET_IP_STR_LEN);
node->port = ntohs(g->port); node->port = ntohs(g->port);
node->pport = ntohs(g->pport);
node->cport = ntohs(g->cport); node->cport = ntohs(g->cport);
node->flags &= ~CLUSTER_NODE_NOADDR; node->flags &= ~CLUSTER_NODE_NOADDR;
} }
@ -1547,6 +1567,7 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
node = createClusterNode(g->nodename, flags); node = createClusterNode(g->nodename, flags);
memcpy(node->ip,g->ip,NET_IP_STR_LEN); memcpy(node->ip,g->ip,NET_IP_STR_LEN);
node->port = ntohs(g->port); node->port = ntohs(g->port);
node->pport = ntohs(g->pport);
node->cport = ntohs(g->cport); node->cport = ntohs(g->cport);
clusterAddNode(node); clusterAddNode(node);
} }
@ -1586,6 +1607,7 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
{ {
char ip[NET_IP_STR_LEN] = {0}; char ip[NET_IP_STR_LEN] = {0};
int port = ntohs(hdr->port); int port = ntohs(hdr->port);
int pport = ntohs(hdr->pport);
int cport = ntohs(hdr->cport); int cport = ntohs(hdr->cport);
/* We don't proceed if the link is the same as the sender link, as this /* We don't proceed if the link is the same as the sender link, as this
@ -1597,12 +1619,13 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
if (link == node->link) return 0; if (link == node->link) return 0;
nodeIp2String(ip,link,hdr->myip); nodeIp2String(ip,link,hdr->myip);
if (node->port == port && node->cport == cport && if (node->port == port && node->cport == cport && node->pport == pport &&
strcmp(ip,node->ip) == 0) return 0; strcmp(ip,node->ip) == 0) return 0;
/* IP / port is different, update it. */ /* IP / port is different, update it. */
memcpy(node->ip,ip,sizeof(ip)); memcpy(node->ip,ip,sizeof(ip));
node->port = port; node->port = port;
node->pport = pport;
node->cport = cport; node->cport = cport;
if (node->link) freeClusterLink(node->link); if (node->link) freeClusterLink(node->link);
node->flags &= ~CLUSTER_NODE_NOADDR; node->flags &= ~CLUSTER_NODE_NOADDR;
@ -1652,7 +1675,7 @@ void clusterSetNodeAsMaster(clusterNode *n) {
* case we receive the info via an UPDATE packet. */ * case we receive the info via an UPDATE packet. */
void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) { void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) {
int j; int j;
clusterNode *curmaster, *newmaster = NULL; clusterNode *curmaster = NULL, *newmaster = NULL;
/* The dirty slots list is a list of slots for which we lose the ownership /* The dirty slots list is a list of slots for which we lose the ownership
* while having still keys inside. This usually happens after a failover * while having still keys inside. This usually happens after a failover
* or after a manual cluster reconfiguration operated by the admin. * or after a manual cluster reconfiguration operated by the admin.
@ -1663,6 +1686,12 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
uint16_t dirty_slots[CLUSTER_SLOTS]; uint16_t dirty_slots[CLUSTER_SLOTS];
int dirty_slots_count = 0; int dirty_slots_count = 0;
/* We should detect if sender is new master of our shard.
* We will know it if all our slots were migrated to sender, and sender
* has no slots except ours */
int sender_slots = 0;
int migrated_our_slots = 0;
/* Here we set curmaster to this node or the node this node /* Here we set curmaster to this node or the node this node
* replicates to if it's a slave. In the for loop we are * replicates to if it's a slave. In the for loop we are
* interested to check if slots are taken away from curmaster. */ * interested to check if slots are taken away from curmaster. */
@ -1675,6 +1704,8 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
for (j = 0; j < CLUSTER_SLOTS; j++) { for (j = 0; j < CLUSTER_SLOTS; j++) {
if (bitmapTestBit(slots,j)) { if (bitmapTestBit(slots,j)) {
sender_slots++;
/* The slot is already bound to the sender of this message. */ /* The slot is already bound to the sender of this message. */
if (g_pserver->cluster->slots[j] == sender) continue; if (g_pserver->cluster->slots[j] == sender) continue;
@ -1701,8 +1732,10 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
dirty_slots_count++; dirty_slots_count++;
} }
if (g_pserver->cluster->slots[j] == curmaster) if (g_pserver->cluster->slots[j] == curmaster) {
newmaster = sender; newmaster = sender;
migrated_our_slots++;
}
clusterDelSlot(j); clusterDelSlot(j);
clusterAddSlot(sender,j); clusterAddSlot(sender,j);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
@ -1725,7 +1758,9 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
* master. * master.
* 2) We are a slave and our master is left without slots. We need * 2) We are a slave and our master is left without slots. We need
* to replicate to the new slots owner. */ * to replicate to the new slots owner. */
if (newmaster && curmaster->numslots == 0) { if (newmaster && curmaster->numslots == 0 &&
(g_pserver->cluster_allow_replica_migration ||
sender_slots == migrated_our_slots)) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Configuration change detected. Reconfiguring myself " "Configuration change detected. Reconfiguring myself "
"as a replica of %.40s", sender->name); "as a replica of %.40s", sender->name);
@ -1853,7 +1888,7 @@ int clusterProcessPacket(clusterLink *link) {
nodeIsSlave(myself) && nodeIsSlave(myself) &&
myself->slaveof == sender && myself->slaveof == sender &&
hdr->mflags[0] & CLUSTERMSG_FLAG0_PAUSED && hdr->mflags[0] & CLUSTERMSG_FLAG0_PAUSED &&
g_pserver->cluster->mf_master_offset == 0) g_pserver->cluster->mf_master_offset == -1)
{ {
g_pserver->cluster->mf_master_offset = sender->repl_offset; g_pserver->cluster->mf_master_offset = sender->repl_offset;
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER); clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER);
@ -1904,6 +1939,7 @@ int clusterProcessPacket(clusterLink *link) {
node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE); node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE);
nodeIp2String(node->ip,link,hdr->myip); nodeIp2String(node->ip,link,hdr->myip);
node->port = ntohs(hdr->port); node->port = ntohs(hdr->port);
node->pport = ntohs(hdr->pport);
node->cport = ntohs(hdr->cport); node->cport = ntohs(hdr->cport);
clusterAddNode(node); clusterAddNode(node);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
@ -1966,6 +2002,7 @@ int clusterProcessPacket(clusterLink *link) {
link->node->flags |= CLUSTER_NODE_NOADDR; link->node->flags |= CLUSTER_NODE_NOADDR;
link->node->ip[0] = '\0'; link->node->ip[0] = '\0';
link->node->port = 0; link->node->port = 0;
link->node->pport = 0;
link->node->cport = 0; link->node->cport = 0;
freeClusterLink(link); freeClusterLink(link);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
@ -2473,19 +2510,16 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
hdr->myip[NET_IP_STR_LEN-1] = '\0'; hdr->myip[NET_IP_STR_LEN-1] = '\0';
} }
/* Handle cluster-announce-port as well. */ /* Handle cluster-announce-[tls-|bus-]port. */
int port = g_pserver->tls_cluster ? g_pserver->tls_port : g_pserver->port; int announced_port, announced_pport, announced_cport;
int announced_port = g_pserver->cluster_announce_port ? deriveAnnouncedPorts(&announced_port, &announced_pport, &announced_cport);
g_pserver->cluster_announce_port : port;
int announced_cport = g_pserver->cluster_announce_bus_port ?
g_pserver->cluster_announce_bus_port :
(port + CLUSTER_PORT_INCR);
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots)); memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
memset(hdr->slaveof,0,CLUSTER_NAMELEN); memset(hdr->slaveof,0,CLUSTER_NAMELEN);
if (myself->slaveof != NULL) if (myself->slaveof != NULL)
memcpy(hdr->slaveof,myself->slaveof->name, CLUSTER_NAMELEN); memcpy(hdr->slaveof,myself->slaveof->name, CLUSTER_NAMELEN);
hdr->port = htons(announced_port); hdr->port = htons(announced_port);
hdr->pport = htons(announced_pport);
hdr->cport = htons(announced_cport); hdr->cport = htons(announced_cport);
hdr->flags = htons(myself->flags); hdr->flags = htons(myself->flags);
hdr->state = g_pserver->cluster->state; hdr->state = g_pserver->cluster->state;
@ -2542,6 +2576,7 @@ void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) {
gossip->port = htons(n->port); gossip->port = htons(n->port);
gossip->cport = htons(n->cport); gossip->cport = htons(n->cport);
gossip->flags = htons(n->flags); gossip->flags = htons(n->flags);
gossip->pport = htons(n->pport);
gossip->notused1 = 0; gossip->notused1 = 0;
} }
@ -3140,7 +3175,7 @@ void clusterFailoverReplaceYourMaster(void) {
/* This function is called if we are a slave node and our master serving /* This function is called if we are a slave node and our master serving
* a non-zero amount of hash slots is in FAIL state. * a non-zero amount of hash slots is in FAIL state.
* *
* The gaol of this function is: * The goal of this function is:
* 1) To check if we are able to perform a failover, is our data updated? * 1) To check if we are able to perform a failover, is our data updated?
* 2) Try to get elected by masters. * 2) Try to get elected by masters.
* 3) Perform the failover informing all the other nodes. * 3) Perform the failover informing all the other nodes.
@ -3185,7 +3220,7 @@ void clusterHandleSlaveFailover(void) {
return; return;
} }
/* Set data_age to the number of seconds we are disconnected from /* Set data_age to the number of milliseconds we are disconnected from
* the master. */ * the master. */
if (getFirstMaster()->repl_state == REPL_STATE_CONNECTED) { if (getFirstMaster()->repl_state == REPL_STATE_CONNECTED) {
data_age = (mstime_t)(g_pserver->unixtime - getFirstMaster()->master->lastinteraction) data_age = (mstime_t)(g_pserver->unixtime - getFirstMaster()->master->lastinteraction)
@ -3469,7 +3504,7 @@ void resetManualFailover(void) {
g_pserver->cluster->mf_end = 0; /* No manual failover in progress. */ g_pserver->cluster->mf_end = 0; /* No manual failover in progress. */
g_pserver->cluster->mf_can_start = 0; g_pserver->cluster->mf_can_start = 0;
g_pserver->cluster->mf_slave = NULL; g_pserver->cluster->mf_slave = NULL;
g_pserver->cluster->mf_master_offset = 0; g_pserver->cluster->mf_master_offset = -1;
} }
/* If a manual failover timed out, abort it. */ /* If a manual failover timed out, abort it. */
@ -3490,7 +3525,7 @@ void clusterHandleManualFailover(void) {
* next steps are performed by clusterHandleSlaveFailover(). */ * next steps are performed by clusterHandleSlaveFailover(). */
if (g_pserver->cluster->mf_can_start) return; if (g_pserver->cluster->mf_can_start) return;
if (g_pserver->cluster->mf_master_offset == 0) return; /* Wait for offset... */ if (g_pserver->cluster->mf_master_offset == -1) return; /* Wait for offset... */
if (g_pserver->cluster->mf_master_offset == replicationGetSlaveOffset(getFirstMaster())) { if (g_pserver->cluster->mf_master_offset == replicationGetSlaveOffset(getFirstMaster())) {
/* Our replication offset matches the master replication offset /* Our replication offset matches the master replication offset
@ -3680,7 +3715,6 @@ void clusterCron(void) {
now - node->link->ctime > now - node->link->ctime >
g_pserver->cluster_node_timeout && /* was not already reconnected */ g_pserver->cluster_node_timeout && /* was not already reconnected */
node->ping_sent && /* we already sent a ping */ node->ping_sent && /* we already sent a ping */
node->pong_received < node->ping_sent && /* still waiting pong */
/* and we are waiting for the pong more than timeout/2 */ /* and we are waiting for the pong more than timeout/2 */
ping_delay > g_pserver->cluster_node_timeout/2 && ping_delay > g_pserver->cluster_node_timeout/2 &&
/* and in such interval we are not seeing any traffic at all. */ /* and in such interval we are not seeing any traffic at all. */
@ -3763,7 +3797,8 @@ void clusterCron(void) {
* the orphaned masters. Note that it does not make sense to try * the orphaned masters. Note that it does not make sense to try
* a migration if there is no master with at least *two* working * a migration if there is no master with at least *two* working
* slaves. */ * slaves. */
if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves) if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves &&
g_pserver->cluster_allow_replica_migration)
clusterHandleSlaveMigration(max_slaves); clusterHandleSlaveMigration(max_slaves);
} }
@ -3873,7 +3908,7 @@ int clusterNodeSetSlotBit(clusterNode *n, int slot) {
* However new masters with slots assigned are considered valid * However new masters with slots assigned are considered valid
* migration targets if the rest of the cluster is not a slave-less. * migration targets if the rest of the cluster is not a slave-less.
* *
* See https://github.com/antirez/redis/issues/3043 for more info. */ * See https://github.com/redis/redis/issues/3043 for more info. */
if (n->numslots == 1 && clusterMastersHaveSlaves()) if (n->numslots == 1 && clusterMastersHaveSlaves())
n->flags |= CLUSTER_NODE_MIGRATE_TO; n->flags |= CLUSTER_NODE_MIGRATE_TO;
} }
@ -4181,15 +4216,16 @@ sds representClusterNodeFlags(sds ci, uint16_t flags) {
* See clusterGenNodesDescription() top comment for more information. * See clusterGenNodesDescription() top comment for more information.
* *
* The function returns the string representation as an SDS string. */ * The function returns the string representation as an SDS string. */
sds clusterGenNodeDescription(clusterNode *node) { sds clusterGenNodeDescription(clusterNode *node, int use_pport) {
int j, start; int j, start;
sds ci; sds ci;
int port = use_pport && node->pport ? node->pport : node->port;
/* Node coordinates */ /* Node coordinates */
ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN); ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
ci = sdscatfmt(ci," %s:%i@%i ", ci = sdscatfmt(ci," %s:%i@%i ",
node->ip, node->ip,
node->port, port,
node->cport); node->cport);
/* Flags */ /* Flags */
@ -4300,10 +4336,13 @@ void clusterGenNodesSlotsInfo(int filter) {
* include all the known nodes in the representation, including nodes in * include all the known nodes in the representation, including nodes in
* the HANDSHAKE state. * the HANDSHAKE state.
* *
* Setting use_pport to 1 in a TLS cluster makes the result contain the
* plaintext client port rather then the TLS client port of each node.
*
* The representation obtained using this function is used for the output * The representation obtained using this function is used for the output
* of the CLUSTER NODES function, and as format for the cluster * of the CLUSTER NODES function, and as format for the cluster
* configuration file (nodes.conf) for a given node. */ * configuration file (nodes.conf) for a given node. */
sds clusterGenNodesDescription(int filter) { sds clusterGenNodesDescription(int filter, int use_pport) {
sds ci = sdsempty(), ni; sds ci = sdsempty(), ni;
dictIterator *di; dictIterator *di;
dictEntry *de; dictEntry *de;
@ -4316,7 +4355,7 @@ sds clusterGenNodesDescription(int filter) {
clusterNode *node = (clusterNode*)dictGetVal(de); clusterNode *node = (clusterNode*)dictGetVal(de);
if (node->flags & filter) continue; if (node->flags & filter) continue;
ni = clusterGenNodeDescription(node); ni = clusterGenNodeDescription(node, use_pport);
ci = sdscatsds(ci,ni); ci = sdscatsds(ci,ni);
sdsfree(ni); sdsfree(ni);
ci = sdscatlen(ci,"\n",1); ci = sdscatlen(ci,"\n",1);
@ -4363,7 +4402,37 @@ int getSlotOrReply(client *c, robj *o) {
return (int) slot; return (int) slot;
} }
void clusterReplyMultiBulkSlots(client *c) { void addNodeReplyForClusterSlot(client *c, clusterNode *node, int start_slot, int end_slot) {
int i, nested_elements = 3; /* slots (2) + master addr (1) */
void *nested_replylen = addReplyDeferredLen(c);
addReplyLongLong(c, start_slot);
addReplyLongLong(c, end_slot);
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->ip);
/* Report non-TLS ports to non-TLS client in TLS cluster if available. */
int use_pport = (g_pserver->tls_cluster &&
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
addReplyLongLong(c, use_pport && node->pport ? node->pport : node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
/* Remaining nodes in reply are replicas for slot range */
for (i = 0; i < node->numslaves; i++) {
/* This loop is copy/pasted from clusterGenNodeDescription()
* with modifications for per-slot node aggregation. */
if (nodeFailed(node->slaves[i])) continue;
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->slaves[i]->ip);
/* Report slave's non-TLS port to non-TLS client in TLS cluster */
addReplyLongLong(c, (use_pport && node->slaves[i]->pport ?
node->slaves[i]->pport :
node->slaves[i]->port));
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
nested_elements++;
}
setDeferredArrayLen(c, nested_replylen, nested_elements);
}
void clusterReplyMultiBulkSlots(client * c) {
/* Format: 1) 1) start slot /* Format: 1) 1) start slot
* 2) end slot * 2) end slot
* 3) 1) master IP * 3) 1) master IP
@ -4374,69 +4443,29 @@ void clusterReplyMultiBulkSlots(client *c) {
* 3) node ID * 3) node ID
* ... continued until done * ... continued until done
*/ */
clusterNode *n = NULL;
int num_masters = 0; int num_masters = 0, start = -1;
void *slot_replylen = addReplyDeferredLen(c); void *slot_replylen = addReplyDeferredLen(c);
dictEntry *de; for (int i = 0; i <= CLUSTER_SLOTS; i++) {
dictIterator *di = dictGetSafeIterator(g_pserver->cluster->nodes); /* Find start node and slot id. */
while((de = dictNext(di)) != NULL) { if (n == NULL) {
clusterNode *node = (clusterNode*)dictGetVal(de); if (i == CLUSTER_SLOTS) break;
int j = 0, start = -1; n = g_pserver->cluster->slots[i];
int i, nested_elements = 0; start = i;
continue;
/* Skip slaves (that are iterated when producing the output of their
* master) and masters not serving any slot. */
if (!nodeIsMaster(node) || node->numslots == 0) continue;
for(i = 0; i < node->numslaves; i++) {
if (nodeFailed(node->slaves[i])) continue;
nested_elements++;
} }
for (j = 0; j < CLUSTER_SLOTS; j++) { /* Add cluster slots info when occur different node with start
int bit, i; * or end of slot. */
if (i == CLUSTER_SLOTS || n != g_pserver->cluster->slots[i]) {
if ((bit = clusterNodeGetSlotBit(node,j)) != 0) { addNodeReplyForClusterSlot(c, n, start, i-1);
if (start == -1) start = j;
}
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
addReplyArrayLen(c, nested_elements + 3); /* slots (2) + master addr (1). */
if (bit && j == CLUSTER_SLOTS-1) j++;
/* If slot exists in output map, add to it's list.
* else, create a new output map for this slot */
if (start == j-1) {
addReplyLongLong(c, start); /* only one slot; low==high */
addReplyLongLong(c, start);
} else {
addReplyLongLong(c, start); /* low */
addReplyLongLong(c, j-1); /* high */
}
start = -1;
/* First node reply position is always the master */
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->ip);
addReplyLongLong(c, node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
/* Remaining nodes in reply are replicas for slot range */
for (i = 0; i < node->numslaves; i++) {
/* This loop is copy/pasted from clusterGenNodeDescription()
* with modifications for per-slot node aggregation */
if (nodeFailed(node->slaves[i])) continue;
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->slaves[i]->ip);
addReplyLongLong(c, node->slaves[i]->port);
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
}
num_masters++; num_masters++;
if (i == CLUSTER_SLOTS) break;
n = g_pserver->cluster->slots[i];
start = i;
} }
} }
}
dictReleaseIterator(di);
setDeferredArrayLen(c, slot_replylen, num_masters); setDeferredArrayLen(c, slot_replylen, num_masters);
} }
@ -4525,7 +4554,11 @@ NULL
} }
} else if (!strcasecmp(szFromObj(c->argv[1]),"nodes") && c->argc == 2) { } else if (!strcasecmp(szFromObj(c->argv[1]),"nodes") && c->argc == 2) {
/* CLUSTER NODES */ /* CLUSTER NODES */
sds nodes = clusterGenNodesDescription(0); /* Report plaintext ports, only if cluster is TLS but client is known to
* be non-TLS). */
int use_pport = (g_pserver->tls_cluster &&
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
sds nodes = clusterGenNodesDescription(0, use_pport);
addReplyVerbatim(c,nodes,sdslen(nodes),"txt"); addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
sdsfree(nodes); sdsfree(nodes);
} else if (!strcasecmp(szFromObj(c->argv[1]),"myid") && c->argc == 2) { } else if (!strcasecmp(szFromObj(c->argv[1]),"myid") && c->argc == 2) {
@ -4901,9 +4934,12 @@ NULL
return; return;
} }
/* Use plaintext port if cluster is TLS but client is non-TLS. */
int use_pport = (g_pserver->tls_cluster &&
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
addReplyArrayLen(c,n->numslaves); addReplyArrayLen(c,n->numslaves);
for (j = 0; j < n->numslaves; j++) { for (j = 0; j < n->numslaves; j++) {
sds ni = clusterGenNodeDescription(n->slaves[j]); sds ni = clusterGenNodeDescription(n->slaves[j], use_pport);
addReplyBulkCString(c,ni); addReplyBulkCString(c,ni);
sdsfree(ni); sdsfree(ni);
} }
@ -5930,18 +5966,14 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
* cluster is down. */ * cluster is down. */
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE; if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
return NULL; return NULL;
} else if ((cmd->flags & CMD_WRITE) && !(cmd->proc == evalCommand) } else if (cmd->flags & CMD_WRITE) {
&& !(cmd->proc == evalShaCommand)) /* The cluster is configured to allow read only commands */
{
/* The cluster is configured to allow read only commands
* but this command is neither readonly, nor EVAL or
* EVALSHA. */
if (error_code) *error_code = CLUSTER_REDIR_DOWN_RO_STATE; if (error_code) *error_code = CLUSTER_REDIR_DOWN_RO_STATE;
return NULL; return NULL;
} else { } else {
/* Fall through and allow the command to be executed: /* Fall through and allow the command to be executed:
* this happens when g_pserver->cluster_allow_reads_when_down is * this happens when server.cluster_allow_reads_when_down is
* true and the command is a readonly command or EVAL / EVALSHA. */ * true and the command is not a write command */
} }
} }
@ -5982,7 +6014,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
int is_write_command = (c->cmd->flags & CMD_WRITE) || int is_write_command = (c->cmd->flags & CMD_WRITE) ||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE)); (c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
if (c->flags & CLIENT_READONLY && if (c->flags & CLIENT_READONLY &&
(!is_write_command || cmd->proc == evalCommand || cmd->proc == evalShaCommand) && !is_write_command &&
nodeIsSlave(myself) && nodeIsSlave(myself) &&
myself->slaveof == n) myself->slaveof == n)
{ {
@ -6019,10 +6051,15 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
} else if (error_code == CLUSTER_REDIR_MOVED || } else if (error_code == CLUSTER_REDIR_MOVED ||
error_code == CLUSTER_REDIR_ASK) error_code == CLUSTER_REDIR_ASK)
{ {
/* Redirect to IP:port. Include plaintext port if cluster is TLS but
* client is non-TLS. */
int use_pport = (g_pserver->tls_cluster &&
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
int port = use_pport && n->pport ? n->pport : n->port;
addReplyErrorSds(c,sdscatprintf(sdsempty(), addReplyErrorSds(c,sdscatprintf(sdsempty(),
"-%s %d %s:%d", "-%s %d %s:%d",
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED", (error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
hashslot,n->ip,n->port)); hashslot, n->ip, port));
} else { } else {
serverPanic("getNodeByQuery() unknown error."); serverPanic("getNodeByQuery() unknown error.");
} }

View File

@ -139,7 +139,9 @@ typedef struct clusterNode {
mstime_t orphaned_time; /* Starting time of orphaned master condition */ mstime_t orphaned_time; /* Starting time of orphaned master condition */
long long repl_offset; /* Last known repl offset for this node. */ long long repl_offset; /* Last known repl offset for this node. */
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */ char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
int port; /* Latest known clients port of this node */ int port; /* Latest known clients port (TLS or plain). */
int pport; /* Latest known clients plaintext port. Only used
if the main clients port is for TLS. */
int cport; /* Latest known cluster port of this node. */ int cport; /* Latest known cluster port of this node. */
clusterLink *link; /* TCP/IP link with this node */ clusterLink *link; /* TCP/IP link with this node */
list *fail_reports; /* List of nodes signaling this as failing */ list *fail_reports; /* List of nodes signaling this as failing */
@ -172,7 +174,7 @@ typedef struct clusterState {
clusterNode *mf_slave; /* Slave performing the manual failover. */ clusterNode *mf_slave; /* Slave performing the manual failover. */
/* Manual failover state of slave. */ /* Manual failover state of slave. */
long long mf_master_offset; /* Master offset the slave needs to start MF long long mf_master_offset; /* Master offset the slave needs to start MF
or zero if still not received. */ or -1 if still not received. */
int mf_can_start; /* If non-zero signal that the manual failover int mf_can_start; /* If non-zero signal that the manual failover
can start requesting masters vote. */ can start requesting masters vote. */
/* The following fields are used by masters to take state on elections. */ /* The following fields are used by masters to take state on elections. */
@ -198,7 +200,8 @@ typedef struct {
uint16_t port; /* base port last time it was seen */ uint16_t port; /* base port last time it was seen */
uint16_t cport; /* cluster port last time it was seen */ uint16_t cport; /* cluster port last time it was seen */
uint16_t flags; /* node->flags copy */ uint16_t flags; /* node->flags copy */
uint32_t notused1; uint16_t pport; /* plaintext-port, when base port is TLS */
uint16_t notused1;
} clusterMsgDataGossip; } clusterMsgDataGossip;
typedef struct { typedef struct {
@ -271,7 +274,8 @@ typedef struct {
unsigned char myslots[CLUSTER_SLOTS/8]; unsigned char myslots[CLUSTER_SLOTS/8];
char slaveof[CLUSTER_NAMELEN]; char slaveof[CLUSTER_NAMELEN];
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */ char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
char notused1[34]; /* 34 bytes reserved for future usage. */ char notused1[32]; /* 32 bytes reserved for future usage. */
uint16_t pport; /* Sender TCP plaintext port, if base port is TLS */
uint16_t cport; /* Sender TCP cluster bus port */ uint16_t cport; /* Sender TCP cluster bus port */
uint16_t flags; /* Sender node flags */ uint16_t flags; /* Sender node flags */
unsigned char state; /* Cluster state from the POV of the sender */ unsigned char state; /* Cluster state from the POV of the sender */

View File

@ -231,8 +231,6 @@ typedef union typeData {
typedef struct typeInterface { typedef struct typeInterface {
/* Called on server start, to init the server with default value */ /* Called on server start, to init the server with default value */
void (*init)(typeData data); void (*init)(typeData data);
/* Called on server start, should return 1 on success, 0 on error and should set err */
int (*load)(typeData data, sds *argc, int argv, const char **err);
/* Called on server startup and CONFIG SET, returns 1 on success, 0 on error /* Called on server startup and CONFIG SET, returns 1 on success, 0 on error
* and can set a verbose err string, update is true when called from CONFIG SET */ * and can set a verbose err string, update is true when called from CONFIG SET */
int (*set)(typeData data, sds value, int update, const char **err); int (*set)(typeData data, sds value, int update, const char **err);
@ -245,11 +243,16 @@ typedef struct typeInterface {
typedef struct standardConfig { typedef struct standardConfig {
const char *name; /* The user visible name of this config */ const char *name; /* The user visible name of this config */
const char *alias; /* An alias that can also be used for this config */ const char *alias; /* An alias that can also be used for this config */
const int modifiable; /* Can this value be updated by CONFIG SET? */ const unsigned int flags; /* Flags for this specific config */
typeInterface interface; /* The function pointers that define the type interface */ typeInterface interface; /* The function pointers that define the type interface */
typeData data; /* The type specific data exposed used by the interface */ typeData data; /* The type specific data exposed used by the interface */
} standardConfig; } standardConfig;
#define MODIFIABLE_CONFIG 0 /* This is the implied default for a standard
* config, which is mutable. */
#define IMMUTABLE_CONFIG (1ULL<<0) /* Can this value only be set at startup? */
#define SENSITIVE_CONFIG (1ULL<<1) /* Does this value contain sensitive information */
extern standardConfig configs[]; extern standardConfig configs[];
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
@ -692,6 +695,10 @@ void loadServerConfigFromString(char *config) {
goto loaderr; goto loaderr;
} }
/* To ensure backward compatibility and work while hz is out of range */
if (g_pserver->config_hz < CONFIG_MIN_HZ) g_pserver->config_hz = CONFIG_MIN_HZ;
if (g_pserver->config_hz > CONFIG_MAX_HZ) g_pserver->config_hz = CONFIG_MAX_HZ;
sdsfreesplitres(lines,totlines); sdsfreesplitres(lines,totlines);
return; return;
@ -788,9 +795,13 @@ void configSetCommand(client *c) {
/* Iterate the configs that are standard */ /* Iterate the configs that are standard */
for (standardConfig *config = configs; config->name != NULL; config++) { for (standardConfig *config = configs; config->name != NULL; config++) {
if(config->modifiable && (!strcasecmp(szFromObj(c->argv[2]),config->name) || if (!(config->flags & IMMUTABLE_CONFIG) &&
(!strcasecmp(szFromObj(c->argv[2]),config->name) ||
(config->alias && !strcasecmp(szFromObj(c->argv[2]),config->alias)))) (config->alias && !strcasecmp(szFromObj(c->argv[2]),config->alias))))
{ {
if (config->flags & SENSITIVE_CONFIG) {
preventCommandLogging(c);
}
if (!config->interface.set(config->data,szFromObj(o),1,&errstr)) { if (!config->interface.set(config->data,szFromObj(o),1,&errstr)) {
goto badfmt; goto badfmt;
} }
@ -1482,15 +1493,20 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
return; return;
} }
/* Note that if there are no save parameters at all, all the current /* Rewrite save parameters, or an empty 'save ""' line to avoid the
* config line with "save" will be detected as orphaned and deleted, * defaults from being used.
* resulting into no RDB persistence as expected. */ */
if (!g_pserver->saveparamslen) {
rewriteConfigRewriteLine(state,"save",sdsnew("save \"\""),1);
} else {
for (j = 0; j < g_pserver->saveparamslen; j++) { for (j = 0; j < g_pserver->saveparamslen; j++) {
line = sdscatprintf(sdsempty(),"save %ld %d", line = sdscatprintf(sdsempty(),"save %ld %d",
(long) g_pserver->saveparams[j].seconds, g_pserver->saveparams[j].changes); (long) g_pserver->saveparams[j].seconds, g_pserver->saveparams[j].changes);
rewriteConfigRewriteLine(state,"save",line,1); rewriteConfigRewriteLine(state,"save",line,1);
} }
/* Mark "save" as processed in case g_pserver->saveparamslen is zero. */ }
/* Mark "save" as processed in case server.saveparamslen is zero. */
rewriteConfigMarkAsProcessed(state,"save"); rewriteConfigMarkAsProcessed(state,"save");
} }
@ -1833,14 +1849,11 @@ int rewriteConfig(char *path, int force_all) {
#define LOADBUF_SIZE 256 #define LOADBUF_SIZE 256
static char loadbuf[LOADBUF_SIZE]; static char loadbuf[LOADBUF_SIZE];
#define MODIFIABLE_CONFIG 1 #define embedCommonConfig(config_name, config_alias, config_flags) \
#define IMMUTABLE_CONFIG 0 config_name, config_alias, config_flags,
#define embedCommonConfig(config_name, config_alias, is_modifiable) \
config_name, config_alias, is_modifiable,
#define embedConfigInterface(initfn, setfn, getfn, rewritefn) { \ #define embedConfigInterface(initfn, setfn, getfn, rewritefn) { \
initfn, nullptr, setfn, getfn, rewritefn, \ initfn, setfn, getfn, rewritefn, \
}, },
/* What follows is the generic config types that are supported. To add a new /* What follows is the generic config types that are supported. To add a new
@ -1887,11 +1900,11 @@ static void boolConfigRewrite(typeData data, const char *name, struct rewriteCon
rewriteConfigYesNoOption(state, name,*(data.yesno.config), data.yesno.default_value); rewriteConfigYesNoOption(state, name,*(data.yesno.config), data.yesno.default_value);
} }
constexpr standardConfig createBoolConfig(const char *name, const char *alias, int modifiable, int &config_addr, int defaultValue, int (*is_valid)(int val, const char **err), int (*update)(int val, int prev, const char **err)) constexpr standardConfig createBoolConfig(const char *name, const char *alias, unsigned flags, int &config_addr, int defaultValue, int (*is_valid)(int val, const char **err), int (*update)(int val, int prev, const char **err))
{ {
standardConfig conf = { standardConfig conf = {
embedCommonConfig(name, alias, modifiable) embedCommonConfig(name, alias, flags)
{ boolConfigInit, nullptr, boolConfigSet, boolConfigGet, boolConfigRewrite } { boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite }
}; };
conf.data.yesno.config = &config_addr; conf.data.yesno.config = &config_addr;
conf.data.yesno.default_value = defaultValue; conf.data.yesno.default_value = defaultValue;
@ -1962,9 +1975,9 @@ static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConf
#define ALLOW_EMPTY_STRING 0 #define ALLOW_EMPTY_STRING 0
#define EMPTY_STRING_IS_NULL 1 #define EMPTY_STRING_IS_NULL 1
constexpr standardConfig createStringConfig(const char *name, const char *alias, int modifiable, int empty_to_null, char *&config_addr, const char *defaultValue, int (*is_valid)(char*,const char**), int (*update)(char*,char*,const char**)) { constexpr standardConfig createStringConfig(const char *name, const char *alias, unsigned flags, int empty_to_null, char *&config_addr, const char *defaultValue, int (*is_valid)(char*,const char**), int (*update)(char*,char*,const char**)) {
standardConfig conf = { standardConfig conf = {
embedCommonConfig(name, alias, modifiable) embedCommonConfig(name, alias, flags)
embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite) embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite)
}; };
conf.data.string = { conf.data.string = {
@ -1977,9 +1990,9 @@ constexpr standardConfig createStringConfig(const char *name, const char *alias,
return conf; return conf;
} }
constexpr standardConfig createSDSConfig(const char *name, const char *alias, int modifiable, int empty_to_null, sds &config_addr, const char *defaultValue, int (*is_valid)(char*,const char**), int (*update)(char*,char*,const char**)) { constexpr standardConfig createSDSConfig(const char *name, const char *alias, unsigned flags, int empty_to_null, sds &config_addr, const char *defaultValue, int (*is_valid)(char*,const char**), int (*update)(char*,char*,const char**)) {
standardConfig conf = { standardConfig conf = {
embedCommonConfig(name, alias, modifiable) embedCommonConfig(name, alias, flags)
embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite) embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite)
}; };
conf.data.sds = { conf.data.sds = {
@ -2036,9 +2049,9 @@ static void enumConfigRewrite(typeData data, const char *name, struct rewriteCon
rewriteConfigEnumOption(state, name,*(data.enumd.config), data.enumd.enum_value, data.enumd.default_value); rewriteConfigEnumOption(state, name,*(data.enumd.config), data.enumd.enum_value, data.enumd.default_value);
} }
constexpr standardConfig createEnumConfig(const char *name, const char *alias, int modifiable, configEnum *enumVal, int &config_addr, int defaultValue, int (*is_valid)(int,const char**), int (*update)(int,int,const char**)) { constexpr standardConfig createEnumConfig(const char *name, const char *alias, unsigned flags, configEnum *enumVal, int &config_addr, int defaultValue, int (*is_valid)(int,const char**), int (*update)(int,int,const char**)) {
standardConfig c = { standardConfig c = {
embedCommonConfig(name, alias, modifiable) embedCommonConfig(name, alias, flags)
embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite) embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite)
}; };
c.data.enumd = { c.data.enumd = {
@ -2194,9 +2207,9 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite
#define INTEGER_CONFIG 0 #define INTEGER_CONFIG 0
#define MEMORY_CONFIG 1 #define MEMORY_CONFIG 1
constexpr standardConfig embedCommonNumericalConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) { constexpr standardConfig embedCommonNumericalConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) {
standardConfig conf = { standardConfig conf = {
embedCommonConfig(name, alias, modifiable) embedCommonConfig(name, alias, flags)
embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite) embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite)
}; };
conf.data.numeric.is_memory = (memory); conf.data.numeric.is_memory = (memory);
@ -2208,73 +2221,73 @@ constexpr standardConfig embedCommonNumericalConfig(const char *name, const char
return conf; return conf;
} }
constexpr standardConfig createIntConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, int &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) constexpr standardConfig createIntConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, int &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**))
{ {
standardConfig conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); standardConfig conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_INT; conf.data.numeric.numeric_type = NUMERIC_TYPE_INT;
conf.data.numeric.config.i = &config_addr; conf.data.numeric.config.i = &config_addr;
return conf; return conf;
} }
constexpr standardConfig createUIntConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, unsigned int &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) constexpr standardConfig createUIntConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, unsigned int &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**))
{ {
auto conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); auto conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_UINT; conf.data.numeric.numeric_type = NUMERIC_TYPE_UINT;
conf.data.numeric.config.ui = &(config_addr); conf.data.numeric.config.ui = &(config_addr);
return conf; return conf;
} }
constexpr standardConfig createLongConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, long &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) { constexpr standardConfig createLongConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, long &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) {
auto conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); auto conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_LONG; conf.data.numeric.numeric_type = NUMERIC_TYPE_LONG;
conf.data.numeric.config.l = &(config_addr); conf.data.numeric.config.l = &(config_addr);
return conf; return conf;
} }
constexpr standardConfig createULongConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, unsigned long &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) { constexpr standardConfig createULongConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, unsigned long &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) {
auto conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); auto conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_ULONG; conf.data.numeric.numeric_type = NUMERIC_TYPE_ULONG;
conf.data.numeric.config.ul = &(config_addr); conf.data.numeric.config.ul = &(config_addr);
return conf; return conf;
} }
constexpr standardConfig createLongLongConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, long long &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) { constexpr standardConfig createLongLongConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, long long &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) {
auto conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); auto conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_LONG_LONG; conf.data.numeric.numeric_type = NUMERIC_TYPE_LONG_LONG;
conf.data.numeric.config.ll = &(config_addr); conf.data.numeric.config.ll = &(config_addr);
return conf; return conf;
} }
constexpr standardConfig createULongLongConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, unsigned long long &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) { constexpr standardConfig createULongLongConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, unsigned long long &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) {
auto conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); auto conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_ULONG_LONG; conf.data.numeric.numeric_type = NUMERIC_TYPE_ULONG_LONG;
conf.data.numeric.config.ull = &(config_addr); conf.data.numeric.config.ull = &(config_addr);
return conf; return conf;
} }
constexpr standardConfig createSizeTConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, size_t &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) { constexpr standardConfig createSizeTConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, size_t &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) {
auto conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); auto conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_SIZE_T; conf.data.numeric.numeric_type = NUMERIC_TYPE_SIZE_T;
conf.data.numeric.config.st = &(config_addr); conf.data.numeric.config.st = &(config_addr);
return conf; return conf;
} }
constexpr standardConfig createSSizeTConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, ssize_t &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) { constexpr standardConfig createSSizeTConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, ssize_t &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) {
auto conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); auto conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_SSIZE_T; conf.data.numeric.numeric_type = NUMERIC_TYPE_SSIZE_T;
conf.data.numeric.config.sst = &(config_addr); conf.data.numeric.config.sst = &(config_addr);
return conf; return conf;
} }
constexpr standardConfig createTimeTConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, time_t &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) { constexpr standardConfig createTimeTConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, time_t &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) {
auto conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); auto conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_TIME_T; conf.data.numeric.numeric_type = NUMERIC_TYPE_TIME_T;
conf.data.numeric.config.tt = &(config_addr); conf.data.numeric.config.tt = &(config_addr);
return conf; return conf;
} }
constexpr standardConfig createOffTConfig(const char *name, const char *alias, int modifiable, long long lower, long long upper, off_t &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) { constexpr standardConfig createOffTConfig(const char *name, const char *alias, unsigned flags, long long lower, long long upper, off_t &config_addr, long long defaultValue, int memory, int (*is_valid)(long long, const char**), int (*update)(long long, long long, const char**)) {
auto conf = embedCommonNumericalConfig(name, alias, modifiable, lower, upper, defaultValue, memory, is_valid, update); auto conf = embedCommonNumericalConfig(name, alias, flags, lower, upper, defaultValue, memory, is_valid, update);
conf.data.numeric.numeric_type = NUMERIC_TYPE_OFF_T; conf.data.numeric.numeric_type = NUMERIC_TYPE_OFF_T;
conf.data.numeric.config.ot = &(config_addr); conf.data.numeric.config.ot = &(config_addr);
return conf; return conf;
@ -2614,13 +2627,15 @@ standardConfig configs[] = {
createBoolConfig("crash-memcheck-enabled", NULL, MODIFIABLE_CONFIG, g_pserver->memcheck_enabled, 1, NULL, NULL), createBoolConfig("crash-memcheck-enabled", NULL, MODIFIABLE_CONFIG, g_pserver->memcheck_enabled, 1, NULL, NULL),
createBoolConfig("use-exit-on-panic", NULL, MODIFIABLE_CONFIG, g_pserver->use_exit_on_panic, 0, NULL, NULL), createBoolConfig("use-exit-on-panic", NULL, MODIFIABLE_CONFIG, g_pserver->use_exit_on_panic, 0, NULL, NULL),
createBoolConfig("disable-thp", NULL, MODIFIABLE_CONFIG, g_pserver->disable_thp, 1, NULL, NULL), createBoolConfig("disable-thp", NULL, MODIFIABLE_CONFIG, g_pserver->disable_thp, 1, NULL, NULL),
createBoolConfig("cluster-allow-replica-migration", NULL, MODIFIABLE_CONFIG, g_pserver->cluster_allow_replica_migration, 1, NULL, NULL),
createBoolConfig("replica-announced", NULL, MODIFIABLE_CONFIG, g_pserver->replica_announced, 1, NULL, NULL),
/* 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),
createStringConfig("unixsocket", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->unixsocket, NULL, NULL, NULL), createStringConfig("unixsocket", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->unixsocket, NULL, NULL, NULL),
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.pidfile, NULL, NULL, NULL), createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.pidfile, NULL, NULL, NULL),
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->slave_announce_ip, NULL, NULL, NULL), createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->slave_announce_ip, NULL, NULL, NULL),
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masteruser, NULL, NULL, updateMasterAuthConfig), createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masteruser, NULL, NULL, updateMasterAuthConfig),
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->cluster_announce_ip, NULL, NULL, NULL), createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->cluster_announce_ip, NULL, NULL, NULL),
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->syslog_ident, "redis", NULL, NULL), createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->syslog_ident, "redis", NULL, NULL),
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->rdb_filename, CONFIG_DEFAULT_RDB_FILENAME, isValidDBfilename, NULL), createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->rdb_filename, CONFIG_DEFAULT_RDB_FILENAME, isValidDBfilename, NULL),
@ -2633,8 +2648,8 @@ standardConfig configs[] = {
createStringConfig("proc-title-template", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, cserver.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate), createStringConfig("proc-title-template", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, cserver.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate),
/* SDS Configs */ /* SDS Configs */
createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masterauth, NULL, NULL, updateMasterAuthConfig), createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masterauth, NULL, NULL, updateMasterAuthConfig),
createSDSConfig("requirepass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->requirepass, NULL, NULL, updateRequirePass), createSDSConfig("requirepass", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->requirepass, NULL, NULL, updateRequirePass),
/* Enum Configs */ /* Enum Configs */
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, cserver.supervised_mode, SUPERVISED_NONE, NULL, NULL), createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, cserver.supervised_mode, SUPERVISED_NONE, NULL, NULL),
@ -2644,7 +2659,7 @@ standardConfig configs[] = {
createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, g_pserver->maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL), createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, g_pserver->maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL),
createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, g_pserver->aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL), createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, g_pserver->aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL),
createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, g_pserver->oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj), createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, g_pserver->oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj),
createEnumConfig("acl-pubsub-default", NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, g_pserver->acl_pubusub_default, USER_FLAG_ALLCHANNELS, NULL, NULL), createEnumConfig("acl-pubsub-default", NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, g_pserver->acl_pubsub_default, USER_FLAG_ALLCHANNELS, NULL, NULL),
createEnumConfig("sanitize-dump-payload", NULL, MODIFIABLE_CONFIG, sanitize_dump_payload_enum, cserver.sanitize_dump_payload, SANITIZE_DUMP_NO, NULL, NULL), createEnumConfig("sanitize-dump-payload", NULL, MODIFIABLE_CONFIG, sanitize_dump_payload_enum, cserver.sanitize_dump_payload, SANITIZE_DUMP_NO, NULL, NULL),
/* Integer configs */ /* Integer configs */
@ -2670,6 +2685,7 @@ standardConfig configs[] = {
createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, g_pserver->tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */ createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, g_pserver->tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */
createIntConfig("cluster-announce-bus-port", NULL, MODIFIABLE_CONFIG, 0, 65535, g_pserver->cluster_announce_bus_port, 0, INTEGER_CONFIG, NULL, NULL), /* Default: Use +10000 offset. */ createIntConfig("cluster-announce-bus-port", NULL, MODIFIABLE_CONFIG, 0, 65535, g_pserver->cluster_announce_bus_port, 0, INTEGER_CONFIG, NULL, NULL), /* Default: Use +10000 offset. */
createIntConfig("cluster-announce-port", NULL, MODIFIABLE_CONFIG, 0, 65535, g_pserver->cluster_announce_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use g_pserver->port */ createIntConfig("cluster-announce-port", NULL, MODIFIABLE_CONFIG, 0, 65535, g_pserver->cluster_announce_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use g_pserver->port */
createIntConfig("cluster-announce-tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, g_pserver->cluster_announce_tls_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use server.tls_port */
createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_timeout, 60, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_timeout, 60, INTEGER_CONFIG, NULL, NULL),
createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL),
createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
@ -2734,8 +2750,10 @@ standardConfig configs[] = {
createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool), createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool),
createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, updateTlsCfg),
createStringConfig("tls-client-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-client-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-client-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-client-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-client-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, updateTlsCfg),
createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg), createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg),

View File

@ -127,9 +127,10 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
#include <stdio.h> #include <stdio.h>
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
int crc64Test(int argc, char *argv[]) { int crc64Test(int argc, char *argv[], int accurate) {
UNUSED(argc); UNUSED(argc);
UNUSED(argv); UNUSED(argv);
UNUSED(accurate);
crc64_init(); crc64_init();
printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n", printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
(uint64_t)_crc64(0, "123456789", 9)); (uint64_t)_crc64(0, "123456789", 9));

View File

@ -11,7 +11,7 @@ void crc64_init(void);
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int crc64Test(int argc, char *argv[]); int crc64Test(int argc, char *argv[], int accurate);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -168,9 +168,9 @@ robj_roptr lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
keymiss: keymiss:
if (!(flags & LOOKUP_NONOTIFY)) { if (!(flags & LOOKUP_NONOTIFY)) {
g_pserver->stat_keyspace_misses++;
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id); notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
} }
g_pserver->stat_keyspace_misses++;
return NULL; return NULL;
} }
@ -194,16 +194,24 @@ robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
robj *lookupKeyWrite(redisDb *db, robj *key) { robj *lookupKeyWrite(redisDb *db, robj *key) {
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE); return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
} }
static void SentReplyOnKeyMiss(client *c, robj *reply){
serverAssert(sdsEncodedObject(reply));
sds rep = szFromObj(reply);
if (sdslen(rep) > 1 && rep[0] == '-'){
addReplyErrorObject(c, reply);
} else {
addReply(c,reply);
}
}
robj_roptr lookupKeyReadOrReply(client *c, robj *key, robj *reply) { robj_roptr lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
robj_roptr o = lookupKeyRead(c->db, key); robj_roptr o = lookupKeyRead(c->db, key);
if (!o) addReply(c,reply); if (!o) SentReplyOnKeyMiss(c, reply);
return o; return o;
} }
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) { robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) {
robj *o = lookupKeyWrite(c->db, key); robj *o = lookupKeyWrite(c->db, key);
if (!o) addReply(c,reply); if (!o) SentReplyOnKeyMiss(c, reply);
return o; return o;
} }
@ -1681,8 +1689,13 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
incrRefCount(argv[0]); incrRefCount(argv[0]);
incrRefCount(argv[1]); incrRefCount(argv[1]);
/* If the master decided to expire a key we must propagate it to replicas no matter what..
* Even if module executed a command without asking for propagation. */
int prev_replication_allowed = g_pserver->replication_allowed;
g_pserver->replication_allowed = 1;
if (!g_pserver->fActiveReplica) // Active replicas do their own expiries, do not propogate if (!g_pserver->fActiveReplica) // Active replicas do their own expiries, do not propogate
propagate(cserver.delCommand,db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL); propagate(cserver.delCommand,db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
g_pserver->replication_allowed = prev_replication_allowed;
decrRefCount(argv[0]); decrRefCount(argv[0]);
decrRefCount(argv[1]); decrRefCount(argv[1]);

View File

@ -259,7 +259,7 @@ void xorObjectDigest(redisDb *db, robj_roptr keyobj, unsigned char *digest, robj
} }
streamIteratorStop(&si); streamIteratorStop(&si);
} else if (o->type == OBJ_MODULE) { } else if (o->type == OBJ_MODULE) {
RedisModuleDigest md; RedisModuleDigest md = {{0},{0}};
moduleValue *mv = (moduleValue*)ptrFromObj(o); moduleValue *mv = (moduleValue*)ptrFromObj(o);
moduleType *mt = mv->type; moduleType *mt = mv->type;
moduleInitDigestContext(md); moduleInitDigestContext(md);
@ -455,7 +455,7 @@ void debugCommand(client *c) {
" conflicting keys will generate an exception and kill the server." " conflicting keys will generate an exception and kill the server."
" * NOSAVE: the database will be loaded from an existing RDB file.", " * NOSAVE: the database will be loaded from an existing RDB file.",
" Examples:", " Examples:",
" * DEBUG RELOAD: verify that the server is able to persist, flsuh and reload", " * DEBUG RELOAD: verify that the server is able to persist, flush and reload",
" the database.", " the database.",
" * DEBUG RELOAD NOSAVE: replace the current database with the contents of an", " * DEBUG RELOAD NOSAVE: replace the current database with the contents of an",
" existing RDB file.", " existing RDB file.",
@ -487,7 +487,7 @@ NULL
} else if (!strcasecmp(szFromObj(c->argv[1]),"segfault")) { } else if (!strcasecmp(szFromObj(c->argv[1]),"segfault")) {
*((char*)-1) = 'x'; *((char*)-1) = 'x';
} else if (!strcasecmp(szFromObj(c->argv[1]),"panic")) { } else if (!strcasecmp(szFromObj(c->argv[1]),"panic")) {
serverPanic("DEBUG PANIC called at Unix time %ld", time(NULL)); serverPanic("DEBUG PANIC called at Unix time %lld", (long long)time(NULL));
} else if (!strcasecmp(szFromObj(c->argv[1]),"restart") || } else if (!strcasecmp(szFromObj(c->argv[1]),"restart") ||
!strcasecmp(szFromObj(c->argv[1]),"crash-and-recover")) !strcasecmp(szFromObj(c->argv[1]),"crash-and-recover"))
{ {
@ -941,6 +941,7 @@ NULL
/* =========================== Crash handling ============================== */ /* =========================== Crash handling ============================== */
void _serverAssert(const char *estr, const char *file, int line) { void _serverAssert(const char *estr, const char *file, int line) {
g_fInCrash = true;
bugReportStart(); bugReportStart();
serverLog(LL_WARNING,"=== ASSERTION FAILED ==="); serverLog(LL_WARNING,"=== ASSERTION FAILED ===");
serverLog(LL_WARNING,"==> %s:%d '%s' is not true",file,line,estr); serverLog(LL_WARNING,"==> %s:%d '%s' is not true",file,line,estr);
@ -1030,12 +1031,14 @@ void _serverAssertWithInfo(const client *c, robj_roptr o, const char *estr, cons
} }
void _serverPanic(const char *file, int line, const char *msg, ...) { void _serverPanic(const char *file, int line, const char *msg, ...) {
g_fInCrash = true;
va_list ap; va_list ap;
va_start(ap,msg); va_start(ap,msg);
char fmtmsg[256]; char fmtmsg[256];
vsnprintf(fmtmsg,sizeof(fmtmsg),msg,ap); vsnprintf(fmtmsg,sizeof(fmtmsg),msg,ap);
va_end(ap); va_end(ap);
g_fInCrash = true;
bugReportStart(); bugReportStart();
serverLog(LL_WARNING,"------------------------------------------------"); serverLog(LL_WARNING,"------------------------------------------------");
serverLog(LL_WARNING,"!!! Software Failure. Press left mouse button to continue"); serverLog(LL_WARNING,"!!! Software Failure. Press left mouse button to continue");
@ -1916,7 +1919,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Accessing address: %p", (void*)info->si_addr); "Accessing address: %p", (void*)info->si_addr);
} }
if (info->si_pid != -1) { if (info->si_code <= SI_USER && info->si_pid != -1) {
serverLog(LL_WARNING, "Killed by PID: %ld, UID: %d", (long) info->si_pid, info->si_uid); serverLog(LL_WARNING, "Killed by PID: %ld, UID: %d", (long) info->si_pid, info->si_uid);
} }
@ -1944,6 +1947,8 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
} }
void printCrashReport(void) { void printCrashReport(void) {
g_fInCrash = true;
/* Log INFO and CLIENT LIST */ /* Log INFO and CLIENT LIST */
logServerInfo(); logServerInfo();

View File

@ -371,7 +371,9 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
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, newsds); uint64_t hash = dictGetHash(d, newsds);
replaceSatelliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged); dictEntry **deref = dictFindEntryRefByPtrAndHash(d, sdsele, hash);
if (deref)
(*deref)->key = newsds;
ln->value = newsds; ln->value = newsds;
defragged++; defragged++;
} }

View File

@ -45,11 +45,7 @@
#include "dict.h" #include "dict.h"
#include "zmalloc.h" #include "zmalloc.h"
#ifndef DICT_BENCHMARK_MAIN
#include "redisassert.h" #include "redisassert.h"
#else
#include <assert.h>
#endif
/* Using dictEnableResize() / dictDisableResize() we make possible to /* Using dictEnableResize() / dictDisableResize() we make possible to
* enable/disable resizing of the hash table as needed. This is very important * enable/disable resizing of the hash table as needed. This is very important
@ -1176,20 +1172,18 @@ void dictGetStats(char *buf, size_t bufsize, dict *d) {
/* ------------------------------- Benchmark ---------------------------------*/ /* ------------------------------- Benchmark ---------------------------------*/
#ifdef DICT_BENCHMARK_MAIN #ifdef REDIS_TEST
#include "sds.h"
uint64_t hashCallback(const void *key) { uint64_t hashCallback(const void *key) {
return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); return dictGenHashFunction((unsigned char*)key, strlen((char*)key));
} }
int compareCallback(void *privdata, const void *key1, const void *key2) { int compareCallback(void *privdata, const void *key1, const void *key2) {
int l1,l2; int l1,l2;
DICT_NOTUSED(privdata); DICT_NOTUSED(privdata);
l1 = sdslen((sds)key1); l1 = strlen((char*)key1);
l2 = sdslen((sds)key2); l2 = strlen((char*)key2);
if (l1 != l2) return 0; if (l1 != l2) return 0;
return memcmp(key1, key2, l1) == 0; return memcmp(key1, key2, l1) == 0;
} }
@ -1197,7 +1191,19 @@ int compareCallback(void *privdata, const void *key1, const void *key2) {
void freeCallback(void *privdata, void *val) { void freeCallback(void *privdata, void *val) {
DICT_NOTUSED(privdata); DICT_NOTUSED(privdata);
sdsfree(val); zfree(val);
}
char *stringFromLongLong(long long value) {
char buf[32];
int len;
char *s;
len = sprintf(buf,"%lld",value);
s = zmalloc(len+1);
memcpy(s, buf, len);
s[len] = '\0';
return s;
} }
dictType BenchmarkDictType = { dictType BenchmarkDictType = {
@ -1216,22 +1222,26 @@ dictType BenchmarkDictType = {
printf(msg ": %ld items in %lld ms\n", count, elapsed); \ printf(msg ": %ld items in %lld ms\n", count, elapsed); \
} while(0) } while(0)
/* dict-benchmark [count] */ /* ./redis-server test dict [<count> | --accurate] */
int main(int argc, char **argv) { int dictTest(int argc, char **argv, int accurate) {
long j; long j;
long long start, elapsed; long long start, elapsed;
dict *dict = dictCreate(&BenchmarkDictType,NULL); dict *dict = dictCreate(&BenchmarkDictType,NULL);
long count = 0; long count = 0;
if (argc == 2) { if (argc == 4) {
count = strtol(argv[1],NULL,10); if (accurate) {
} else {
count = 5000000; count = 5000000;
} else {
count = strtol(argv[3],NULL,10);
}
} else {
count = 5000;
} }
start_benchmark(); start_benchmark();
for (j = 0; j < count; j++) { for (j = 0; j < count; j++) {
int retval = dictAdd(dict,sdsfromlonglong(j),(void*)j); int retval = dictAdd(dict,stringFromLongLong(j),(void*)j);
assert(retval == DICT_OK); assert(retval == DICT_OK);
} }
end_benchmark("Inserting"); end_benchmark("Inserting");
@ -1244,28 +1254,28 @@ int main(int argc, char **argv) {
start_benchmark(); start_benchmark();
for (j = 0; j < count; j++) { for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(j); char *key = stringFromLongLong(j);
dictEntry *de = dictFind(dict,key); dictEntry *de = dictFind(dict,key);
assert(de != NULL); assert(de != NULL);
sdsfree(key); zfree(key);
} }
end_benchmark("Linear access of existing elements"); end_benchmark("Linear access of existing elements");
start_benchmark(); start_benchmark();
for (j = 0; j < count; j++) { for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(j); char *key = stringFromLongLong(j);
dictEntry *de = dictFind(dict,key); dictEntry *de = dictFind(dict,key);
assert(de != NULL); assert(de != NULL);
sdsfree(key); zfree(key);
} }
end_benchmark("Linear access of existing elements (2nd round)"); end_benchmark("Linear access of existing elements (2nd round)");
start_benchmark(); start_benchmark();
for (j = 0; j < count; j++) { for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(rand() % count); char *key = stringFromLongLong(rand() % count);
dictEntry *de = dictFind(dict,key); dictEntry *de = dictFind(dict,key);
assert(de != NULL); assert(de != NULL);
sdsfree(key); zfree(key);
} }
end_benchmark("Random access of existing elements"); end_benchmark("Random access of existing elements");
@ -1278,17 +1288,17 @@ int main(int argc, char **argv) {
start_benchmark(); start_benchmark();
for (j = 0; j < count; j++) { for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(rand() % count); char *key = stringFromLongLong(rand() % count);
key[0] = 'X'; key[0] = 'X';
dictEntry *de = dictFind(dict,key); dictEntry *de = dictFind(dict,key);
assert(de == NULL); assert(de == NULL);
sdsfree(key); zfree(key);
} }
end_benchmark("Accessing missing"); end_benchmark("Accessing missing");
start_benchmark(); start_benchmark();
for (j = 0; j < count; j++) { for (j = 0; j < count; j++) {
sds key = sdsfromlonglong(j); char *key = stringFromLongLong(j);
int retval = dictDelete(dict,key); int retval = dictDelete(dict,key);
assert(retval == DICT_OK); assert(retval == DICT_OK);
key[0] += 17; /* Change first number to letter. */ key[0] += 17; /* Change first number to letter. */
@ -1296,5 +1306,7 @@ int main(int argc, char **argv) {
assert(retval == DICT_OK); assert(retval == DICT_OK);
} }
end_benchmark("Removing and adding"); end_benchmark("Removing and adding");
dictRelease(dict);
return 0;
} }
#endif #endif

View File

@ -238,6 +238,10 @@ extern dictType dictTypeHeapStringCopyKey;
extern dictType dictTypeHeapStrings; extern dictType dictTypeHeapStrings;
extern dictType dictTypeHeapStringCopyKeyValue; extern dictType dictTypeHeapStringCopyKeyValue;
#ifdef REDIS_TEST
int dictTest(int argc, char *argv[], int accurate);
#endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -105,11 +105,12 @@ uint64_t intrev64(uint64_t v) {
#include <stdio.h> #include <stdio.h>
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
int endianconvTest(int argc, char *argv[]) { int endianconvTest(int argc, char *argv[], int accurate) {
char buf[32]; char buf[32];
UNUSED(argc); UNUSED(argc);
UNUSED(argv); UNUSED(argv);
UNUSED(accurate);
sprintf(buf,"ciaoroma"); sprintf(buf,"ciaoroma");
memrev16(buf); memrev16(buf);

View File

@ -76,7 +76,7 @@ uint64_t intrev64(uint64_t v);
#endif #endif
#ifdef REDIS_TEST #ifdef REDIS_TEST
int endianconvTest(int argc, char *argv[]); int endianconvTest(int argc, char *argv[], int accurate);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -70,7 +70,9 @@
#ifdef HAVE_BACKTRACE #ifdef HAVE_BACKTRACE
#include <ucontext.h> #include <ucontext.h>
__attribute__((weak)) void logStackTrace(ucontext_t *) {} __attribute__((weak)) void logStackTrace(void *, int) {
printf("\tFailed to generate stack trace\n");
}
#endif #endif
extern int g_fInCrash; extern int g_fInCrash;
@ -188,9 +190,7 @@ void printTrace()
{ {
#ifdef HAVE_BACKTRACE #ifdef HAVE_BACKTRACE
serverLog(3 /*LL_WARNING*/, "printing backtrace for thread %d", gettid()); serverLog(3 /*LL_WARNING*/, "printing backtrace for thread %d", gettid());
ucontext_t ctxt; logStackTrace(nullptr, 1);
getcontext(&ctxt);
logStackTrace(&ctxt);
#endif #endif
} }

View File

@ -185,7 +185,7 @@ struct commandHelp {
8, 8,
"6.2.0" }, "6.2.0" },
{ "CLIENT KILL", { "CLIENT KILL",
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [USER username] [ADDR ip:port] [SKIPME yes/no]", "[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [USER username] [ADDR ip:port] [LADDR 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" },

View File

@ -284,7 +284,7 @@ size_t intsetBlobLen(intset *is) {
return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding); return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
} }
/* Validate the integrity of the data stracture. /* Validate the integrity of the data structure.
* when `deep` is 0, only the integrity of the header is validated. * when `deep` is 0, only the integrity of the header is validated.
* when `deep` is 1, we make sure there are no duplicate or out of order records. */ * when `deep` is 1, we make sure there are no duplicate or out of order records. */
int intsetValidateIntegrity(const unsigned char *p, size_t size, int deep) { int intsetValidateIntegrity(const unsigned char *p, size_t size, int deep) {
@ -392,7 +392,7 @@ static void checkConsistency(intset *is) {
} }
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
int intsetTest(int argc, char **argv) { int intsetTest(int argc, char **argv, int accurate) {
uint8_t success; uint8_t success;
int i; int i;
intset *is; intset *is;
@ -400,6 +400,7 @@ int intsetTest(int argc, char **argv) {
UNUSED(argc); UNUSED(argc);
UNUSED(argv); UNUSED(argv);
UNUSED(accurate);
printf("Value encodings: "); { printf("Value encodings: "); {
assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16); assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16);
@ -424,6 +425,7 @@ int intsetTest(int argc, char **argv) {
is = intsetAdd(is,4,&success); assert(success); is = intsetAdd(is,4,&success); assert(success);
is = intsetAdd(is,4,&success); assert(!success); is = intsetAdd(is,4,&success); assert(!success);
ok(); ok();
zfree(is);
} }
printf("Large number of random adds: "); { printf("Large number of random adds: "); {
@ -436,6 +438,7 @@ int intsetTest(int argc, char **argv) {
assert(intrev32ifbe(is->length) == inserts); assert(intrev32ifbe(is->length) == inserts);
checkConsistency(is); checkConsistency(is);
ok(); ok();
zfree(is);
} }
printf("Upgrade from int16 to int32: "); { printf("Upgrade from int16 to int32: "); {
@ -447,6 +450,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,32)); assert(intsetFind(is,32));
assert(intsetFind(is,65535)); assert(intsetFind(is,65535));
checkConsistency(is); checkConsistency(is);
zfree(is);
is = intsetNew(); is = intsetNew();
is = intsetAdd(is,32,NULL); is = intsetAdd(is,32,NULL);
@ -457,6 +461,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,-65535)); assert(intsetFind(is,-65535));
checkConsistency(is); checkConsistency(is);
ok(); ok();
zfree(is);
} }
printf("Upgrade from int16 to int64: "); { printf("Upgrade from int16 to int64: "); {
@ -468,6 +473,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,32)); assert(intsetFind(is,32));
assert(intsetFind(is,4294967295)); assert(intsetFind(is,4294967295));
checkConsistency(is); checkConsistency(is);
zfree(is);
is = intsetNew(); is = intsetNew();
is = intsetAdd(is,32,NULL); is = intsetAdd(is,32,NULL);
@ -478,6 +484,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,-4294967295)); assert(intsetFind(is,-4294967295));
checkConsistency(is); checkConsistency(is);
ok(); ok();
zfree(is);
} }
printf("Upgrade from int32 to int64: "); { printf("Upgrade from int32 to int64: "); {
@ -489,6 +496,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,65535)); assert(intsetFind(is,65535));
assert(intsetFind(is,4294967295)); assert(intsetFind(is,4294967295));
checkConsistency(is); checkConsistency(is);
zfree(is);
is = intsetNew(); is = intsetNew();
is = intsetAdd(is,65535,NULL); is = intsetAdd(is,65535,NULL);
@ -499,6 +507,7 @@ int intsetTest(int argc, char **argv) {
assert(intsetFind(is,-4294967295)); assert(intsetFind(is,-4294967295));
checkConsistency(is); checkConsistency(is);
ok(); ok();
zfree(is);
} }
printf("Stress lookups: "); { printf("Stress lookups: "); {
@ -512,6 +521,7 @@ int intsetTest(int argc, char **argv) {
for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1<<bits)-1),NULL); for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1<<bits)-1),NULL);
printf("%ld lookups, %ld element set, %lldusec\n", printf("%ld lookups, %ld element set, %lldusec\n",
num,size,usec()-start); num,size,usec()-start);
zfree(is);
} }
printf("Stress add+delete: "); { printf("Stress add+delete: "); {
@ -528,6 +538,7 @@ int intsetTest(int argc, char **argv) {
} }
checkConsistency(is); checkConsistency(is);
ok(); ok();
zfree(is);
} }
return 0; return 0;

View File

@ -61,7 +61,7 @@ size_t intsetBlobLen(intset *is);
int intsetValidateIntegrity(const unsigned char *is, size_t size, int deep); int intsetValidateIntegrity(const unsigned char *is, size_t size, int deep);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int intsetTest(int argc, char *argv[]); int intsetTest(int argc, char *argv[], int accurate);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -256,7 +256,7 @@ sds createLatencyReport(void) {
if (dictSize(g_pserver->latency_events) == 0 && if (dictSize(g_pserver->latency_events) == 0 &&
g_pserver->latency_monitor_threshold == 0) g_pserver->latency_monitor_threshold == 0)
{ {
report = sdscat(report,"I'm sorry, Dave, I can't do that. Latency monitoring is disabled in this KeyDB instance. You may use \"CONFIG SET latency-monitor-threshold <milliseconds>.\" in order to enable it. If we weren't in a deep space mission I'd suggest to take a look at http://redis.io/topics/latency-monitor.\n"); report = sdscat(report,"I'm sorry, Dave, I can't do that. Latency monitoring is disabled in this KeyDB instance. You may use \"CONFIG SET latency-monitor-threshold <milliseconds>.\" in order to enable it. If we weren't in a deep space mission I'd suggest to take a look at https://redis.io/topics/latency-monitor.\n");
return report; return report;
} }
@ -426,7 +426,7 @@ sds createLatencyReport(void) {
} }
if (advise_slowlog_inspect) { if (advise_slowlog_inspect) {
report = sdscat(report,"- Check your Slow Log to understand what are the commands you are running which are too slow to execute. Please check http://redis.io/commands/slowlog for more information.\n"); report = sdscat(report,"- Check your Slow Log to understand what are the commands you are running which are too slow to execute. Please check https://redis.io/commands/slowlog for more information.\n");
} }
/* Intrinsic latency. */ /* Intrinsic latency. */

View File

@ -908,7 +908,7 @@ int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes) {
#undef OUT_OF_RANGE #undef OUT_OF_RANGE
} }
/* Validate the integrity of the data stracture. /* Validate the integrity of the data structure.
* when `deep` is 0, only the integrity of the header is validated. * when `deep` is 0, only the integrity of the header is validated.
* when `deep` is 1, we scan all the entries one by one. */ * when `deep` is 1, we scan all the entries one by one. */
int lpValidateIntegrity(unsigned char *lp, size_t size, int deep){ int lpValidateIntegrity(unsigned char *lp, size_t size, int deep){

View File

@ -27,6 +27,30 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
/* --------------------------------------------------------------------------
* Modules API documentation information
*
* The comments in this file are used to generate the API documentation on the
* Redis website.
*
* Each function starting with RM_ and preceded by a block comment is included
* in the API documentation. To hide an RM_ function, put a blank line between
* the comment and the function definition or put the comment inside the
* function body.
*
* The functions are divided into sections. Each section is preceded by a
* documentation block, which is comment block starting with a markdown level 2
* heading, i.e. a line starting with ##, on the first line of the comment block
* (with the exception of a ----- line which can appear first). Other comment
* blocks, which are not intended for the modules API user, such as this comment
* block, do NOT start with a markdown level 2 heading, so they are included in
* the generated a API documentation.
*
* The documentation comments may contain markdown formatting. Some automatic
* replacements are done, such as the replacement of RM with RedisModule in
* function names. For details, see the script src/modules/gendoc.rb.
* -------------------------------------------------------------------------- */
#include "server.h" #include "server.h"
#include "cluster.h" #include "cluster.h"
#include "slowlog.h" #include "slowlog.h"
@ -177,6 +201,7 @@ typedef struct RedisModuleCtx RedisModuleCtx;
#define REDISMODULE_CTX_THREAD_SAFE (1<<4) #define REDISMODULE_CTX_THREAD_SAFE (1<<4)
#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<5) #define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<5)
#define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<6) #define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<6)
#define REDISMODULE_CTX_MULTI_EMITTED (1<<7)
/* This represents a Redis key opened with RM_OpenKey(). */ /* This represents a Redis key opened with RM_OpenKey(). */
struct RedisModuleKey { struct RedisModuleKey {
@ -411,7 +436,10 @@ void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d);
void RM_FreeServerInfo(RedisModuleCtx *ctx, RedisModuleServerInfoData *data); void RM_FreeServerInfo(RedisModuleCtx *ctx, RedisModuleServerInfoData *data);
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Heap allocation raw functions * ## Heap allocation raw functions
*
* Memory allocated with these functions are taken into account by Redis key
* eviction algorithms and are reported in Redis memory usage information.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Use like malloc(). Memory allocated with this function is reported in /* Use like malloc(). Memory allocated with this function is reported in
@ -594,13 +622,13 @@ int moduleDelKeyIfEmpty(RedisModuleKey *key) {
* defined in the main executable having the same names. * defined in the main executable having the same names.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Lookup the requested module API and store the function pointer into the int RM_GetApi(const char *funcname, void **targetPtrPtr) {
/* Lookup the requested module API and store the function pointer into the
* target pointer. The function returns REDISMODULE_ERR if there is no such * target pointer. The function returns REDISMODULE_ERR if there is no such
* named API, otherwise REDISMODULE_OK. * named API, otherwise REDISMODULE_OK.
* *
* This function is not meant to be used by modules developer, it is only * This function is not meant to be used by modules developer, it is only
* used implicitly by including redismodule.h. */ * used implicitly by including redismodule.h. */
int RM_GetApi(const char *funcname, void **targetPtrPtr) {
dictEntry *he = dictFind(g_pserver->moduleapi, funcname); dictEntry *he = dictFind(g_pserver->moduleapi, funcname);
if (!he) return REDISMODULE_ERR; if (!he) return REDISMODULE_ERR;
*targetPtrPtr = dictGetVal(he); *targetPtrPtr = dictGetVal(he);
@ -615,17 +643,21 @@ void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
/* We don't need to do anything here if the context was never used /* We don't need to do anything here if the context was never used
* in order to propagate commands. */ * in order to propagate commands. */
if (!(ctx->flags & REDISMODULE_CTX_MULTI_EMITTED)) return;
/* We don't need to do anything here if the server isn't inside
* a transaction. */
if (!g_pserver->propagate_in_transaction) return; if (!g_pserver->propagate_in_transaction) return;
/* If this command is executed from with Lua or MULTI/EXEC we do noy /* If this command is executed from with Lua or MULTI/EXEC we do not
* need to propagate EXEC */ * need to propagate EXEC */
if (serverTL->in_eval || serverTL->in_exec) return; if (serverTL->in_eval || serverTL->in_exec) return;
/* Handle the replication of the final EXEC, since whatever a command /* Handle the replication of the final EXEC, since whatever a command
* emits is always wrapped around MULTI/EXEC. */ * emits is always wrapped around MULTI/EXEC. */
beforePropagateMultiOrExec(0);
alsoPropagate(cserver.execCommand,c->db->id,&shared.exec,1, alsoPropagate(cserver.execCommand,c->db->id,&shared.exec,1,
PROPAGATE_AOF|PROPAGATE_REPL); PROPAGATE_AOF|PROPAGATE_REPL);
afterPropagateExec();
/* If this is not a module command context (but is instead a simple /* If this is not a module command context (but is instead a simple
* callback context), we have to handle directly the "also propagate" * callback context), we have to handle directly the "also propagate"
@ -724,6 +756,14 @@ int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc,
return result->numkeys; return result->numkeys;
} }
/* --------------------------------------------------------------------------
* ## Commands API
*
* These functions are used to implement custom Redis commands.
*
* For examples, see https://redis.io/topics/modules-intro.
* -------------------------------------------------------------------------- */
/* Return non-zero if a module command, that was declared with the /* Return non-zero if a module command, that was declared with the
* flag "getkeys-api", is called in a special way to get the keys positions * flag "getkeys-api", is called in a special way to get the keys positions
* and not to get executed. Otherwise zero is returned. */ * and not to get executed. Otherwise zero is returned. */
@ -898,11 +938,15 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Called by RM_Init() to setup the `ctx->module` structure. /* --------------------------------------------------------------------------
* ## Module information and time measurement
* -------------------------------------------------------------------------- */
void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
/* Called by RM_Init() to setup the `ctx->module` structure.
* *
* This is an internal function, Redis modules developers don't need * This is an internal function, Redis modules developers don't need
* to use it. */ * to use it. */
void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
RedisModule *module; RedisModule *module;
if (ctx->module != NULL) return; if (ctx->module != NULL) return;
@ -968,20 +1012,29 @@ int RM_BlockedClientMeasureTimeEnd(RedisModuleBlockedClient *bc) {
* repl-diskless-load to work if enabled. * repl-diskless-load to work if enabled.
* The module should use RedisModule_IsIOError after reads, before using the * The module should use RedisModule_IsIOError after reads, before using the
* data that was read, and in case of error, propagate it upwards, and also be * data that was read, and in case of error, propagate it upwards, and also be
* able to release the partially populated value and all it's allocations. */ * able to release the partially populated value and all it's allocations.
*
* REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED:
* See RM_SignalModifiedKey().
*/
void RM_SetModuleOptions(RedisModuleCtx *ctx, int options) { void RM_SetModuleOptions(RedisModuleCtx *ctx, int options) {
ctx->module->options = options; ctx->module->options = options;
} }
/* Signals that the key is modified from user's perspective (i.e. invalidate WATCH /* Signals that the key is modified from user's perspective (i.e. invalidate WATCH
* and client side caching). */ * and client side caching).
*
* This is done automatically when a key opened for writing is closed, unless
* the option REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED has been set using
* RM_SetModuleOptions().
*/
int RM_SignalModifiedKey(RedisModuleCtx *ctx, RedisModuleString *keyname) { int RM_SignalModifiedKey(RedisModuleCtx *ctx, RedisModuleString *keyname) {
signalModifiedKey(ctx->client,ctx->client->db,keyname); signalModifiedKey(ctx->client,ctx->client->db,keyname);
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Automatic memory management for modules * ## Automatic memory management for modules
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Enable automatic memory management. /* Enable automatic memory management.
@ -1077,7 +1130,7 @@ void autoMemoryCollect(RedisModuleCtx *ctx) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* String objects APIs * ## String objects APIs
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Create a new module string object. The returned string must be freed /* Create a new module string object. The returned string must be freed
@ -1346,14 +1399,6 @@ int RM_StringToLongDouble(const RedisModuleString *str, long double *ld) {
* Returns REDISMODULE_OK on success and returns REDISMODULE_ERR if the string * Returns REDISMODULE_OK on success and returns REDISMODULE_ERR if the string
* is not a valid string representation of a stream ID. The special IDs "+" and * is not a valid string representation of a stream ID. The special IDs "+" and
* "-" are allowed. * "-" are allowed.
*
* RedisModuleStreamID is a struct with two 64-bit fields, which is used in
* stream functions and defined as
*
* typedef struct RedisModuleStreamID {
* uint64_t ms;
* uint64_t seq;
* } RedisModuleStreamID;
*/ */
int RM_StringToStreamID(const RedisModuleString *str, RedisModuleStreamID *id) { int RM_StringToStreamID(const RedisModuleString *str, RedisModuleStreamID *id) {
streamID streamid; streamID streamid;
@ -1408,13 +1453,15 @@ int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const cha
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Reply APIs * ## Reply APIs
*
* These functions are used for sending replies to the client.
* *
* Most functions always return REDISMODULE_OK so you can use it with * Most functions always return REDISMODULE_OK so you can use it with
* 'return' in order to return from the command implementation with: * 'return' in order to return from the command implementation with:
* *
* if (... some condition ...) * if (... some condition ...)
* return RM_ReplyWithLongLong(ctx,mycount); * return RedisModule_ReplyWithLongLong(ctx,mycount);
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Send an error about the number of arguments given to the command, /* Send an error about the number of arguments given to the command,
@ -1746,7 +1793,7 @@ int RM_ReplyWithLongDouble(RedisModuleCtx *ctx, long double ld) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Commands replication API * ## Commands replication API
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Helper function to replicate MULTI the first time we replicate something /* Helper function to replicate MULTI the first time we replicate something
@ -1755,7 +1802,7 @@ int RM_ReplyWithLongDouble(RedisModuleCtx *ctx, long double ld) {
void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) { void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
/* Skip this if client explicitly wrap the command with MULTI, or if /* Skip this if client explicitly wrap the command with MULTI, or if
* the module command was called by a script. */ * the module command was called by a script. */
if (g_pserver->lua_caller || serverTL->in_exec) return; if (serverTL->in_eval || serverTL->in_exec) return;
/* If we already emitted MULTI return ASAP. */ /* If we already emitted MULTI return ASAP. */
if (g_pserver->propagate_in_transaction) return; if (g_pserver->propagate_in_transaction) return;
/* If this is a thread safe context, we do not want to wrap commands /* If this is a thread safe context, we do not want to wrap commands
@ -1766,10 +1813,12 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
* context, we have to setup the op array for the "also propagate" API * context, we have to setup the op array for the "also propagate" API
* so that RM_Replicate() will work. */ * so that RM_Replicate() will work. */
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL)) { if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL)) {
serverAssert(ctx->saved_oparray.ops == NULL);
ctx->saved_oparray = g_pserver->also_propagate; ctx->saved_oparray = g_pserver->also_propagate;
redisOpArrayInit(&g_pserver->also_propagate); redisOpArrayInit(&g_pserver->also_propagate);
} }
execCommandPropagateMulti(ctx->client->db->id); execCommandPropagateMulti(ctx->client->db->id);
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
} }
/* Replicate the specified command and arguments to slaves and AOF, as effect /* Replicate the specified command and arguments to slaves and AOF, as effect
@ -1793,7 +1842,7 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
* the AOF or the replicas from the propagation of the specified command. * the AOF or the replicas from the propagation of the specified command.
* Otherwise, by default, the command will be propagated in both channels. * Otherwise, by default, the command will be propagated in both channels.
* *
* ## Note about calling this function from a thread safe context: * #### Note about calling this function from a thread safe context:
* *
* Normally when you call this function from the callback implementing a * Normally when you call this function from the callback implementing a
* module command, or any other callback provided by the Redis Module API, * module command, or any other callback provided by the Redis Module API,
@ -1805,7 +1854,7 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
* and the command specified is inserted in the AOF and replication stream * and the command specified is inserted in the AOF and replication stream
* immediately. * immediately.
* *
* ## Return value * #### Return value
* *
* The command returns REDISMODULE_ERR if the format specifiers are invalid * The command returns REDISMODULE_ERR if the format specifiers are invalid
* or the command name does not belong to a known command. */ * or the command name does not belong to a known command. */
@ -1869,7 +1918,7 @@ int RM_ReplicateVerbatim(RedisModuleCtx *ctx) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* DB and Key APIs -- Generic API * ## DB and Key APIs -- Generic API
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Return the ID of the current client calling the currently active module /* Return the ID of the current client calling the currently active module
@ -2145,7 +2194,7 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
flags |= REDISMODULE_CTX_FLAGS_LOADING; flags |= REDISMODULE_CTX_FLAGS_LOADING;
/* Maxmemory and eviction policy */ /* Maxmemory and eviction policy */
if (g_pserver->maxmemory > 0) { if (g_pserver->maxmemory > 0 && (!listLength(g_pserver->masters) || !g_pserver->repl_slave_ignore_maxmemory)) {
flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY; flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
if (g_pserver->maxmemory_policy != MAXMEMORY_NO_EVICTION) if (g_pserver->maxmemory_policy != MAXMEMORY_NO_EVICTION)
@ -2400,7 +2449,7 @@ mstime_t RM_GetExpire(RedisModuleKey *key) {
* The function returns REDISMODULE_OK on success or REDISMODULE_ERR if * The function returns REDISMODULE_OK on success or REDISMODULE_ERR if
* the key was not open for writing or is an empty key. */ * the key was not open for writing or is an empty key. */
int RM_SetExpire(RedisModuleKey *key, mstime_t expire) { int RM_SetExpire(RedisModuleKey *key, mstime_t expire) {
if (!(key->mode & REDISMODULE_WRITE) || key->value == NULL) if (!(key->mode & REDISMODULE_WRITE) || key->value == NULL || (expire < 0 && expire != REDISMODULE_NO_EXPIRE))
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (expire != REDISMODULE_NO_EXPIRE) { if (expire != REDISMODULE_NO_EXPIRE) {
expire += mstime(); expire += mstime();
@ -2411,6 +2460,36 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) {
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Return the key expire value, as absolute Unix timestamp.
* If no TTL is associated with the key or if the key is empty,
* REDISMODULE_NO_EXPIRE is returned. */
mstime_t RM_GetAbsExpire(RedisModuleKey *key) {
auto expire = getExpire(key->db,key->key);
if (expire == nullptr || key->value == NULL)
return REDISMODULE_NO_EXPIRE;
return expire->when();
}
/* Set a new expire for the key. If the special expire
* REDISMODULE_NO_EXPIRE is set, the expire is cancelled if there was
* one (the same as the PERSIST command).
*
* Note that the expire must be provided as a positive integer representing
* the absolute Unix timestamp the key should have.
*
* The function returns REDISMODULE_OK on success or REDISMODULE_ERR if
* the key was not open for writing or is an empty key. */
int RM_SetAbsExpire(RedisModuleKey *key, mstime_t expire) {
if (!(key->mode & REDISMODULE_WRITE) || key->value == NULL || (expire < 0 && expire != REDISMODULE_NO_EXPIRE))
return REDISMODULE_ERR;
if (expire != REDISMODULE_NO_EXPIRE) {
setExpire(key->ctx->client,key->db,key->key,nullptr/*subkey*/,expire);
} else {
removeExpire(key->db,key->key);
}
return REDISMODULE_OK;
}
/* Performs similar operation to FLUSHALL, and optionally start a new AOF file (if enabled) /* Performs similar operation to FLUSHALL, and optionally start a new AOF file (if enabled)
* If restart_aof is true, you must make sure the command that triggered this call is not * If restart_aof is true, you must make sure the command that triggered this call is not
* propagated to the AOF file. * propagated to the AOF file.
@ -2434,7 +2513,9 @@ RedisModuleString *RM_RandomKey(RedisModuleCtx *ctx) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Key API for String type * ## Key API for String type
*
* See also RM_ValueLength(), which returns the length of a string.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* If the key is open for writing, set the specified string 'str' as the /* If the key is open for writing, set the specified string 'str' as the
@ -2544,7 +2625,9 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Key API for List type * ## Key API for List type
*
* See also RM_ValueLength(), which returns the length of a list.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Push an element into a list, on head or tail depending on 'where' argument. /* Push an element into a list, on head or tail depending on 'where' argument.
@ -2582,26 +2665,28 @@ RedisModuleString *RM_ListPop(RedisModuleKey *key, int where) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Key API for Sorted Set type * ## Key API for Sorted Set type
*
* See also RM_ValueLength(), which returns the length of a sorted set.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Conversion from/to public flags of the Modules API and our private flags, /* Conversion from/to public flags of the Modules API and our private flags,
* so that we have everything decoupled. */ * so that we have everything decoupled. */
int moduleZsetAddFlagsToCoreFlags(int flags) { int moduleZsetAddFlagsToCoreFlags(int flags) {
int retflags = 0; int retflags = 0;
if (flags & REDISMODULE_ZADD_XX) retflags |= ZADD_XX; if (flags & REDISMODULE_ZADD_XX) retflags |= ZADD_IN_XX;
if (flags & REDISMODULE_ZADD_NX) retflags |= ZADD_NX; if (flags & REDISMODULE_ZADD_NX) retflags |= ZADD_IN_NX;
if (flags & REDISMODULE_ZADD_GT) retflags |= ZADD_GT; if (flags & REDISMODULE_ZADD_GT) retflags |= ZADD_IN_GT;
if (flags & REDISMODULE_ZADD_LT) retflags |= ZADD_LT; if (flags & REDISMODULE_ZADD_LT) retflags |= ZADD_IN_LT;
return retflags; return retflags;
} }
/* See previous function comment. */ /* See previous function comment. */
int moduleZsetAddFlagsFromCoreFlags(int flags) { int moduleZsetAddFlagsFromCoreFlags(int flags) {
int retflags = 0; int retflags = 0;
if (flags & ZADD_ADDED) retflags |= REDISMODULE_ZADD_ADDED; if (flags & ZADD_OUT_ADDED) retflags |= REDISMODULE_ZADD_ADDED;
if (flags & ZADD_UPDATED) retflags |= REDISMODULE_ZADD_UPDATED; if (flags & ZADD_OUT_UPDATED) retflags |= REDISMODULE_ZADD_UPDATED;
if (flags & ZADD_NOP) retflags |= REDISMODULE_ZADD_NOP; if (flags & ZADD_OUT_NOP) retflags |= REDISMODULE_ZADD_NOP;
return retflags; return retflags;
} }
@ -2638,16 +2723,16 @@ int moduleZsetAddFlagsFromCoreFlags(int flags) {
* * 'score' double value is not a number (NaN). * * 'score' double value is not a number (NaN).
*/ */
int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr) { int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr) {
int flags = 0; int in_flags = 0, out_flags = 0;
if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR; if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR;
if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_ZSET); if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_ZSET);
if (flagsptr) flags = moduleZsetAddFlagsToCoreFlags(*flagsptr); if (flagsptr) in_flags = moduleZsetAddFlagsToCoreFlags(*flagsptr);
if (zsetAdd(key->value,score,szFromObj(ele),&flags,NULL) == 0) { if (zsetAdd(key->value,score,szFromObj(ele),in_flags,&out_flags,NULL) == 0) {
if (flagsptr) *flagsptr = 0; if (flagsptr) *flagsptr = 0;
return REDISMODULE_ERR; return REDISMODULE_ERR;
} }
if (flagsptr) *flagsptr = moduleZsetAddFlagsFromCoreFlags(flags); if (flagsptr) *flagsptr = moduleZsetAddFlagsFromCoreFlags(out_flags);
return REDISMODULE_OK; return REDISMODULE_OK;
} }
@ -2665,22 +2750,17 @@ int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *f
* with the new score of the element after the increment, if no error * with the new score of the element after the increment, if no error
* is returned. */ * is returned. */
int RM_ZsetIncrby(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore) { int RM_ZsetIncrby(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore) {
int flags = 0; int in_flags = 0, out_flags = 0;
if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR; if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR;
if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_ZSET); if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_ZSET);
if (flagsptr) flags = moduleZsetAddFlagsToCoreFlags(*flagsptr); if (flagsptr) in_flags = moduleZsetAddFlagsToCoreFlags(*flagsptr);
flags |= ZADD_INCR; in_flags |= ZADD_IN_INCR;
if (zsetAdd(key->value,score,szFromObj(ele),&flags,newscore) == 0) { if (zsetAdd(key->value,score,szFromObj(ele),in_flags,&out_flags,newscore) == 0) {
if (flagsptr) *flagsptr = 0; if (flagsptr) *flagsptr = 0;
return REDISMODULE_ERR; return REDISMODULE_ERR;
} }
/* zsetAdd() may signal back that the resulting score is not a number. */ if (flagsptr) *flagsptr = moduleZsetAddFlagsFromCoreFlags(out_flags);
if (flagsptr && (*flagsptr & ZADD_NAN)) {
*flagsptr = 0;
return REDISMODULE_ERR;
}
if (flagsptr) *flagsptr = moduleZsetAddFlagsFromCoreFlags(flags);
return REDISMODULE_OK; return REDISMODULE_OK;
} }
@ -2730,7 +2810,7 @@ int RM_ZsetScore(RedisModuleKey *key, RedisModuleString *ele, double *score) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Key API for Sorted Set iterator * ## Key API for Sorted Set iterator
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
void zsetKeyReset(RedisModuleKey *key) { void zsetKeyReset(RedisModuleKey *key) {
@ -3037,7 +3117,9 @@ int RM_ZsetRangePrev(RedisModuleKey *key) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Key API for Hash type * ## Key API for Hash type
*
* See also RM_ValueLength(), which returns the number of fields in a hash.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Set the field of the specified hash field to the specified value. /* Set the field of the specified hash field to the specified value.
@ -3272,7 +3354,20 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Key API for the stream type. * ## Key API for Stream type
*
* For an introduction to streams, see https://redis.io/topics/streams-intro.
*
* The type RedisModuleStreamID, which is used in stream functions, is a struct
* with two 64-bit fields and is defined as
*
* typedef struct RedisModuleStreamID {
* uint64_t ms;
* uint64_t seq;
* } RedisModuleStreamID;
*
* See also RM_ValueLength(), which returns the length of a stream, and the
* conversion functions RM_StringToStreamID() and RM_CreateStringFromStreamID().
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Adds an entry to a stream. Like XADD without trimming. /* Adds an entry to a stream. Like XADD without trimming.
@ -3439,8 +3534,8 @@ int RM_StreamDelete(RedisModuleKey *key, RedisModuleStreamID *id) {
* // * //
* // ... Do stuff ... * // ... Do stuff ...
* // * //
* RedisModule_Free(field); * RedisModule_FreeString(ctx, field);
* RedisModule_Free(value); * RedisModule_FreeString(ctx, value);
* } * }
* } * }
* RedisModule_StreamIteratorStop(key); * RedisModule_StreamIteratorStop(key);
@ -3721,7 +3816,9 @@ long long RM_StreamTrimByID(RedisModuleKey *key, int flags, RedisModuleStreamID
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Redis <-> Modules generic Call() API * ## Calling Redis commands from modules
*
* RM_Call() sends a command to Redis. The remaining functions handle the reply.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Create a new RedisModuleCallReply object. The processing of the reply /* Create a new RedisModuleCallReply object. The processing of the reply
@ -4073,6 +4170,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
int replicate = 0; /* Replicate this command? */ int replicate = 0; /* Replicate this command? */
int call_flags; int call_flags;
sds proto = nullptr; sds proto = nullptr;
int prev_replication_allowed;
/* Handle arguments. */ /* Handle arguments. */
va_start(ap, fmt); va_start(ap, fmt);
@ -4150,20 +4248,30 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
} }
} }
/* If we are using single commands replication, we need to wrap what /* We need to use a global replication_allowed flag in order to prevent
* we propagate into a MULTI/EXEC block, so that it will be atomic like * replication of nested RM_Calls. Example:
* a Lua script in the context of AOF and slaves. */ * 1. module1.foo does RM_Call of module2.bar without replication (i.e. no '!')
if (replicate) moduleReplicateMultiIfNeeded(ctx); * 2. module2.bar internally calls RM_Call of INCR with '!'
* 3. at the end of module1.foo we call RM_ReplicateVerbatim
* We want the replica/AOF to see only module1.foo and not the INCR from module2.bar */
prev_replication_allowed = g_pserver->replication_allowed;
g_pserver->replication_allowed = replicate && g_pserver->replication_allowed;
/* Run the command */ /* Run the command */
call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_NOWRAP; call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_NOWRAP;
if (replicate) { if (replicate) {
/* If we are using single commands replication, we need to wrap what
* we propagate into a MULTI/EXEC block, so that it will be atomic like
* a Lua script in the context of AOF and slaves. */
moduleReplicateMultiIfNeeded(ctx);
if (!(flags & REDISMODULE_ARGV_NO_AOF)) if (!(flags & REDISMODULE_ARGV_NO_AOF))
call_flags |= CMD_CALL_PROPAGATE_AOF; call_flags |= CMD_CALL_PROPAGATE_AOF;
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS)) if (!(flags & REDISMODULE_ARGV_NO_REPLICAS))
call_flags |= CMD_CALL_PROPAGATE_REPL; call_flags |= CMD_CALL_PROPAGATE_REPL;
} }
call(c,call_flags); call(c,call_flags);
g_pserver->replication_allowed = prev_replication_allowed;
serverAssert((c->flags & CLIENT_BLOCKED) == 0); serverAssert((c->flags & CLIENT_BLOCKED) == 0);
@ -4204,7 +4312,7 @@ const char *RM_CallReplyProto(RedisModuleCallReply *reply, size_t *len) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules data types * ## Modules data types
* *
* When String DMA or using existing data structures is not enough, it is * When String DMA or using existing data structures is not enough, it is
* possible to create new data types from scratch and export them to * possible to create new data types from scratch and export them to
@ -4347,6 +4455,12 @@ void moduleTypeNameByID(char *name, uint64_t moduleid) {
} }
} }
/* Return the name of the module that owns the specified moduleType. */
const char *moduleTypeModuleName(moduleType *mt) {
if (!mt || !mt->module) return NULL;
return mt->module->name;
}
/* Create a copy of a module type value using the copy callback. If failed /* Create a copy of a module type value using the copy callback. If failed
* or not supported, produce an error reply and return NULL. * or not supported, produce an error reply and return NULL.
*/ */
@ -4562,7 +4676,7 @@ void *RM_ModuleTypeGetValue(RedisModuleKey *key) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* RDB loading and saving functions * ## RDB loading and saving functions
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Called when there is a load error in the context of a module. On some /* Called when there is a load error in the context of a module. On some
@ -4878,7 +4992,7 @@ ssize_t rdbSaveModulesAux(rio *rdb, int when) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Key digest API (DEBUG DIGEST interface for modules types) * ## Key digest API (DEBUG DIGEST interface for modules types)
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Add a new element to the digest. This function can be called multiple times /* Add a new element to the digest. This function can be called multiple times
@ -4999,7 +5113,7 @@ RedisModuleString *RM_SaveDataTypeToString(RedisModuleCtx *ctx, void *data, cons
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* AOF API for modules data types * ## AOF API for modules data types
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Emits a command into the AOF during the AOF rewriting process. This function /* Emits a command into the AOF during the AOF rewriting process. This function
@ -5054,7 +5168,7 @@ void RM_EmitAOF(RedisModuleIO *io, const char *cmdname, const char *fmt, ...) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* IO context handling * ## IO context handling
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
RedisModuleCtx *RM_GetContextFromIO(RedisModuleIO *io) { RedisModuleCtx *RM_GetContextFromIO(RedisModuleIO *io) {
@ -5081,7 +5195,7 @@ const RedisModuleString *RM_GetKeyNameFromModuleKey(RedisModuleKey *key) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Logging * ## Logging
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* This is the low level function implementing both: /* This is the low level function implementing both:
@ -5112,10 +5226,10 @@ void moduleLogRaw(RedisModule *module, const char *levelstr, const char *fmt, va
* printf-alike specifiers, while level is a string describing the log * printf-alike specifiers, while level is a string describing the log
* level to use when emitting the log, and must be one of the following: * level to use when emitting the log, and must be one of the following:
* *
* * "debug" * * "debug" (`REDISMODULE_LOGLEVEL_DEBUG`)
* * "verbose" * * "verbose" (`REDISMODULE_LOGLEVEL_VERBOSE`)
* * "notice" * * "notice" (`REDISMODULE_LOGLEVEL_NOTICE`)
* * "warning" * * "warning" (`REDISMODULE_LOGLEVEL_WARNING`)
* *
* If the specified log level is invalid, verbose is used by default. * If the specified log level is invalid, verbose is used by default.
* There is a fixed limit to the length of the log line this function is able * There is a fixed limit to the length of the log line this function is able
@ -5166,7 +5280,10 @@ void RM_LatencyAddSample(const char *event, mstime_t latency) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Blocking clients from modules * ## Blocking clients from modules
*
* For a guide about blocking commands in modules, see
* https://redis.io/topics/modules-blocking-ops.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Readable handler for the awake pipe. We do nothing here, the awake bytes /* Readable handler for the awake pipe. We do nothing here, the awake bytes
@ -5227,11 +5344,6 @@ void unblockClientFromModule(client *c) {
moduleUnblockClient(c); moduleUnblockClient(c);
bc->client = NULL; bc->client = NULL;
/* Reset the client for a new query since, for blocking commands implemented
* into modules, we do not it immediately after the command returns (and
* the client blocks) in order to be still able to access the argument
* vector from callbacks. */
resetClient(c);
} }
/* Block a client in the context of a module: this function implements both /* Block a client in the context of a module: this function implements both
@ -5651,6 +5763,12 @@ void moduleHandleBlockedClients(int iel) {
* API to unblock the client and the memory will be released. */ * API to unblock the client and the memory will be released. */
void moduleBlockedClientTimedOut(client *c) { void moduleBlockedClientTimedOut(client *c) {
RedisModuleBlockedClient *bc = (RedisModuleBlockedClient*)c->bpop.module_blocked_handle; RedisModuleBlockedClient *bc = (RedisModuleBlockedClient*)c->bpop.module_blocked_handle;
/* Protect against re-processing: don't serve clients that are already
* in the unblocking list for any reason (including RM_UnblockClient()
* explicit call). See #6798. */
if (bc->unblocked) return;
RedisModuleCtx ctx = REDISMODULE_CTX_INIT; RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
ctx.flags |= REDISMODULE_CTX_BLOCKED_TIMEOUT; ctx.flags |= REDISMODULE_CTX_BLOCKED_TIMEOUT;
ctx.module = bc->module; ctx.module = bc->module;
@ -5707,7 +5825,7 @@ int RM_BlockedClientDisconnected(RedisModuleCtx *ctx) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Thread Safe Contexts * ## Thread Safe Contexts
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Return a context which can be used inside threads to make Redis context /* Return a context which can be used inside threads to make Redis context
@ -5907,7 +6025,7 @@ int moduleGILAcquiredByModule(void) {
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Module Keyspace Notifications API * ## Module Keyspace Notifications API
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Subscribe to keyspace notifications. This is a low-level version of the /* Subscribe to keyspace notifications. This is a low-level version of the
@ -5931,6 +6049,7 @@ int moduleGILAcquiredByModule(void) {
* - REDISMODULE_NOTIFY_EXPIRED: Expiration events * - REDISMODULE_NOTIFY_EXPIRED: Expiration events
* - REDISMODULE_NOTIFY_EVICTED: Eviction events * - REDISMODULE_NOTIFY_EVICTED: Eviction events
* - REDISMODULE_NOTIFY_STREAM: Stream events * - REDISMODULE_NOTIFY_STREAM: Stream events
* - REDISMODULE_NOTIFY_MODULE: Module types 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, * - REDISMODULE_NOTIFY_LOADED: A special notification available only for modules,
@ -6040,7 +6159,7 @@ void moduleUnsubscribeNotifications(RedisModule *module) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules Cluster API * ## Modules Cluster API
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* The Cluster message callback function pointer type. */ /* The Cluster message callback function pointer type. */
@ -6295,7 +6414,7 @@ void RM_SetClusterFlags(RedisModuleCtx *ctx, uint64_t flags) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules Timers API * ## Modules Timers API
* *
* Module timers are an high precision "green timers" abstraction where * Module timers are an high precision "green timers" abstraction where
* every module can register even millions of timers without problems, even if * every module can register even millions of timers without problems, even if
@ -6485,7 +6604,7 @@ int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remain
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules ACL API * ## Modules ACL API
* *
* Implements a hook into the authentication and authorization within Redis. * Implements a hook into the authentication and authorization within Redis.
* --------------------------------------------------------------------------*/ * --------------------------------------------------------------------------*/
@ -6715,7 +6834,7 @@ RedisModuleString *RM_GetClientCertificate(RedisModuleCtx *ctx, uint64_t client_
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules Dictionary API * ## Modules Dictionary API
* *
* Implements a sorted dictionary (actually backed by a radix tree) with * Implements a sorted dictionary (actually backed by a radix tree) with
* the usual get / set / del / num-items API, together with an iterator * the usual get / set / del / num-items API, together with an iterator
@ -6969,7 +7088,7 @@ int RM_DictCompare(RedisModuleDictIter *di, const char *op, RedisModuleString *k
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules Info fields * ## Modules Info fields
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
int RM_InfoEndDictField(RedisModuleInfoCtx *ctx); int RM_InfoEndDictField(RedisModuleInfoCtx *ctx);
@ -7283,7 +7402,7 @@ double RM_ServerInfoGetFieldDouble(RedisModuleServerInfoData *data, const char*
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules utility APIs * ## Modules utility APIs
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Return random bytes using SHA1 in counter mode with a /dev/urandom /* Return random bytes using SHA1 in counter mode with a /dev/urandom
@ -7302,7 +7421,7 @@ void RM_GetRandomHexChars(char *dst, size_t len) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules API exporting / importing * ## Modules API exporting / importing
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* This function is called by a module in order to export some API with a /* This function is called by a module in order to export some API with a
@ -7439,7 +7558,7 @@ int moduleUnregisterFilters(RedisModule *module) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Module Command Filter API * ## Module Command Filter API
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Register a new command filter function. /* Register a new command filter function.
@ -7652,7 +7771,7 @@ float RM_GetUsedMemoryRatio(){
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Scanning keyspace and hashes * ## Scanning keyspace and hashes
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata); typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata);
@ -7923,7 +8042,7 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Module fork API * ## Module fork API
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Create a background child process with the current frozen snaphost of the /* Create a background child process with the current frozen snaphost of the
@ -7984,7 +8103,7 @@ int TerminateModuleForkChild(int child_pid, int wait) {
serverLog(LL_VERBOSE,"Killing running module fork child: %ld", serverLog(LL_VERBOSE,"Killing running module fork child: %ld",
(long) g_pserver->child_pid); (long) g_pserver->child_pid);
if (kill(g_pserver->child_pid,SIGUSR1) != -1 && wait) { if (kill(g_pserver->child_pid,SIGUSR1) != -1 && wait) {
while(wait4(g_pserver->child_pid,&statloc,0,NULL) != while(waitpid(g_pserver->child_pid,&statloc,0) !=
g_pserver->child_pid); g_pserver->child_pid);
} }
/* Reset the buffer accumulating changes while the child saves. */ /* Reset the buffer accumulating changes while the child saves. */
@ -8017,7 +8136,7 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) {
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Server hooks implementation * ## Server hooks implementation
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Register to be notified, via a callback, when the specified server event /* Register to be notified, via a callback, when the specified server event
@ -8899,6 +9018,10 @@ size_t moduleCount(void) {
return dictSize(modules); return dictSize(modules);
} }
/* --------------------------------------------------------------------------
* ## Key eviction API
* -------------------------------------------------------------------------- */
/* Set the key last access time for LRU based eviction. not relevant if the /* Set the key last access time for LRU based eviction. not relevant if the
* servers's maxmemory policy is LFU based. Value is idle time in milliseconds. * servers's maxmemory policy is LFU based. Value is idle time in milliseconds.
* returns REDISMODULE_OK if the LRU was updated, REDISMODULE_ERR otherwise. */ * returns REDISMODULE_OK if the LRU was updated, REDISMODULE_ERR otherwise. */
@ -8951,6 +9074,10 @@ int RM_GetLFU(RedisModuleKey *key, long long *lfu_freq) {
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* --------------------------------------------------------------------------
* ## Miscellaneous APIs
* -------------------------------------------------------------------------- */
/** /**
* Returns the full ContextFlags mask, using the return value * Returns the full ContextFlags mask, using the return value
* the module can check if a certain set of flags are supported * the module can check if a certain set of flags are supported
@ -9099,6 +9226,10 @@ int *RM_GetCommandKeys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc,
return res; return res;
} }
/* --------------------------------------------------------------------------
* ## Defrag API
* -------------------------------------------------------------------------- */
/* The defrag context, used to manage state during calls to the data type /* The defrag context, used to manage state during calls to the data type
* defrag callback. * defrag callback.
*/ */

View File

@ -1,3 +1,4 @@
# coding: utf-8
# gendoc.rb -- Converts the top-comments inside module.c to modules API # gendoc.rb -- Converts the top-comments inside module.c to modules API
# reference documentation in markdown format. # reference documentation in markdown format.
@ -21,15 +22,48 @@ def markdown(s)
l = l.gsub(/(?<![`A-z])[a-z_]+\(\)/, '`\0`') l = l.gsub(/(?<![`A-z])[a-z_]+\(\)/, '`\0`')
# Add backquotes around macro and var names containing underscores. # Add backquotes around macro and var names containing underscores.
l = l.gsub(/(?<![`A-z\*])[A-Za-z]+_[A-Za-z0-9_]+/){|x| "`#{x}`"} l = l.gsub(/(?<![`A-z\*])[A-Za-z]+_[A-Za-z0-9_]+/){|x| "`#{x}`"}
# Link URLs preceded by space (i.e. when not already linked) # Link URLs preceded by space or newline (not already linked)
l = l.gsub(/ (https?:\/\/[A-Za-z0-9_\/\.\-]+[A-Za-z0-9\/])/, l = l.gsub(/(^| )(https?:\/\/[A-Za-z0-9_\/\.\-]+[A-Za-z0-9\/])/,
' [\1](\1)') '\1[\2](\2)')
# Replace double-dash with unicode ndash
l = l.gsub(/ -- /, ' ')
end end
# Link function names to their definition within the page
l = l.gsub(/`(RedisModule_[A-z0-9]+)[()]*`/) {|x|
$index[$1] ? "[#{x}](\##{$1})" : x
}
newlines << l newlines << l
} }
return newlines.join("\n") return newlines.join("\n")
end end
# Linebreak a prototype longer than 80 characters on the commas, but only
# between balanced parentheses so that we don't linebreak args which are
# function pointers, and then aligning each arg under each other.
def linebreak_proto(proto, indent)
if proto.bytesize <= 80
return proto
end
parts = proto.split(/,\s*/);
if parts.length == 1
return proto;
end
align_pos = proto.index("(") + 1;
align = " " * align_pos
result = parts.shift;
bracket_balance = 0;
parts.each{|part|
if bracket_balance == 0
result += ",\n" + indent + align
else
result += ", "
end
result += part
bracket_balance += part.count("(") - part.count(")")
}
return result;
end
# Given the source code array and the index at which an exported symbol was # Given the source code array and the index at which an exported symbol was
# detected, extracts and outputs the documentation. # detected, extracts and outputs the documentation.
def docufy(src,i) def docufy(src,i)
@ -38,7 +72,11 @@ def docufy(src,i)
name = name.sub("RM_","RedisModule_") name = name.sub("RM_","RedisModule_")
proto = src[i].sub("{","").strip+";\n" proto = src[i].sub("{","").strip+";\n"
proto = proto.sub("RM_","RedisModule_") proto = proto.sub("RM_","RedisModule_")
puts "## `#{name}`\n\n" proto = linebreak_proto(proto, " ");
# Add a link target with the function name. (We don't trust the exact id of
# the generated one, which depends on the Markdown implementation.)
puts "<span id=\"#{name}\"></span>\n\n"
puts "### `#{name}`\n\n"
puts " #{proto}\n" puts " #{proto}\n"
comment = "" comment = ""
while true while true
@ -50,13 +88,87 @@ def docufy(src,i)
puts comment+"\n\n" puts comment+"\n\n"
end end
# Print a comment from line until */ is found, as markdown.
def section_doc(src, i)
name = get_section_heading(src, i)
comment = "<span id=\"#{section_name_to_id(name)}\"></span>\n\n"
while true
# append line, except if it's a horizontal divider
comment = comment + src[i] if src[i] !~ /^[\/ ]?\*{1,2} ?-{50,}/
break if src[i] =~ /\*\//
i = i+1
end
comment = markdown(comment)
puts comment+"\n\n"
end
# generates an id suitable for links within the page
def section_name_to_id(name)
return "section-" +
name.strip.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/^-+|-+$/, '')
end
# Returns the name of the first section heading in the comment block for which
# is_section_doc(src, i) is true
def get_section_heading(src, i)
if src[i] =~ /^\/\*\*? \#+ *(.*)/
heading = $1
elsif src[i+1] =~ /^ ?\* \#+ *(.*)/
heading = $1
end
return heading.gsub(' -- ', ' ')
end
# Returns true if the line is the start of a generic documentation section. Such
# section must start with the # symbol, i.e. a markdown heading, on the first or
# the second line.
def is_section_doc(src, i)
return src[i] =~ /^\/\*\*? \#/ ||
(src[i] =~ /^\/\*/ && src[i+1] =~ /^ ?\* \#/)
end
def is_func_line(src, i)
line = src[i]
return line =~ /RM_/ &&
line[0] != ' ' && line[0] != '#' && line[0] != '/' &&
src[i-1] =~ /\*\//
end
puts "# Modules API reference\n\n" puts "# Modules API reference\n\n"
puts "<!-- This file is generated from module.c using gendoc.rb -->\n\n" puts "<!-- This file is generated from module.c using gendoc.rb -->\n\n"
src = File.open("../module.c").to_a src = File.open(File.dirname(__FILE__) ++ "/../module.c").to_a
src.each_with_index{|line,i|
if line =~ /RM_/ && line[0] != ' ' && line[0] != '#' && line[0] != '/' # Build function index
if src[i-1] =~ /\*\// $index = {}
docufy(src,i) src.each_with_index do |line,i|
if is_func_line(src, i)
line =~ /RM_([A-z0-9]+)/
name = "RedisModule_#{$1}"
$index[name] = true
end end
end
# Print TOC
puts "## Sections\n\n"
src.each_with_index do |_line,i|
if is_section_doc(src, i)
name = get_section_heading(src, i)
puts "* [#{name}](\##{section_name_to_id(name)})\n"
end end
} end
puts "* [Function index](#section-function-index)\n\n"
# Docufy: Print function prototype and markdown docs
src.each_with_index do |_line,i|
if is_func_line(src, i)
docufy(src, i)
elsif is_section_doc(src, i)
section_doc(src, i)
end
end
# Print function index
puts "<span id=\"section-function-index\"></span>\n\n"
puts "## Function index\n\n"
$index.keys.sort.each{|x| puts "* [`#{x}`](\##{x})\n"}
puts "\n"

View File

@ -116,34 +116,34 @@ void discardCommand(client *c) {
addReply(c,shared.ok); addReply(c,shared.ok);
} }
void beforePropagateMultiOrExec(int multi) { void beforePropagateMulti() {
if (multi) {
/* Propagating MULTI */ /* Propagating MULTI */
serverAssert(!g_pserver->propagate_in_transaction); serverAssert(!g_pserver->propagate_in_transaction);
g_pserver->propagate_in_transaction = 1; g_pserver->propagate_in_transaction = 1;
} else { }
void afterPropagateExec() {
/* Propagating EXEC */ /* Propagating EXEC */
serverAssert(g_pserver->propagate_in_transaction == 1); serverAssert(g_pserver->propagate_in_transaction == 1);
g_pserver->propagate_in_transaction = 0; g_pserver->propagate_in_transaction = 0;
}
} }
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand /* Send a MULTI command to all the slaves and AOF file. Check the execCommand
* implementation for more information. */ * implementation for more information. */
void execCommandPropagateMulti(int dbid) { void execCommandPropagateMulti(int dbid) {
beforePropagateMultiOrExec(1); beforePropagateMulti();
propagate(cserver.multiCommand,dbid,&shared.multi,1, propagate(cserver.multiCommand,dbid,&shared.multi,1,
PROPAGATE_AOF|PROPAGATE_REPL); PROPAGATE_AOF|PROPAGATE_REPL);
} }
void execCommandPropagateExec(int dbid) { void execCommandPropagateExec(int dbid) {
beforePropagateMultiOrExec(0);
propagate(cserver.execCommand,dbid,&shared.exec,1, propagate(cserver.execCommand,dbid,&shared.exec,1,
PROPAGATE_AOF|PROPAGATE_REPL); PROPAGATE_AOF|PROPAGATE_REPL);
afterPropagateExec();
} }
/* Aborts a transaction, with a specific error message. /* Aborts a transaction, with a specific error message.
* The transaction is always aboarted with -EXECABORT so that the client knows * The transaction is always aborted with -EXECABORT so that the client knows
* the server exited the multi state, but the actual reason for the abort is * the server exited the multi state, but the actual reason for the abort is
* included too. * included too.
* Note: 'error' may or may not end with \r\n. see addReplyErrorFormat. */ * Note: 'error' may or may not end with \r\n. see addReplyErrorFormat. */
@ -206,11 +206,9 @@ void execCommand(client *c) {
c->cmd = c->mstate.commands[j].cmd; c->cmd = c->mstate.commands[j].cmd;
/* ACL permissions are also checked at the time of execution in case /* ACL permissions are also checked at the time of execution in case
* they were changed after the commands were ququed. */ * they were changed after the commands were queued. */
int acl_errpos; int acl_errpos;
int acl_retval = ACLCheckCommandPerm(c,&acl_errpos); int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
if (acl_retval == ACL_OK && c->cmd->proc == publishCommand)
acl_retval = ACLCheckPubsubPerm(c,1,1,0,&acl_errpos);
if (acl_retval != ACL_OK) { if (acl_retval != ACL_OK) {
const char *reason; const char *reason;
switch (acl_retval) { switch (acl_retval) {
@ -221,7 +219,8 @@ void execCommand(client *c) {
reason = "no permission to touch the specified keys"; reason = "no permission to touch the specified keys";
break; break;
case ACL_DENIED_CHANNEL: case ACL_DENIED_CHANNEL:
reason = "no permission to publish to the specified channel"; reason = "no permission to access one of the channels used "
"as arguments";
break; break;
default: default:
reason = "no permission"; reason = "no permission";
@ -261,7 +260,6 @@ void execCommand(client *c) {
if (g_pserver->propagate_in_transaction) { if (g_pserver->propagate_in_transaction) {
int is_master = listLength(g_pserver->masters) == 0; int is_master = listLength(g_pserver->masters) == 0;
g_pserver->dirty++; g_pserver->dirty++;
beforePropagateMultiOrExec(0);
/* If inside the MULTI/EXEC block this instance was suddenly /* If inside the MULTI/EXEC block this instance was suddenly
* switched from master to replica (using the SLAVEOF command), the * switched from master to replica (using the SLAVEOF command), the
* initial MULTI was propagated into the replication backlog, but the * initial MULTI was propagated into the replication backlog, but the
@ -271,6 +269,7 @@ void execCommand(client *c) {
const char *execcmd = "*1\r\n$4\r\nEXEC\r\n"; const char *execcmd = "*1\r\n$4\r\nEXEC\r\n";
feedReplicationBacklog(execcmd,strlen(execcmd)); feedReplicationBacklog(execcmd,strlen(execcmd));
} }
afterPropagateExec();
} }
serverTL->in_exec = 0; serverTL->in_exec = 0;

View File

@ -169,6 +169,7 @@ client *createClient(connection *conn, int iel) {
c->read_reploff = 0; c->read_reploff = 0;
c->repl_ack_off = 0; c->repl_ack_off = 0;
c->repl_ack_time = 0; c->repl_ack_time = 0;
c->repl_last_partial_write = 0;
c->slave_listening_port = 0; c->slave_listening_port = 0;
c->slave_addr = NULL; c->slave_addr = NULL;
c->slave_capa = SLAVE_CAPA_NONE; c->slave_capa = SLAVE_CAPA_NONE;
@ -404,8 +405,9 @@ void _addReplyProtoToList(client *c, const char *s, size_t len) {
memcpy(tail->buf(), s, len); memcpy(tail->buf(), s, len);
listAddNodeTail(c->reply, tail); listAddNodeTail(c->reply, tail);
c->reply_bytes += tail->size; c->reply_bytes += tail->size;
}
asyncCloseClientOnOutputBufferLimitReached(c); asyncCloseClientOnOutputBufferLimitReached(c);
}
} }
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
@ -684,7 +686,7 @@ void *addReplyDeferredLen(client *c) {
void setDeferredReply(client *c, void *node, const char *s, size_t length) { void setDeferredReply(client *c, void *node, const char *s, size_t length) {
listNode *ln = (listNode*)node; listNode *ln = (listNode*)node;
clientReplyBlock *next; clientReplyBlock *next, *prev;
/* Abort when *node is NULL: when the client should not accept writes /* Abort when *node is NULL: when the client should not accept writes
* we return NULL in addReplyDeferredLen() */ * we return NULL in addReplyDeferredLen() */
@ -693,14 +695,31 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
/* Normally we fill this dummy NULL node, added by addReplyDeferredLen(), /* Normally we fill this dummy NULL node, added by addReplyDeferredLen(),
* with a new buffer structure containing the protocol needed to specify * with a new buffer structure containing the protocol needed to specify
* the length of the array following. However sometimes when there is * the length of the array following. However sometimes there might be room
* little memory to move, we may instead remove this NULL node, and prefix * in the previous/next node so we can instead remove this NULL node, and
* our protocol in the node immediately after to it, in order to save a * suffix/prefix our data in the node immediately before/after it, in order
* write(2) syscall later. Conditions needed to do it: * to save a write(2) syscall later. Conditions needed to do it:
* *
* - The prev node is non-NULL and has space in it or
* - The next node is non-NULL, * - The next node is non-NULL,
* - It has enough room already allocated * - It has enough room already allocated
* - And not too large (avoid large memmove) */ * - And not too large (avoid large memmove) */
if (ln->prev != NULL && (prev = (clientReplyBlock*)listNodeValue(ln->prev)) &&
prev->size - prev->used > 0)
{
size_t len_to_copy = prev->size - prev->used;
if (len_to_copy > length)
len_to_copy = length;
memcpy(prev->buf() + prev->used, s, len_to_copy);
prev->used += len_to_copy;
length -= len_to_copy;
if (length == 0) {
listDelNode(c->reply, ln);
return;
}
s += len_to_copy;
}
if (ln->next != NULL && (next = (clientReplyBlock*)listNodeValue(ln->next)) && if (ln->next != NULL && (next = (clientReplyBlock*)listNodeValue(ln->next)) &&
next->size - next->used >= length && next->size - next->used >= length &&
next->used < PROTO_REPLY_CHUNK_BYTES * 4) next->used < PROTO_REPLY_CHUNK_BYTES * 4)
@ -718,8 +737,9 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
memcpy(buf->buf(), s, length); memcpy(buf->buf(), s, length);
listNodeValue(ln) = buf; listNodeValue(ln) = buf;
c->reply_bytes += buf->size; c->reply_bytes += buf->size;
}
asyncCloseClientOnOutputBufferLimitReached(c); asyncCloseClientOnOutputBufferLimitReached(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. */
@ -1795,9 +1815,7 @@ int writeToClient(client *c, int handler_installed) {
g_pserver->stat_net_output_bytes += totwritten; g_pserver->stat_net_output_bytes += totwritten;
if (nwritten == -1) { if (nwritten == -1) {
if (connGetState(c->conn) == CONN_STATE_CONNECTED) { if (connGetState(c->conn) != CONN_STATE_CONNECTED) {
nwritten = 0;
} else {
serverLog(LL_VERBOSE, serverLog(LL_VERBOSE,
"Error writing to client: %s", connGetLastError(c->conn)); "Error writing to client: %s", connGetLastError(c->conn));
freeClientAsync(c); freeClientAsync(c);
@ -2022,6 +2040,9 @@ void resetClient(client *c) {
c->flags |= CLIENT_REPLY_SKIP; c->flags |= CLIENT_REPLY_SKIP;
c->flags &= ~CLIENT_REPLY_SKIP_NEXT; c->flags &= ~CLIENT_REPLY_SKIP_NEXT;
} }
/* Always clear the prevent logging field. */
c->flags &= ~CLIENT_PREVENT_LOGGING;
} }
/* This function is used when we want to re-enter the event loop but there /* This function is used when we want to re-enter the event loop but there
@ -2333,13 +2354,10 @@ void commandProcessed(client *c, int flags) {
c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos; c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos;
} }
/* Don't reset the client structure for clients blocked in a /* Don't reset the client structure for blocked clients, so that the reply
* module blocking command, so that the reply callback will * callback will still be able to access the client argv and argc fields.
* still be able to access the client argv and argc field. * The client will be reset in unblockClient(). */
* The client will be reset in unblockClientFromModule(). */ if (!(c->flags & CLIENT_BLOCKED)) {
if (!(c->flags & CLIENT_BLOCKED) ||
(c->btype != BLOCKED_MODULE && c->btype != BLOCKED_PAUSE))
{
resetClient(c); resetClient(c);
} }
@ -2373,6 +2391,7 @@ void commandProcessed(client *c, int flags) {
* of processing the command, otherwise C_OK is returned. */ * of processing the command, otherwise C_OK is returned. */
int processCommandAndResetClient(client *c, int flags) { int processCommandAndResetClient(client *c, int flags) {
int deadclient = 0; int deadclient = 0;
client *old_client = serverTL->current_client;
serverTL->current_client = c; serverTL->current_client = c;
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
@ -2380,7 +2399,14 @@ int processCommandAndResetClient(client *c, int flags) {
commandProcessed(c, flags); commandProcessed(c, flags);
} }
if (serverTL->current_client == NULL) deadclient = 1; if (serverTL->current_client == NULL) deadclient = 1;
serverTL->current_client = NULL; /*
* Restore the old client, this is needed because when a script
* times out, we will get into this code from processEventsWhileBlocked.
* Which will cause to set the server.current_client. If not restored
* we will return 1 to our caller which will falsely indicate the client
* is dead and will stop reading from its buffer.
*/
serverTL->current_client = old_client;
/* performEvictions may flush slave output buffers. This may /* performEvictions may flush slave output buffers. This may
* result in a replica, that may be the active client, to be * result in a replica, that may be the active client, to be
* freed. */ * freed. */
@ -3389,6 +3415,7 @@ void helloCommand(client *c) {
int moreargs = (c->argc-1) - j; int moreargs = (c->argc-1) - j;
const char *opt = (const char*)ptrFromObj(c->argv[j]); const char *opt = (const char*)ptrFromObj(c->argv[j]);
if (!strcasecmp(opt,"AUTH") && moreargs >= 2) { if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
preventCommandLogging(c);
if (ACLAuthenticateUser(c, c->argv[j+1], c->argv[j+2]) == C_ERR) { if (ACLAuthenticateUser(c, c->argv[j+1], c->argv[j+2]) == C_ERR) {
addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled."); addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled.");
return; return;
@ -3457,7 +3484,7 @@ void securityWarningCommand(client *c) {
static time_t logged_time; static time_t logged_time;
time_t now = time(NULL); time_t now = time(NULL);
if (labs(now-logged_time) > 60) { if (llabs(now-logged_time) > 60) {
serverLog(LL_WARNING,"Possible SECURITY ATTACK detected. It looks like somebody is sending POST or Host: commands to KeyDB. This is likely due to an attacker attempting to use Cross Protocol Scripting to compromise your KeyDB instance. Connection aborted."); serverLog(LL_WARNING,"Possible SECURITY ATTACK detected. It looks like somebody is sending POST or Host: commands to KeyDB. This is likely due to an attacker attempting to use Cross Protocol Scripting to compromise your KeyDB instance. Connection aborted.");
logged_time = now; logged_time = now;
} }
@ -3749,6 +3776,7 @@ void unpauseClients(void) {
listRewind(g_pserver->paused_clients,&li); listRewind(g_pserver->paused_clients,&li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
c = (client*)listNodeValue(ln); c = (client*)listNodeValue(ln);
std::unique_lock<fastlock> ul(c->lock);
unblockClient(c); unblockClient(c);
} }
} }
@ -3762,6 +3790,8 @@ int areClientsPaused(void) {
* if it has. Also returns true if clients are now paused and false * if it has. Also returns true if clients are now paused and false
* otherwise. */ * otherwise. */
int checkClientPauseTimeoutAndReturnIfPaused(void) { int checkClientPauseTimeoutAndReturnIfPaused(void) {
if (!areClientsPaused())
return 0;
if (g_pserver->client_pause_end_time < g_pserver->mstime) { if (g_pserver->client_pause_end_time < g_pserver->mstime) {
unpauseClients(); unpauseClients();
} }
@ -3813,6 +3843,7 @@ void processEventsWhileBlocked(int iel) {
g_pserver->repl_batch_offStart = -1; g_pserver->repl_batch_offStart = -1;
} }
long long eventsCount = 0;
aeReleaseLock(); aeReleaseLock();
serverAssert(!GlobalLocksAcquired()); serverAssert(!GlobalLocksAcquired());
try try
@ -3825,8 +3856,8 @@ void processEventsWhileBlocked(int iel) {
AE_CALL_BEFORE_SLEEP|AE_CALL_AFTER_SLEEP); AE_CALL_BEFORE_SLEEP|AE_CALL_AFTER_SLEEP);
/* Note that g_pserver->events_processed_while_blocked will also get /* Note that g_pserver->events_processed_while_blocked will also get
* incremeted by callbacks called by the event loop handlers. */ * incremeted by callbacks called by the event loop handlers. */
g_pserver->events_processed_while_blocked += ae_events; eventsCount += ae_events;
long long events = g_pserver->events_processed_while_blocked - startval; long long events = eventsCount - startval;
if (!events) break; if (!events) break;
} }
ProcessingEventsWhileBlocked = 0; ProcessingEventsWhileBlocked = 0;
@ -3847,6 +3878,8 @@ void processEventsWhileBlocked(int iel) {
locker.arm(nullptr); locker.arm(nullptr);
locker.release(); locker.release();
g_pserver->events_processed_while_blocked += eventsCount;
whileBlockedCron(); whileBlockedCron();
// Restore it so the calling code is not confused // Restore it so the calling code is not confused

View File

@ -56,6 +56,7 @@ int keyspaceEventsStringToFlags(char *classes) {
case 'E': flags |= NOTIFY_KEYEVENT; break; case 'E': flags |= NOTIFY_KEYEVENT; break;
case 't': flags |= NOTIFY_STREAM; break; case 't': flags |= NOTIFY_STREAM; break;
case 'm': flags |= NOTIFY_KEY_MISS; break; case 'm': flags |= NOTIFY_KEY_MISS; break;
case 'd': flags |= NOTIFY_MODULE; break;
default: return -1; default: return -1;
} }
} }
@ -82,6 +83,7 @@ sds keyspaceEventsFlagsToString(int flags) {
if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1); if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1); if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1); if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
if (flags & NOTIFY_MODULE) res = sdscatlen(res,"d",1);
} }
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1); if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1); if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);

View File

@ -793,7 +793,11 @@ int getRangeLongFromObjectOrReply(client *c, robj *o, long min, long max, long *
} }
int getPositiveLongFromObjectOrReply(client *c, robj *o, long *target, const char *msg) { int getPositiveLongFromObjectOrReply(client *c, robj *o, long *target, const char *msg) {
if (msg) {
return getRangeLongFromObjectOrReply(c, o, 0, LONG_MAX, target, msg); return getRangeLongFromObjectOrReply(c, o, 0, LONG_MAX, target, msg);
} else {
return getRangeLongFromObjectOrReply(c, o, 0, LONG_MAX, target, "value is out of range, must be positive");
}
} }
int getIntFromObjectOrReply(client *c, robj *o, int *target, const char *msg) { int getIntFromObjectOrReply(client *c, robj *o, int *target, const char *msg) {

View File

@ -349,21 +349,6 @@ int pubsubPublishMessage(robj *channel, robj *message) {
return receivers; return receivers;
} }
/* This wraps handling ACL channel permissions for the given client. */
int pubsubCheckACLPermissionsOrReply(client *c, int idx, int count, int literal) {
/* Check if the user can run the command according to the current
* ACLs. */
int acl_chanpos;
int acl_retval = ACLCheckPubsubPerm(c,idx,count,literal,&acl_chanpos);
if (acl_retval == ACL_DENIED_CHANNEL) {
addACLLogEntry(c,acl_retval,acl_chanpos,NULL);
addReplyError(c,
"-NOPERM this user has no permissions to access "
"one of the channels used as arguments");
}
return acl_retval;
}
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Pubsub commands implementation * Pubsub commands implementation
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
@ -372,7 +357,6 @@ int pubsubCheckACLPermissionsOrReply(client *c, int idx, int count, int literal)
void subscribeCommand(client *c) { void subscribeCommand(client *c) {
int j; int j;
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
if (pubsubCheckACLPermissionsOrReply(c,1,c->argc-1,0) != ACL_OK) return;
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) { if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
/** /**
* A client that has CLIENT_DENY_BLOCKING flag on * A client that has CLIENT_DENY_BLOCKING flag on
@ -407,7 +391,6 @@ void unsubscribeCommand(client *c) {
void psubscribeCommand(client *c) { void psubscribeCommand(client *c) {
int j; int j;
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
if (pubsubCheckACLPermissionsOrReply(c,1,c->argc-1,1) != ACL_OK) return;
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) { if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
/** /**
* A client that has CLIENT_DENY_BLOCKING flag on * A client that has CLIENT_DENY_BLOCKING flag on
@ -440,7 +423,6 @@ void punsubscribeCommand(client *c) {
/* PUBLISH <channel> <message> */ /* PUBLISH <channel> <message> */
void publishCommand(client *c) { void publishCommand(client *c) {
if (pubsubCheckACLPermissionsOrReply(c,1,1,0) != ACL_OK) return;
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]); int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
if (g_pserver->cluster_enabled) if (g_pserver->cluster_enabled)
clusterPropagatePublish(c->argv[1],c->argv[2]); clusterPropagatePublish(c->argv[1],c->argv[2]);

View File

@ -315,7 +315,9 @@ REDIS_STATIC void __quicklistCompress(const quicklist *quicklist,
if (forward == node || reverse == node) if (forward == node || reverse == node)
in_depth = 1; in_depth = 1;
if (forward == reverse) /* We passed into compress depth of opposite side of the quicklist
* so there's no need to compress anything and we can exit. */
if (forward == reverse || forward->next == reverse)
return; return;
forward = forward->next; forward = forward->next;
@ -325,11 +327,9 @@ REDIS_STATIC void __quicklistCompress(const quicklist *quicklist,
if (!in_depth) if (!in_depth)
quicklistCompressNode(node); quicklistCompressNode(node);
if (depth > 2) {
/* At this point, forward and reverse are one node beyond depth */ /* At this point, forward and reverse are one node beyond depth */
quicklistCompressNode(forward); quicklistCompressNode(forward);
quicklistCompressNode(reverse); quicklistCompressNode(reverse);
}
} }
#define quicklistCompress(_ql, _node) \ #define quicklistCompress(_ql, _node) \
@ -380,10 +380,11 @@ REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist,
quicklist->head = quicklist->tail = new_node; quicklist->head = quicklist->tail = new_node;
} }
/* Update len first, so in __quicklistCompress we know exactly len */
quicklist->len++;
if (old_node) if (old_node)
quicklistCompress(quicklist, old_node); quicklistCompress(quicklist, old_node);
quicklist->len++;
} }
/* Wrappers for node inserting around existing node. */ /* Wrappers for node inserting around existing node. */
@ -602,15 +603,16 @@ REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
quicklist->head = node->next; quicklist->head = node->next;
} }
/* Update len first, so in __quicklistCompress we know exactly len */
quicklist->len--;
quicklist->count -= node->count;
/* If we deleted a node within our compress depth, we /* If we deleted a node within our compress depth, we
* now have compressed nodes needing to be decompressed. */ * now have compressed nodes needing to be decompressed. */
__quicklistCompress(quicklist, NULL); __quicklistCompress(quicklist, NULL);
quicklist->count -= node->count;
zfree(node->zl); zfree(node->zl);
zfree(node); zfree(node);
quicklist->len--;
} }
/* Delete one entry from list given the node for the entry and a pointer /* Delete one entry from list given the node for the entry and a pointer
@ -1296,17 +1298,24 @@ void quicklistRotate(quicklist *quicklist) {
/* First, get the tail entry */ /* First, get the tail entry */
unsigned char *p = ziplistIndex(quicklist->tail->zl, -1); unsigned char *p = ziplistIndex(quicklist->tail->zl, -1);
unsigned char *value; unsigned char *value, *tmp;
long long longval; long long longval;
unsigned int sz; unsigned int sz;
char longstr[32] = {0}; char longstr[32] = {0};
ziplistGet(p, &value, &sz, &longval); ziplistGet(p, &tmp, &sz, &longval);
/* If value found is NULL, then ziplistGet populated longval instead */ /* If value found is NULL, then ziplistGet populated longval instead */
if (!value) { if (!tmp) {
/* Write the longval as a string so we can re-add it */ /* Write the longval as a string so we can re-add it */
sz = ll2string(longstr, sizeof(longstr), longval); sz = ll2string(longstr, sizeof(longstr), longval);
value = (unsigned char *)longstr; value = (unsigned char *)longstr;
} else if (quicklist->len == 1) {
/* Copy buffer since there could be a memory overlap when move
* entity from tail to head in the same ziplist. */
value = zmalloc(sz, MALLOC_SHARED);
memcpy(value, tmp, sz);
} else {
value = tmp;
} }
/* Add tail entry to head (must happen before tail is deleted). */ /* Add tail entry to head (must happen before tail is deleted). */
@ -1321,6 +1330,8 @@ void quicklistRotate(quicklist *quicklist) {
/* Remove tail entry. */ /* Remove tail entry. */
quicklistDelIndex(quicklist, quicklist->tail, &p); quicklistDelIndex(quicklist, quicklist->tail, &p);
if (value != (unsigned char*)longstr && value != tmp)
zfree(value);
} }
/* pop from quicklist and return result in 'data' ptr. Value of 'data' /* pop from quicklist and return result in 'data' ptr. Value of 'data'
@ -1509,8 +1520,6 @@ void quicklistBookmarksClear(quicklist *ql) {
#define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__) #define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__)
#define OK printf("\tOK\n")
#define ERROR \ #define ERROR \
do { \ do { \
printf("\tERROR!\n"); \ printf("\tERROR!\n"); \
@ -1630,7 +1639,6 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
} }
if (ql->len == 0 && !errors) { if (ql->len == 0 && !errors) {
OK;
return errors; return errors;
} }
@ -1679,8 +1687,6 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
} }
} }
if (!errors)
OK;
return errors; return errors;
} }
@ -1692,9 +1698,10 @@ static char *genstr(char *prefix, int i) {
} }
/* main test, but callable from other files */ /* main test, but callable from other files */
int quicklistTest(int argc, char *argv[]) { int quicklistTest(int argc, char *argv[], int accurate) {
UNUSED(argc); UNUSED(argc);
UNUSED(argv); UNUSED(argv);
UNUSED(accurate);
unsigned int err = 0; unsigned int err = 0;
int optimize_start = int optimize_start =
@ -1703,11 +1710,14 @@ int quicklistTest(int argc, char *argv[]) {
printf("Starting optimization offset at: %d\n", optimize_start); printf("Starting optimization offset at: %d\n", optimize_start);
int options[] = {0, 1, 2, 3, 4, 5, 6, 10}; int options[] = {0, 1, 2, 3, 4, 5, 6, 10};
int fills[] = {-5, -4, -3, -2, -1, 0,
1, 2, 32, 66, 128, 999};
size_t option_count = sizeof(options) / sizeof(*options); size_t option_count = sizeof(options) / sizeof(*options);
int fill_count = (int)(sizeof(fills) / sizeof(*fills));
long long runtime[option_count]; long long runtime[option_count];
for (int _i = 0; _i < (int)option_count; _i++) { for (int _i = 0; _i < (int)option_count; _i++) {
printf("Testing Option %d\n", options[_i]); printf("Testing Compression option %d\n", options[_i]);
long long start = mstime(); long long start = mstime();
TEST("create list") { TEST("create list") {
@ -1732,57 +1742,53 @@ int quicklistTest(int argc, char *argv[]) {
quicklistRelease(ql); quicklistRelease(ql);
} }
for (int f = optimize_start; f < 32; f++) { TEST_DESC("add to tail 5x at compress %d", options[_i]) {
TEST_DESC("add to tail 5x at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
quicklistPushTail(ql, genstr("hello", i), 32); quicklistPushTail(ql, genstr("hello", i), 32);
if (ql->count != 5) if (ql->count != 5)
ERROR; ERROR;
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 1, 5, 5, 5); ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql); quicklistRelease(ql);
} }
} }
for (int f = optimize_start; f < 32; f++) { TEST_DESC("add to head 5x at compress %d", options[_i]) {
TEST_DESC("add to head 5x at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
quicklistPushHead(ql, genstr("hello", i), 32); quicklistPushHead(ql, genstr("hello", i), 32);
if (ql->count != 5) if (ql->count != 5)
ERROR; ERROR;
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 1, 5, 5, 5); ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql); quicklistRelease(ql);
} }
} }
for (int f = optimize_start; f < 512; f++) { TEST_DESC("add to tail 500x at compress %d", options[_i]) {
TEST_DESC("add to tail 500x at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i), 64); quicklistPushTail(ql, genstr("hello", i), 64);
if (ql->count != 500) if (ql->count != 500)
ERROR; ERROR;
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 16, 500, 32, 20); ql_verify(ql, 16, 500, 32, 20);
quicklistRelease(ql); quicklistRelease(ql);
} }
} }
for (int f = optimize_start; f < 512; f++) { TEST_DESC("add to head 500x at compress %d", options[_i]) {
TEST_DESC("add to head 500x at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 32); quicklistPushHead(ql, genstr("hello", i), 32);
if (ql->count != 500) if (ql->count != 500)
ERROR; ERROR;
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 16, 500, 20, 32); ql_verify(ql, 16, 500, 20, 32);
quicklistRelease(ql); quicklistRelease(ql);
} }
@ -1795,9 +1801,9 @@ int quicklistTest(int argc, char *argv[]) {
quicklistRelease(ql); quicklistRelease(ql);
} }
for (int f = optimize_start; f < 32; f++) {
TEST("rotate one val once") { TEST("rotate one val once") {
quicklist *ql = quicklistNew(f, options[_i]); for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklistPushHead(ql, "hello", 6); quicklistPushHead(ql, "hello", 6);
quicklistRotate(ql); quicklistRotate(ql);
/* Ignore compression verify because ziplist is /* Ignore compression verify because ziplist is
@ -1807,10 +1813,9 @@ int quicklistTest(int argc, char *argv[]) {
} }
} }
for (int f = optimize_start; f < 3; f++) { TEST_DESC("rotate 500 val 5000 times at compress %d", options[_i]) {
TEST_DESC("rotate 500 val 5000 times at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
quicklistPushHead(ql, "900", 3); quicklistPushHead(ql, "900", 3);
quicklistPushHead(ql, "7000", 4); quicklistPushHead(ql, "7000", 4);
quicklistPushHead(ql, "-1200", 5); quicklistPushHead(ql, "-1200", 5);
@ -1822,11 +1827,11 @@ int quicklistTest(int argc, char *argv[]) {
ql_info(ql); ql_info(ql);
quicklistRotate(ql); quicklistRotate(ql);
} }
if (f == 1) if (fills[f] == 1)
ql_verify(ql, 504, 504, 1, 1); ql_verify(ql, 504, 504, 1, 1);
else if (f == 2) else if (fills[f] == 2)
ql_verify(ql, 252, 504, 2, 2); ql_verify(ql, 252, 504, 2, 2);
else if (f == 32) else if (fills[f] == 32)
ql_verify(ql, 16, 504, 32, 24); ql_verify(ql, 16, 504, 32, 24);
quicklistRelease(ql); quicklistRelease(ql);
} }
@ -2003,11 +2008,10 @@ int quicklistTest(int argc, char *argv[]) {
quicklistRelease(ql); quicklistRelease(ql);
} }
for (int f = optimize_start; f < 12; f++) { TEST_DESC("insert once in elements while iterating at compress %d",
TEST_DESC("insert once in elements while iterating at fill %d at " options[_i]) {
"compress %d\n", for (int f = 0; f < fill_count; f++) {
f, options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
quicklistPushTail(ql, "abc", 3); quicklistPushTail(ql, "abc", 3);
quicklistSetFill(ql, 1); quicklistSetFill(ql, 1);
quicklistPushTail(ql, "def", 3); /* force to unique node */ quicklistPushTail(ql, "def", 3); /* force to unique node */
@ -2059,12 +2063,10 @@ int quicklistTest(int argc, char *argv[]) {
} }
} }
for (int f = optimize_start; f < 1024; f++) { TEST_DESC("insert [before] 250 new in middle of 500 elements at compress %d",
TEST_DESC( options[_i]) {
"insert [before] 250 new in middle of 500 elements at fill" for (int f = 0; f < fill_count; f++) {
" %d at compress %d", quicklist *ql = quicklistNew(fills[f], options[_i]);
f, options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i), 32); quicklistPushTail(ql, genstr("hello", i), 32);
for (int i = 0; i < 250; i++) { for (int i = 0; i < 250; i++) {
@ -2072,17 +2074,16 @@ int quicklistTest(int argc, char *argv[]) {
quicklistIndex(ql, 250, &entry); quicklistIndex(ql, 250, &entry);
quicklistInsertBefore(ql, &entry, genstr("abc", i), 32); quicklistInsertBefore(ql, &entry, genstr("abc", i), 32);
} }
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 25, 750, 32, 20); ql_verify(ql, 25, 750, 32, 20);
quicklistRelease(ql); quicklistRelease(ql);
} }
} }
for (int f = optimize_start; f < 1024; f++) { TEST_DESC("insert [after] 250 new in middle of 500 elements at compress %d",
TEST_DESC("insert [after] 250 new in middle of 500 elements at " options[_i]) {
"fill %d at compress %d", for (int f = 0; f < fill_count; f++) {
f, options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
quicklistPushHead(ql, genstr("hello", i), 32); quicklistPushHead(ql, genstr("hello", i), 32);
for (int i = 0; i < 250; i++) { for (int i = 0; i < 250; i++) {
@ -2094,7 +2095,7 @@ int quicklistTest(int argc, char *argv[]) {
if (ql->count != 750) if (ql->count != 750)
ERR("List size not 750, but rather %ld", ql->count); ERR("List size not 750, but rather %ld", ql->count);
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 26, 750, 20, 32); ql_verify(ql, 26, 750, 20, 32);
quicklistRelease(ql); quicklistRelease(ql);
} }
@ -2132,70 +2133,58 @@ int quicklistTest(int argc, char *argv[]) {
quicklistRelease(copy); quicklistRelease(copy);
} }
for (int f = optimize_start; f < 512; f++) { for (int f = 0; f < fill_count; f++) {
TEST_DESC("index 1,200 from 500 list at fill %d at compress %d", f, TEST_DESC("index 1,200 from 500 list at fill %d at compress %d", f,
options[_i]) { options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]); quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32); quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, 1, &entry); quicklistIndex(ql, 1, &entry);
if (!strcmp((char *)entry.value, "hello2")) if (strcmp((char *)entry.value, "hello2") != 0)
OK;
else
ERR("Value: %s", entry.value); ERR("Value: %s", entry.value);
quicklistIndex(ql, 200, &entry); quicklistIndex(ql, 200, &entry);
if (!strcmp((char *)entry.value, "hello201")) if (strcmp((char *)entry.value, "hello201") != 0)
OK;
else
ERR("Value: %s", entry.value); ERR("Value: %s", entry.value);
quicklistRelease(ql); quicklistRelease(ql);
} }
TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d", f, TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d",
options[_i]) { fills[f], options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]); quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32); quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, -1, &entry); quicklistIndex(ql, -1, &entry);
if (!strcmp((char *)entry.value, "hello500")) if (strcmp((char *)entry.value, "hello500") != 0)
OK;
else
ERR("Value: %s", entry.value); ERR("Value: %s", entry.value);
quicklistIndex(ql, -2, &entry); quicklistIndex(ql, -2, &entry);
if (!strcmp((char *)entry.value, "hello499")) if (strcmp((char *)entry.value, "hello499") != 0)
OK;
else
ERR("Value: %s", entry.value); ERR("Value: %s", entry.value);
quicklistRelease(ql); quicklistRelease(ql);
} }
TEST_DESC("index -100 from 500 list at fill %d at compress %d", f, TEST_DESC("index -100 from 500 list at fill %d at compress %d",
options[_i]) { fills[f], options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]); quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32); quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, -100, &entry); quicklistIndex(ql, -100, &entry);
if (!strcmp((char *)entry.value, "hello401")) if (strcmp((char *)entry.value, "hello401") != 0)
OK;
else
ERR("Value: %s", entry.value); ERR("Value: %s", entry.value);
quicklistRelease(ql); quicklistRelease(ql);
} }
TEST_DESC("index too big +1 from 50 list at fill %d at compress %d", TEST_DESC("index too big +1 from 50 list at fill %d at compress %d",
f, options[_i]) { fills[f], options[_i]) {
quicklist *ql = quicklistNew(f, options[_i]); quicklist *ql = quicklistNew(fills[f], options[_i]);
for (int i = 0; i < 50; i++) for (int i = 0; i < 50; i++)
quicklistPushTail(ql, genstr("hello", i + 1), 32); quicklistPushTail(ql, genstr("hello", i + 1), 32);
quicklistEntry entry; quicklistEntry entry;
if (quicklistIndex(ql, 50, &entry)) if (quicklistIndex(ql, 50, &entry))
ERR("Index found at 50 with 50 list: %.*s", entry.sz, ERR("Index found at 50 with 50 list: %.*s", entry.sz,
entry.value); entry.value);
else
OK;
quicklistRelease(ql); quicklistRelease(ql);
} }
} }
@ -2367,12 +2356,11 @@ int quicklistTest(int argc, char *argv[]) {
quicklistReplaceAtIndex(ql, 1, "foo", 3); quicklistReplaceAtIndex(ql, 1, "foo", 3);
quicklistReplaceAtIndex(ql, -1, "bar", 3); quicklistReplaceAtIndex(ql, -1, "bar", 3);
quicklistRelease(ql); quicklistRelease(ql);
OK;
} }
for (int f = optimize_start; f < 16; f++) { TEST_DESC("lrem test at compress %d", options[_i]) {
TEST_DESC("lrem test at fill %d at compress %d", f, options[_i]) { for (int f = 0; f < fill_count; f++) {
quicklist *ql = quicklistNew(f, options[_i]); quicklist *ql = quicklistNew(fills[f], options[_i]);
char *words[] = {"abc", "foo", "bar", "foobar", "foobared", char *words[] = {"abc", "foo", "bar", "foobar", "foobared",
"zap", "bar", "test", "foo"}; "zap", "bar", "test", "foo"};
char *result[] = {"abc", "foo", "foobar", "foobared", char *result[] = {"abc", "foo", "foobar", "foobared",
@ -2397,14 +2385,12 @@ int quicklistTest(int argc, char *argv[]) {
/* check result of lrem 0 bar */ /* check result of lrem 0 bar */
iter = quicklistGetIterator(ql, AL_START_HEAD); iter = quicklistGetIterator(ql, AL_START_HEAD);
i = 0; i = 0;
int ok = 1;
while (quicklistNext(iter, &entry)) { while (quicklistNext(iter, &entry)) {
/* Result must be: abc, foo, foobar, foobared, zap, test, /* Result must be: abc, foo, foobar, foobared, zap, test,
* foo */ * foo */
if (strncmp((char *)entry.value, result[i], entry.sz)) { if (strncmp((char *)entry.value, result[i], entry.sz)) {
ERR("No match at position %d, got %.*s instead of %s", ERR("No match at position %d, got %.*s instead of %s",
i, entry.sz, entry.value, result[i]); i, entry.sz, entry.value, result[i]);
ok = 0;
} }
i++; i++;
} }
@ -2441,23 +2427,18 @@ int quicklistTest(int argc, char *argv[]) {
entry.sz)) { entry.sz)) {
ERR("No match at position %d, got %.*s instead of %s", ERR("No match at position %d, got %.*s instead of %s",
i, entry.sz, entry.value, resultB[resB - 1 - i]); i, entry.sz, entry.value, resultB[resB - 1 - i]);
ok = 0;
} }
i++; i++;
} }
quicklistReleaseIterator(iter); quicklistReleaseIterator(iter);
/* final result of all tests */
if (ok)
OK;
quicklistRelease(ql); quicklistRelease(ql);
} }
} }
for (int f = optimize_start; f < 16; f++) { TEST_DESC("iterate reverse + delete at compress %d", options[_i]) {
TEST_DESC("iterate reverse + delete at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
quicklistPushTail(ql, "abc", 3); quicklistPushTail(ql, "abc", 3);
quicklistPushTail(ql, "def", 3); quicklistPushTail(ql, "def", 3);
quicklistPushTail(ql, "hij", 3); quicklistPushTail(ql, "hij", 3);
@ -2494,10 +2475,9 @@ int quicklistTest(int argc, char *argv[]) {
} }
} }
for (int f = optimize_start; f < 800; f++) { TEST_DESC("iterator at index test at compress %d", options[_i]) {
TEST_DESC("iterator at index test at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
char num[32]; char num[32];
long long nums[5000]; long long nums[5000];
for (int i = 0; i < 760; i++) { for (int i = 0; i < 760; i++) {
@ -2521,10 +2501,9 @@ int quicklistTest(int argc, char *argv[]) {
} }
} }
for (int f = optimize_start; f < 40; f++) { TEST_DESC("ltrim test A at compress %d", options[_i]) {
TEST_DESC("ltrim test A at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
char num[32]; char num[32];
long long nums[5000]; long long nums[5000];
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
@ -2532,7 +2511,7 @@ int quicklistTest(int argc, char *argv[]) {
int sz = ll2string(num, sizeof(num), nums[i]); int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz); quicklistPushTail(ql, num, sz);
} }
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 1, 32, 32, 32); ql_verify(ql, 1, 32, 32, 32);
/* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */ /* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */
quicklistDelRange(ql, 0, 25); quicklistDelRange(ql, 0, 25);
@ -2545,18 +2524,17 @@ int quicklistTest(int argc, char *argv[]) {
"%lld", "%lld",
entry.longval, nums[25 + i]); entry.longval, nums[25 + i]);
} }
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 1, 7, 7, 7); ql_verify(ql, 1, 7, 7, 7);
quicklistRelease(ql); quicklistRelease(ql);
} }
} }
for (int f = optimize_start; f < 40; f++) { TEST_DESC("ltrim test B at compress %d", options[_i]) {
TEST_DESC("ltrim test B at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) {
/* Force-disable compression because our 33 sequential /* Force-disable compression because our 33 sequential
* integers don't compress and the check always fails. */ * integers don't compress and the check always fails. */
quicklist *ql = quicklistNew(f, QUICKLIST_NOCOMPRESS); quicklist *ql = quicklistNew(fills[f], QUICKLIST_NOCOMPRESS);
char num[32]; char num[32];
long long nums[5000]; long long nums[5000];
for (int i = 0; i < 33; i++) { for (int i = 0; i < 33; i++) {
@ -2564,24 +2542,20 @@ int quicklistTest(int argc, char *argv[]) {
int sz = ll2string(num, sizeof(num), nums[i]); int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz); quicklistPushTail(ql, num, sz);
} }
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 2, 33, 32, 1); ql_verify(ql, 2, 33, 32, 1);
/* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */ /* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */
quicklistDelRange(ql, 0, 5); quicklistDelRange(ql, 0, 5);
quicklistDelRange(ql, -16, 16); quicklistDelRange(ql, -16, 16);
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 1, 12, 12, 12); ql_verify(ql, 1, 12, 12, 12);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, 0, &entry); quicklistIndex(ql, 0, &entry);
if (entry.longval != 5) if (entry.longval != 5)
ERR("A: longval not 5, but %lld", entry.longval); ERR("A: longval not 5, but %lld", entry.longval);
else
OK;
quicklistIndex(ql, -1, &entry); quicklistIndex(ql, -1, &entry);
if (entry.longval != 16) if (entry.longval != 16)
ERR("B! got instead: %lld", entry.longval); ERR("B! got instead: %lld", entry.longval);
else
OK;
quicklistPushTail(ql, "bobobob", 7); quicklistPushTail(ql, "bobobob", 7);
quicklistIndex(ql, -1, &entry); quicklistIndex(ql, -1, &entry);
if (strncmp((char *)entry.value, "bobobob", 7)) if (strncmp((char *)entry.value, "bobobob", 7))
@ -2598,10 +2572,9 @@ int quicklistTest(int argc, char *argv[]) {
} }
} }
for (int f = optimize_start; f < 40; f++) { TEST_DESC("ltrim test C at compress %d", options[_i]) {
TEST_DESC("ltrim test C at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
char num[32]; char num[32];
long long nums[5000]; long long nums[5000];
for (int i = 0; i < 33; i++) { for (int i = 0; i < 33; i++) {
@ -2609,28 +2582,25 @@ int quicklistTest(int argc, char *argv[]) {
int sz = ll2string(num, sizeof(num), nums[i]); int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz); quicklistPushTail(ql, num, sz);
} }
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 2, 33, 32, 1); ql_verify(ql, 2, 33, 32, 1);
/* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */ /* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */
quicklistDelRange(ql, 0, 3); quicklistDelRange(ql, 0, 3);
quicklistDelRange(ql, -29, quicklistDelRange(ql, -29,
4000); /* make sure not loop forever */ 4000); /* make sure not loop forever */
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 1, 1, 1, 1); ql_verify(ql, 1, 1, 1, 1);
quicklistEntry entry; quicklistEntry entry;
quicklistIndex(ql, 0, &entry); quicklistIndex(ql, 0, &entry);
if (entry.longval != -5157318210846258173) if (entry.longval != -5157318210846258173)
ERROR; ERROR;
else
OK;
quicklistRelease(ql); quicklistRelease(ql);
} }
} }
for (int f = optimize_start; f < 40; f++) { TEST_DESC("ltrim test D at compress %d", options[_i]) {
TEST_DESC("ltrim test D at fill %d at compress %d", f, for (int f = 0; f < fill_count; f++) {
options[_i]) { quicklist *ql = quicklistNew(fills[f], options[_i]);
quicklist *ql = quicklistNew(f, options[_i]);
char num[32]; char num[32];
long long nums[5000]; long long nums[5000];
for (int i = 0; i < 33; i++) { for (int i = 0; i < 33; i++) {
@ -2638,7 +2608,7 @@ int quicklistTest(int argc, char *argv[]) {
int sz = ll2string(num, sizeof(num), nums[i]); int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, num, sz); quicklistPushTail(ql, num, sz);
} }
if (f == 32) if (fills[f] == 32)
ql_verify(ql, 2, 33, 32, 1); ql_verify(ql, 2, 33, 32, 1);
quicklistDelRange(ql, -12, 3); quicklistDelRange(ql, -12, 3);
if (ql->count != 30) if (ql->count != 30)
@ -2648,9 +2618,8 @@ int quicklistTest(int argc, char *argv[]) {
} }
} }
for (int f = optimize_start; f < 72; f++) { TEST_DESC("create quicklist from ziplist at compress %d", options[_i]) {
TEST_DESC("create quicklist from ziplist at fill %d at compress %d", for (int f = 0; f < fill_count; f++) {
f, options[_i]) {
unsigned char *zl = ziplistNew(); unsigned char *zl = ziplistNew();
long long nums[64]; long long nums[64];
char num[64]; char num[64];
@ -2664,12 +2633,12 @@ int quicklistTest(int argc, char *argv[]) {
zl = ziplistPush(zl, (unsigned char *)genstr("hello", i), zl = ziplistPush(zl, (unsigned char *)genstr("hello", i),
32, ZIPLIST_TAIL); 32, ZIPLIST_TAIL);
} }
quicklist *ql = quicklistCreateFromZiplist(f, options[_i], zl); quicklist *ql = quicklistCreateFromZiplist(fills[f], options[_i], zl);
if (f == 1) if (fills[f] == 1)
ql_verify(ql, 66, 66, 1, 1); ql_verify(ql, 66, 66, 1, 1);
else if (f == 32) else if (fills[f] == 32)
ql_verify(ql, 3, 66, 32, 2); ql_verify(ql, 3, 66, 32, 2);
else if (f == 66) else if (fills[f] == 66)
ql_verify(ql, 1, 66, 66, 66); ql_verify(ql, 1, 66, 66, 66);
quicklistRelease(ql); quicklistRelease(ql);
} }
@ -2682,21 +2651,30 @@ int quicklistTest(int argc, char *argv[]) {
/* Run a longer test of compression depth outside of primary test loop. */ /* Run a longer test of compression depth outside of primary test loop. */
int list_sizes[] = {250, 251, 500, 999, 1000}; int list_sizes[] = {250, 251, 500, 999, 1000};
long long start = mstime(); long long start = mstime();
for (int list = 0; list < (int)(sizeof(list_sizes) / sizeof(*list_sizes)); int list_count = accurate ? (int)(sizeof(list_sizes) / sizeof(*list_sizes)) : 1;
list++) { for (int list = 0; list < list_count; list++) {
for (int f = optimize_start; f < 128; f++) { TEST_DESC("verify specific compression of interior nodes with %d list ",
list_sizes[list]) {
for (int f = 0; f < fill_count; f++) {
for (int depth = 1; depth < 40; depth++) { for (int depth = 1; depth < 40; depth++) {
/* skip over many redundant test cases */ /* skip over many redundant test cases */
TEST_DESC("verify specific compression of interior nodes with " quicklist *ql = quicklistNew(fills[f], depth);
"%d list "
"at fill %d at compress %d",
list_sizes[list], f, depth) {
quicklist *ql = quicklistNew(f, depth);
for (int i = 0; i < list_sizes[list]; i++) { for (int i = 0; i < list_sizes[list]; i++) {
quicklistPushTail(ql, genstr("hello TAIL", i + 1), 64); quicklistPushTail(ql, genstr("hello TAIL", i + 1), 64);
quicklistPushHead(ql, genstr("hello HEAD", i + 1), 64); quicklistPushHead(ql, genstr("hello HEAD", i + 1), 64);
} }
for (int step = 0; step < 2; step++) {
/* test remove node */
if (step == 1) {
for (int i = 0; i < list_sizes[list] / 2; i++) {
unsigned char *data;
quicklistPop(ql, QUICKLIST_HEAD, &data, NULL, NULL);
zfree(data);
quicklistPop(ql, QUICKLIST_TAIL, &data, NULL, NULL);
zfree(data);
}
}
quicklistNode *node = ql->head; quicklistNode *node = ql->head;
unsigned int low_raw = ql->compress; unsigned int low_raw = ql->compress;
unsigned int high_raw = ql->len - ql->compress; unsigned int high_raw = ql->len - ql->compress;
@ -2721,6 +2699,8 @@ int quicklistTest(int argc, char *argv[]) {
} }
} }
} }
}
quicklistRelease(ql); quicklistRelease(ql);
} }
} }

View File

@ -213,7 +213,7 @@ quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name);
void quicklistBookmarksClear(quicklist *ql); void quicklistBookmarksClear(quicklist *ql);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int quicklistTest(int argc, char *argv[]); int quicklistTest(int argc, char *argv[], int accurate);
#endif #endif
/* Directions for iterators */ /* Directions for iterators */

View File

@ -1118,8 +1118,7 @@ size_t rdbSavedObjectLen(robj *o, robj *key) {
/* Save a key-value pair, with expire time, type, key, value. /* Save a key-value pair, with expire time, type, key, value.
* On error -1 is returned. * On error -1 is returned.
* On success if the key was actually saved 1 is returned, otherwise 0 * On success if the key was actually saved 1 is returned. */
* is returned (the key was already expired). */
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, expireEntry *pexpire) { int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, expireEntry *pexpire) {
int savelru = g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LRU; int savelru = g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LRU;
int savelfu = g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU; int savelfu = g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU;
@ -2261,16 +2260,17 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, uint64_t mvcc_tstamp) {
return NULL; return NULL;
} }
moduleType *mt = moduleTypeLookupModuleByID(moduleid); moduleType *mt = moduleTypeLookupModuleByID(moduleid);
char name[10];
if (rdbCheckMode && rdbtype == RDB_TYPE_MODULE_2) { if (rdbCheckMode && rdbtype == RDB_TYPE_MODULE_2) {
char name[10];
moduleTypeNameByID(name,moduleid); moduleTypeNameByID(name,moduleid);
return rdbLoadCheckModuleValue(rdb,name); return rdbLoadCheckModuleValue(rdb,name);
} }
if (mt == NULL) { if (mt == NULL) {
char name[10];
moduleTypeNameByID(name,moduleid); moduleTypeNameByID(name,moduleid);
rdbReportCorruptRDB("The RDB file contains module data I can't load: no matching module '%s'", name); rdbReportCorruptRDB("The RDB file contains module data I can't load: no matching module type '%s'", name);
return NULL; return NULL;
} }
RedisModuleIO io; RedisModuleIO io;
@ -2297,7 +2297,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, uint64_t mvcc_tstamp) {
return NULL; return NULL;
} }
if (eof != RDB_MODULE_OPCODE_EOF) { if (eof != RDB_MODULE_OPCODE_EOF) {
rdbReportCorruptRDB("The RDB file contains module data for the module '%s' that is not terminated by the proper module value EOF marker", name); rdbReportCorruptRDB("The RDB file contains module data for the module '%s' that is not terminated by "
"the proper module value EOF marker", moduleTypeModuleName(mt));
if (ptr) { if (ptr) {
o = createModuleObject(mt,ptr); /* creating just in order to easily destroy */ o = createModuleObject(mt,ptr); /* creating just in order to easily destroy */
decrRefCount(o); decrRefCount(o);
@ -2307,8 +2308,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, uint64_t mvcc_tstamp) {
} }
if (ptr == NULL) { if (ptr == NULL) {
moduleTypeNameByID(name,moduleid); rdbReportCorruptRDB("The RDB file contains module data for the module type '%s', that the responsible "
rdbReportCorruptRDB("The RDB file contains module data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name); "module is not able to load. Check for modules log above for additional clues.",
moduleTypeModuleName(mt));
return NULL; return NULL;
} }
o = createModuleObject(mt,ptr); o = createModuleObject(mt,ptr);
@ -2935,7 +2937,7 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
* the cleanup needed. */ * the cleanup needed. */
void killRDBChild(void) { void killRDBChild(void) {
kill(g_pserver->child_pid, SIGUSR1); kill(g_pserver->child_pid, SIGUSR1);
/* Because we are not using here wait4 (like we have in killAppendOnlyChild /* Because we are not using here waitpid (like we have in killAppendOnlyChild
* and TerminateModuleForkChild), all the cleanup operations is done by * and TerminateModuleForkChild), all the cleanup operations is done by
* checkChildrenDone, that later will find that the process killed. * checkChildrenDone, that later will find that the process killed.
* This includes: * This includes:

View File

@ -106,7 +106,6 @@ static struct config {
int showerrors; int showerrors;
long long start; long long start;
long long totlatency; long long totlatency;
long long *latency;
const char *title; const char *title;
list *clients; list *clients;
int quiet; int quiet;
@ -1661,7 +1660,10 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
const float instantaneous_rps = (float)(requests_finished-previous_requests_finished)/instantaneous_dt; const float instantaneous_rps = (float)(requests_finished-previous_requests_finished)/instantaneous_dt;
config.previous_tick = current_tick; config.previous_tick = current_tick;
atomicSet(config.previous_requests_finished,requests_finished); atomicSet(config.previous_requests_finished,requests_finished);
config.last_printed_bytes = printf("%s: rps=%.1f (overall: %.1f) avg_msec=%.3f (overall: %.3f)\r", config.title, instantaneous_rps, rps, hdr_mean(config.current_sec_latency_histogram)/1000.0f, hdr_mean(config.latency_histogram)/1000.0f); int printed_bytes = printf("%s: rps=%.1f (overall: %.1f) avg_msec=%.3f (overall: %.3f)\r", config.title, instantaneous_rps, rps, hdr_mean(config.current_sec_latency_histogram)/1000.0f, hdr_mean(config.latency_histogram)/1000.0f);
if (printed_bytes > config.last_printed_bytes){
config.last_printed_bytes = printed_bytes;
}
hdr_reset(config.current_sec_latency_histogram); hdr_reset(config.current_sec_latency_histogram);
fflush(stdout); fflush(stdout);
return 250; /* every 250ms */ return 250; /* every 250ms */

View File

@ -192,6 +192,7 @@ int redis_check_rdb(const char *rdbfilename, FILE *fp) {
int closefile = (fp == NULL); int closefile = (fp == NULL);
if (fp == NULL && (fp = fopen(rdbfilename,"r")) == NULL) return 1; if (fp == NULL && (fp = fopen(rdbfilename,"r")) == NULL) return 1;
startLoadingFile(fp, rdbfilename, RDBFLAGS_NONE);
rioInitWithFile(&rdb,fp); rioInitWithFile(&rdb,fp);
rdbstate.rio = &rdb; rdbstate.rio = &rdb;
rdb.update_cksum = rdbLoadProgressCallback; rdb.update_cksum = rdbLoadProgressCallback;
@ -208,7 +209,6 @@ int redis_check_rdb(const char *rdbfilename, FILE *fp) {
} }
expiretime = -1; expiretime = -1;
startLoadingFile(fp, rdbfilename, RDBFLAGS_NONE);
while(1) { while(1) {
robj *key, *val; robj *key, *val;

View File

@ -397,8 +397,12 @@ int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
else types = sdsempty(); else types = sdsempty();
/* Master type 'm' is always set as the first character of the /* Master type 'm' is always set as the first character of the
* types string. */ * types string. */
if (!node->replicate) types = sdscatprintf(types, "m%s", types); if (node->replicate) types = sdscat(types, "s");
else types = sdscat(types, "s"); else {
sds s = sdscatsds(sdsnew("m"), types);
sdsfree(types);
types = s;
}
dictReplace(related, key, types); dictReplace(related, key, types);
} }
/* Now it's trivial to check, for each related group having the /* Now it's trivial to check, for each related group having the

View File

@ -238,15 +238,17 @@ static void parseRedisUri(const char *uri) {
if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) { if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) {
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
config.tls = 1; config.tls = 1;
curr += strlen(tlsscheme);
#else #else
fprintf(stderr,"rediss:// is only supported when redis-cli is compiled with OpenSSL\n"); fprintf(stderr,"rediss:// is only supported when redis-cli is compiled with OpenSSL\n");
exit(1); exit(1);
#endif #endif
} else if (strncasecmp(scheme, curr, strlen(scheme))) { } else if (!strncasecmp(scheme, curr, strlen(scheme))) {
curr += strlen(scheme);
} else {
fprintf(stderr,"Invalid URI scheme\n"); fprintf(stderr,"Invalid URI scheme\n");
exit(1); exit(1);
} }
curr += strlen(scheme);
if (curr == end) return; if (curr == end) return;
/* Extract user info. */ /* Extract user info. */
@ -568,6 +570,23 @@ static void freeHintsCallback(void *ptr) {
* Networking / parsing * Networking / parsing
*--------------------------------------------------------------------------- */ *--------------------------------------------------------------------------- */
/* Unquote a null-terminated string and return it as a binary-safe sds. */
static sds unquoteCString(char *str) {
int count;
sds *unquoted = sdssplitargs(str, &count);
sds res = NULL;
if (unquoted && count == 1) {
res = unquoted[0];
unquoted[0] = NULL;
}
if (unquoted)
sdsfreesplitres(unquoted, count);
return res;
}
/* Send AUTH command to the server */ /* Send AUTH command to the server */
static int cliAuth(redisContext *ctx, char *user, char *auth) { static int cliAuth(redisContext *ctx, char *user, char *auth) {
redisReply *reply; redisReply *reply;
@ -1338,6 +1357,8 @@ static int parseOptions(int argc, char **argv) {
config.output = OUTPUT_RAW; config.output = OUTPUT_RAW;
} else if (!strcmp(argv[i],"--no-raw")) { } else if (!strcmp(argv[i],"--no-raw")) {
config.output = OUTPUT_STANDARD; config.output = OUTPUT_STANDARD;
} else if (!strcmp(argv[i],"--quoted-input")) {
config.quoted_input = 1;
} else if (!strcmp(argv[i],"--csv")) { } else if (!strcmp(argv[i],"--csv")) {
config.output = OUTPUT_CSV; config.output = OUTPUT_CSV;
} else if (!strcmp(argv[i],"--latency")) { } else if (!strcmp(argv[i],"--latency")) {
@ -1362,7 +1383,15 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"--scan")) { } else if (!strcmp(argv[i],"--scan")) {
config.scan_mode = 1; config.scan_mode = 1;
} else if (!strcmp(argv[i],"--pattern") && !lastarg) { } else if (!strcmp(argv[i],"--pattern") && !lastarg) {
config.pattern = argv[++i]; sdsfree(config.pattern);
config.pattern = sdsnew(argv[++i]);
} else if (!strcmp(argv[i],"--quoted-pattern") && !lastarg) {
sdsfree(config.pattern);
config.pattern = unquoteCString(argv[++i]);
if (!config.pattern) {
fprintf(stderr,"Invalid quoted string specified for --quoted-pattern.\n");
exit(1);
}
} else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) { } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
config.intrinsic_latency_mode = 1; config.intrinsic_latency_mode = 1;
config.intrinsic_latency_duration = atoi(argv[++i]); config.intrinsic_latency_duration = atoi(argv[++i]);
@ -1648,6 +1677,7 @@ static void usage(void) {
" --raw Use raw formatting for replies (default when STDOUT is\n" " --raw Use raw formatting for replies (default when STDOUT is\n"
" not a tty).\n" " not a tty).\n"
" --no-raw Force formatted output even when STDOUT is not a tty.\n" " --no-raw Force formatted output even when STDOUT is not a tty.\n"
" --quoted-input Force input to be handled as quoted strings.\n"
" --csv Output in CSV format.\n" " --csv Output in CSV format.\n"
" --show-pushes <yn> Whether to print RESP3 PUSH messages. Enabled by default when\n" " --show-pushes <yn> Whether to print RESP3 PUSH messages. Enabled by default when\n"
" STDOUT is a tty but can be overriden with --show-pushes no.\n" " STDOUT is a tty but can be overriden with --show-pushes no.\n"
@ -1683,6 +1713,8 @@ static void usage(void) {
" --scan List all keys using the SCAN command.\n" " --scan List all keys using the SCAN command.\n"
" --pattern <pat> Keys pattern when using the --scan, --bigkeys or --hotkeys\n" " --pattern <pat> Keys pattern when using the --scan, --bigkeys or --hotkeys\n"
" options (default: *).\n" " options (default: *).\n"
" --quoted-pattern <pat> Same as --pattern, but the specified string can be\n"
" quoted, in order to pass an otherwise non binary-safe string.\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"
@ -1708,6 +1740,7 @@ static void usage(void) {
" keydb-cli get mypasswd\n" " keydb-cli get mypasswd\n"
" keydb-cli -r 100 lpush mylist x\n" " keydb-cli -r 100 lpush mylist x\n"
" keydb-cli -r 100 -i 1 info | grep used_memory_human:\n" " keydb-cli -r 100 -i 1 info | grep used_memory_human:\n"
" keydb-cli --quoted-input set '\"null-\\x00-separated\"' value\n"
" keydb-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n" " keydb-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
" keydb-cli --scan --pattern '*:12345*'\n" " keydb-cli --scan --pattern '*:12345*'\n"
"\n" "\n"
@ -1737,22 +1770,28 @@ int confirmWithYes(const char *msg, int ignore_force) {
return (nread != 0 && !strcmp("yes", buf)); return (nread != 0 && !strcmp("yes", buf));
} }
/* Turn the plain C strings into Sds strings */ /* Create an sds array from argv, either as-is or by dequoting every
static char **convertToSds(int count, char** args) { * element. When quoted is non-zero, may return a NULL to indicate an
int j; * invalid quoted string.
char **sds = zmalloc(sizeof(char*)*count, MALLOC_LOCAL); */
static sds *getSdsArrayFromArgv(int argc, char **argv, int quoted) {
sds *res = sds_malloc(sizeof(sds) * argc);
for(j = 0; j < count; j++) for (int j = 0; j < argc; j++) {
sds[j] = sdsnew(args[j]); if (quoted) {
sds unquoted = unquoteCString(argv[j]);
if (!unquoted) {
while (--j >= 0) sdsfree(res[j]);
sds_free(res);
return NULL;
}
res[j] = unquoted;
} else {
res[j] = sdsnew(argv[j]);
}
}
return sds; return res;
}
static void freeConvertedSds(int count, char **sds) {
int j;
for (j = 0; j < count; j++)
sdsfree(sds[j]);
zfree(sds);
} }
static int issueCommandRepeat(int argc, char **argv, long repeat) { static int issueCommandRepeat(int argc, char **argv, long repeat) {
@ -1985,17 +2024,19 @@ static void repl(void) {
static int noninteractive(int argc, char **argv) { static int noninteractive(int argc, char **argv) {
int retval = 0; int retval = 0;
sds *sds_args = getSdsArrayFromArgv(argc, argv, config.quoted_input);
argv = convertToSds(argc, argv); if (!sds_args) {
if (config.stdinarg) { printf("Invalid quoted string\n");
argv = zrealloc(argv, (argc+1)*sizeof(char*), MALLOC_LOCAL); return 1;
argv[argc] = readArgFromStdin();
retval = issueCommand(argc+1, argv);
sdsfree(argv[argc]);
} else {
retval = issueCommand(argc, argv);
} }
freeConvertedSds(argc, argv); if (config.stdinarg) {
sds_args = sds_realloc(sds_args, (argc + 1) * sizeof(sds));
sds_args[argc] = readArgFromStdin();
argc++;
}
retval = issueCommand(argc, sds_args);
sdsfreesplitres(sds_args, argc);
return retval; return retval;
} }
@ -6234,7 +6275,10 @@ static void getRDB(clusterManagerNode *node) {
redisFree(s); /* Close the connection ASAP as fsync() may take time. */ redisFree(s); /* Close the connection ASAP as fsync() may take time. */
if (node) if (node)
node->context = NULL; node->context = NULL;
fsync(fd); if (fsync(fd) == -1) {
fprintf(stderr,"Fail to fsync '%s': %s\n", filename, strerror(errno));
exit(1);
}
close(fd); close(fd);
if (node) { if (node) {
sdsfree(filename); sdsfree(filename);
@ -6410,8 +6454,8 @@ redisReply *sendScan(unsigned long long *it) {
redisReply *reply; redisReply *reply;
if (config.pattern) if (config.pattern)
reply = redisCommand(context,"SCAN %llu MATCH %s", reply = redisCommand(context, "SCAN %llu MATCH %b",
*it,config.pattern); *it, config.pattern, sdslen(config.pattern));
else else
reply = redisCommand(context,"SCAN %llu",*it); reply = redisCommand(context,"SCAN %llu",*it);
@ -6446,8 +6490,14 @@ int getDbSize(void) {
reply = redisCommand(context, "DBSIZE"); reply = redisCommand(context, "DBSIZE");
if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) { if (reply == NULL) {
fprintf(stderr, "Couldn't determine DBSIZE!\n"); fprintf(stderr, "\nI/O error\n");
exit(1);
} else if (reply->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "Couldn't determine DBSIZE: %s\n", reply->str);
exit(1);
} else if (reply->type != REDIS_REPLY_INTEGER) {
fprintf(stderr, "Non INTEGER response from DBSIZE!\n");
exit(1); exit(1);
} }
@ -6819,24 +6869,17 @@ static void scanMode(void) {
unsigned long long cur = 0; unsigned long long cur = 0;
do { do {
if (config.pattern) reply = sendScan(&cur);
reply = redisCommand(context,"SCAN %llu MATCH %s", for (unsigned int j = 0; j < reply->element[1]->elements; j++) {
cur,config.pattern); if (config.output == OUTPUT_STANDARD) {
else sds out = sdscatrepr(sdsempty(), reply->element[1]->element[j]->str,
reply = redisCommand(context,"SCAN %llu",cur); reply->element[1]->element[j]->len);
if (reply == NULL) { printf("%s\n", out);
printf("I/O error\n"); sdsfree(out);
exit(1);
} else if (reply->type == REDIS_REPLY_ERROR) {
printf("ERROR: %s\n", reply->str);
exit(1);
} else { } else {
unsigned int j;
cur = strtoull(reply->element[0]->str,NULL,10);
for (j = 0; j < reply->element[1]->elements; j++)
printf("%s\n", reply->element[1]->element[j]->str); printf("%s\n", reply->element[1]->element[j]->str);
} }
}
freeReplyObject(reply); freeReplyObject(reply);
} while(cur != 0); } while(cur != 0);

View File

@ -166,7 +166,7 @@ extern struct config {
int scan_mode; int scan_mode;
int intrinsic_latency_mode; int intrinsic_latency_mode;
int intrinsic_latency_duration; int intrinsic_latency_duration;
char *pattern; sds pattern;
char *rdb_filename; char *rdb_filename;
int bigkeys; int bigkeys;
int memkeys; int memkeys;
@ -195,6 +195,7 @@ extern struct config {
int disable_motd; int disable_motd;
int in_multi; int in_multi;
int pre_multi_dbnum; int pre_multi_dbnum;
int quoted_input; /* Force input args to be treated as quoted strings */
} config; } config;
struct clusterManager { struct clusterManager {

View File

@ -164,13 +164,14 @@ This flag should not be used directly by the module.
#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_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */
#define REDISMODULE_NOTIFY_MODULE (1<<13) /* d, module key space notification */
/* Next notification flag, must be updated when adding new flags above! /* Next notification flag, must be updated when adding new flags above!
This flag should not be used directly by the module. This flag should not be used directly by the module.
* Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */ * Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */
#define _REDISMODULE_NOTIFY_NEXT (1<<13) #define _REDISMODULE_NOTIFY_NEXT (1<<14)
#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 | REDISMODULE_NOTIFY_MODULE) /* 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. */
@ -197,6 +198,12 @@ This flag should not be used directly by the module.
#define REDISMODULE_NOT_USED(V) ((void) V) #define REDISMODULE_NOT_USED(V) ((void) V)
/* Logging level strings */
#define REDISMODULE_LOGLEVEL_DEBUG "debug"
#define REDISMODULE_LOGLEVEL_VERBOSE "verbose"
#define REDISMODULE_LOGLEVEL_NOTICE "notice"
#define REDISMODULE_LOGLEVEL_WARNING "warning"
/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */ /* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
#define REDISMODULE_AUX_BEFORE_RDB (1<<0) #define REDISMODULE_AUX_BEFORE_RDB (1<<0)
#define REDISMODULE_AUX_AFTER_RDB (1<<1) #define REDISMODULE_AUX_AFTER_RDB (1<<1)
@ -639,6 +646,8 @@ REDISMODULE_API char * (*RedisModule_StringDMA)(RedisModuleKey *key, size_t *len
REDISMODULE_API int (*RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen) REDISMODULE_ATTR;
REDISMODULE_API mstime_t (*RedisModule_GetExpire)(RedisModuleKey *key) 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 int (*RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR;
REDISMODULE_API mstime_t (*RedisModule_GetAbsExpire)(RedisModuleKey *key) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetAbsExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ResetDataset)(int restart_aof, int async) 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 unsigned long long (*RedisModule_DbSize)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_RandomKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_RandomKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
@ -839,7 +848,7 @@ REDISMODULE_API int (*RedisModule_DefragCursorSet)(RedisModuleDefragCtx *ctx, un
REDISMODULE_API int (*RedisModule_DefragCursorGet)(RedisModuleDefragCtx *ctx, unsigned long *cursor) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_DefragCursorGet)(RedisModuleDefragCtx *ctx, unsigned long *cursor) REDISMODULE_ATTR;
#endif #endif
#define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF) #define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
/* 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) REDISMODULE_ATTR_UNUSED; static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR_UNUSED;
@ -911,6 +920,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(StringTruncate); REDISMODULE_GET_API(StringTruncate);
REDISMODULE_GET_API(GetExpire); REDISMODULE_GET_API(GetExpire);
REDISMODULE_GET_API(SetExpire); REDISMODULE_GET_API(SetExpire);
REDISMODULE_GET_API(GetAbsExpire);
REDISMODULE_GET_API(SetAbsExpire);
REDISMODULE_GET_API(ResetDataset); REDISMODULE_GET_API(ResetDataset);
REDISMODULE_GET_API(DbSize); REDISMODULE_GET_API(DbSize);
REDISMODULE_GET_API(RandomKey); REDISMODULE_GET_API(RandomKey);

View File

@ -1191,15 +1191,32 @@ LError:
/* REPLCONF <option> <value> <option> <value> ... /* REPLCONF <option> <value> <option> <value> ...
* This command is used by a replica in order to configure the replication * This command is used by a replica in order to configure the replication
* process before starting it with the SYNC command. * process before starting it with the SYNC command.
* This command is also used by a master in order to get the replication
* offset from a replica.
* *
* Currently the only use of this command is to communicate to the master * Currently we support these options:
* what is the listening port of the Slave redis instance, so that the
* master can accurately list slaves and their listening ports in
* the INFO output.
* *
* In the future the same command can be used in order to configure * - listening-port <port>
* the replication to initiate an incremental replication instead of a * - ip-address <ip>
* full resync. */ * What is the listening ip and port of the Replica redis instance, so that
* the master can accurately lists replicas and their listening ports in the
* INFO output.
*
* - capa <eof|psync2>
* What is the capabilities of this instance.
* eof: supports EOF-style RDB transfer for diskless replication.
* psync2: supports PSYNC v2, so understands +CONTINUE <new repl ID>.
*
* - ack <offset>
* Replica informs the master the amount of replication stream that it
* processed so far.
*
* - getack
* Unlike other subcommands, this is used by master to get the replication
* offset from a replica.
*
* - rdb-only
* Only wants RDB snapshot without replication buffer. */
void replconfCommand(client *c) { void replconfCommand(client *c) {
int j; int j;
bool fCapaCommand = false; bool fCapaCommand = false;
@ -1483,6 +1500,8 @@ void rdbPipeWriteHandlerConnRemoved(struct connection *conn) {
if (!connHasWriteHandler(conn)) if (!connHasWriteHandler(conn))
return; return;
connSetWriteHandler(conn, NULL); connSetWriteHandler(conn, NULL);
client *slave = (client*)connGetPrivateData(conn);
slave->repl_last_partial_write = 0;
g_pserver->rdb_pipe_numconns_writing--; g_pserver->rdb_pipe_numconns_writing--;
/* if there are no more writes for now for this conn, or write error: */ /* if there are no more writes for now for this conn, or write error: */
if (g_pserver->rdb_pipe_numconns_writing == 0) { if (g_pserver->rdb_pipe_numconns_writing == 0) {
@ -1513,9 +1532,11 @@ void rdbPipeWriteHandler(struct connection *conn) {
} else { } else {
slave->repldboff += nwritten; slave->repldboff += nwritten;
g_pserver->stat_net_output_bytes += nwritten; g_pserver->stat_net_output_bytes += nwritten;
if (slave->repldboff < g_pserver->rdb_pipe_bufflen) if (slave->repldboff < g_pserver->rdb_pipe_bufflen) {
slave->repl_last_partial_write = g_pserver->unixtime;
return; /* more data to write.. */ return; /* more data to write.. */
} }
}
rdbPipeWriteHandlerConnRemoved(conn); rdbPipeWriteHandlerConnRemoved(conn);
} }
@ -1602,6 +1623,7 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData,
* setup write handler (and disable pipe read handler, below) */ * setup write handler (and disable pipe read handler, below) */
if (nwritten != g_pserver->rdb_pipe_bufflen) { if (nwritten != g_pserver->rdb_pipe_bufflen) {
g_pserver->rdb_pipe_numconns_writing++; g_pserver->rdb_pipe_numconns_writing++;
slave->repl_last_partial_write = g_pserver->unixtime;
slave->postFunction([conn](client *) { slave->postFunction([conn](client *) {
connSetWriteHandler(conn, rdbPipeWriteHandler); connSetWriteHandler(conn, rdbPipeWriteHandler);
}); });
@ -1946,6 +1968,7 @@ void readSyncBulkPayload(connection *conn) {
redisMaster *mi = (redisMaster*)connGetPrivateData(conn); redisMaster *mi = (redisMaster*)connGetPrivateData(conn);
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
serverAssert(mi->master == nullptr);
/* Static vars used to hold the EOF mark, and the last bytes received /* Static vars used to hold the EOF mark, and the last bytes received
* from the server: when they match, we reached the end of the transfer. */ * from the server: when they match, we reached the end of the transfer. */
@ -2324,8 +2347,7 @@ void readSyncBulkPayload(connection *conn) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success"); serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success");
if (cserver.supervised_mode == SUPERVISED_SYSTEMD) { if (cserver.supervised_mode == SUPERVISED_SYSTEMD) {
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Finished with success. Ready to accept connections.\n"); redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Finished with success. Ready to accept connections in read-write mode.\n");
redisCommunicateSystemd("READY=1\n");
} }
/* Send the initial ACK immediately to put this replica in online state. */ /* Send the initial ACK immediately to put this replica in online state. */
@ -2943,8 +2965,7 @@ void syncWithMaster(connection *conn) {
if (psync_result == PSYNC_CONTINUE) { if (psync_result == PSYNC_CONTINUE) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization."); serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
if (cserver.supervised_mode == SUPERVISED_SYSTEMD) { if (cserver.supervised_mode == SUPERVISED_SYSTEMD) {
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections.\n"); redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections in read-write mode.\n");
redisCommunicateSystemd("READY=1\n");
} }
return; return;
} }
@ -3002,6 +3023,7 @@ void syncWithMaster(connection *conn) {
} }
/* Setup the non blocking download of the bulk file. */ /* Setup the non blocking download of the bulk file. */
serverAssert(mi->master == nullptr);
if (connSetReadHandler(conn, readSyncBulkPayload) if (connSetReadHandler(conn, readSyncBulkPayload)
== C_ERR) == C_ERR)
{ {
@ -3042,6 +3064,7 @@ write_error: /* Handle sendCommand() errors. */
} }
int connectWithMaster(redisMaster *mi) { int connectWithMaster(redisMaster *mi) {
serverAssert(mi->master == nullptr);
mi->repl_transfer_s = g_pserver->tls_replication ? connCreateTLS() : connCreateSocket(); mi->repl_transfer_s = g_pserver->tls_replication ? connCreateTLS() : connCreateSocket();
connSetPrivateData(mi->repl_transfer_s, mi); connSetPrivateData(mi->repl_transfer_s, mi);
if (connConnect(mi->repl_transfer_s, mi->masterhost, mi->masterport, if (connConnect(mi->repl_transfer_s, mi->masterhost, mi->masterport,
@ -3148,10 +3171,8 @@ struct redisMaster *replicationAddMaster(char *ip, int port) {
sdsfree(mi->masterhost); sdsfree(mi->masterhost);
mi->masterhost = nullptr; mi->masterhost = nullptr;
if (mi->master) { if (mi->master) {
if (FCorrectThread(mi->master))
freeClient(mi->master);
else
freeClientAsync(mi->master); freeClientAsync(mi->master);
mi->master = nullptr;
} }
if (!g_pserver->fActiveReplica) if (!g_pserver->fActiveReplica)
disconnectAllBlockedClients(); /* Clients blocked in master, now replica. */ disconnectAllBlockedClients(); /* Clients blocked in master, now replica. */
@ -3263,6 +3284,9 @@ void replicationUnsetMaster(redisMaster *mi) {
* failover if slaves do not connect immediately. */ * failover if slaves do not connect immediately. */
g_pserver->repl_no_slaves_since = g_pserver->unixtime; g_pserver->repl_no_slaves_since = g_pserver->unixtime;
/* Reset down time so it'll be ready for when we turn into replica again. */
mi->repl_down_since = 0;
listNode *ln = listSearchKey(g_pserver->masters, mi); listNode *ln = listSearchKey(g_pserver->masters, mi);
serverAssert(ln != nullptr); serverAssert(ln != nullptr);
listDelNode(g_pserver->masters, ln); listDelNode(g_pserver->masters, ln);
@ -3512,6 +3536,7 @@ void replicationSendAck(redisMaster *mi)
* handshake in order to reactivate the cached master. * handshake in order to reactivate the cached master.
*/ */
void replicationCacheMaster(redisMaster *mi, client *c) { void replicationCacheMaster(redisMaster *mi, client *c) {
serverAssert(mi->master == c);
serverAssert(mi->master != NULL && mi->cached_master == NULL); serverAssert(mi->master != NULL && mi->cached_master == NULL);
serverLog(LL_NOTICE,"Caching the disconnected master state."); serverLog(LL_NOTICE,"Caching the disconnected master state.");
AssertCorrectThread(c); AssertCorrectThread(c);
@ -3554,6 +3579,7 @@ void replicationCacheMaster(redisMaster *mi, client *c) {
* so make sure to adjust the replication state. This function will * so make sure to adjust the replication state. This function will
* also set g_pserver->master to NULL. */ * also set g_pserver->master to NULL. */
replicationHandleMasterDisconnection(mi); replicationHandleMasterDisconnection(mi);
serverAssert(mi->master == nullptr);
} }
/* This function is called when a master is turend into a slave, in order to /* This function is called when a master is turend into a slave, in order to
@ -3640,6 +3666,7 @@ void replicationResurrectCachedMaster(redisMaster *mi, connection *conn) {
/* 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);
serverAssert(mi->master->iel == ielFromEventLoop(serverTL->el));
if (connSetReadHandler(mi->master->conn, readQueryFromClient, true)) { if (connSetReadHandler(mi->master->conn, readQueryFromClient, true)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno)); serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
freeClientAsync(mi->master); /* Close ASAP. */ freeClientAsync(mi->master); /* Close ASAP. */
@ -3874,7 +3901,7 @@ void processClientsWaitingReplicas(void) {
listRewind(g_pserver->clients_waiting_acks,&li); listRewind(g_pserver->clients_waiting_acks,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
client *c = (client*)ln->value; client *c = (client*)ln->value;
fastlock_lock(&c->lock); std::unique_lock<fastlock> ul(c->lock);
/* Every time we find a client that is satisfied for a given /* Every time we find a client that is satisfied for a given
* offset and number of replicas, we remember it so the next client * offset and number of replicas, we remember it so the next client
@ -3895,7 +3922,6 @@ void processClientsWaitingReplicas(void) {
addReplyLongLong(c,numreplicas); addReplyLongLong(c,numreplicas);
} }
} }
fastlock_unlock(&c->lock);
} }
} }
@ -4071,21 +4097,27 @@ void replicationCron(void) {
client *replica = (client*)ln->value; client *replica = (client*)ln->value;
std::unique_lock<fastlock> ul(replica->lock); std::unique_lock<fastlock> ul(replica->lock);
if (replica->replstate != SLAVE_STATE_ONLINE) continue; if (replica->replstate == SLAVE_STATE_ONLINE) {
if (replica->flags & CLIENT_PRE_PSYNC) continue; if (replica->flags & CLIENT_PRE_PSYNC)
if ((g_pserver->unixtime - replica->repl_ack_time) > g_pserver->repl_timeout) continue;
{ if ((g_pserver->unixtime - replica->repl_ack_time) > g_pserver->repl_timeout) {
serverLog(LL_WARNING, "Disconnecting timedout replica: %s", serverLog(LL_WARNING, "Disconnecting timedout replica (streaming sync): %s",
replicationGetSlaveName(replica)); replicationGetSlaveName(replica));
if (FCorrectThread(replica))
{
ul.release();
if (!freeClient(replica))
replica->lock.unlock(); // we didn't free so we have undo the lock we just released
}
else
{
freeClientAsync(replica); freeClientAsync(replica);
continue;
}
}
/* We consider disconnecting only diskless replicas because disk-based replicas aren't fed
* by the fork child so if a disk-based replica is stuck it doesn't prevent the fork child
* from terminating. */
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_END && g_pserver->rdb_child_type == RDB_CHILD_TYPE_SOCKET) {
if (replica->repl_last_partial_write != 0 &&
(g_pserver->unixtime - replica->repl_last_partial_write) > g_pserver->repl_timeout)
{
serverLog(LL_WARNING, "Disconnecting timedout replica (full sync): %s",
replicationGetSlaveName(replica));
freeClientAsync(replica);
continue;
} }
} }
} }
@ -4253,7 +4285,7 @@ void abortFailover(redisMaster *mi, const char *err) {
} }
/* /*
* FAILOVER [TO <HOST> <IP> [FORCE]] [ABORT] [TIMEOUT <timeout>] * FAILOVER [TO <HOST> <PORT> [FORCE]] [ABORT] [TIMEOUT <timeout>]
* *
* This command will coordinate a failover between the master and one * This command will coordinate a failover between the master and one
* of its replicas. The happy path contains the following steps: * of its replicas. The happy path contains the following steps:
@ -4362,7 +4394,7 @@ void failoverCommand(client *c) {
client *replica = findReplica(host, port); client *replica = findReplica(host, port);
if (replica == NULL) { if (replica == NULL) {
addReplyError(c,"FAILOVER target HOST and IP is not " addReplyError(c,"FAILOVER target HOST and PORT is not "
"a replica."); "a replica.");
return; return;
} }

View File

@ -118,7 +118,7 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
r->io.file.buffered >= r->io.file.autosync) r->io.file.buffered >= r->io.file.autosync)
{ {
fflush(r->io.file.fp); fflush(r->io.file.fp);
redis_fsync(fileno(r->io.file.fp)); if (redis_fsync(fileno(r->io.file.fp)) == -1) return 0;
r->io.file.buffered = 0; r->io.file.buffered = 0;
} }
return retval; return retval;
@ -162,7 +162,7 @@ void rioInitWithFile(rio *r, FILE *fp) {
} }
/* ------------------- Connection implementation ------------------- /* ------------------- Connection implementation -------------------
* We use this RIO implemetnation when reading an RDB file directly from * We use this RIO implementation when reading an RDB file directly from
* the connection to the memory via rdbLoadRio(), thus this implementation * the connection to the memory via rdbLoadRio(), thus this implementation
* only implements reading from a connection that is, normally, * only implements reading from a connection that is, normally,
* just a socket. */ * just a socket. */
@ -265,7 +265,7 @@ void rioInitWithConn(rio *r, connection *conn, size_t read_limit) {
sdsclear(r->io.conn.buf); sdsclear(r->io.conn.buf);
} }
/* Release the RIO tream. Optionally returns the unread buffered data /* Release the RIO stream. Optionally returns the unread buffered data
* when the SDS pointer 'remaining' is passed. */ * when the SDS pointer 'remaining' is passed. */
void rioFreeConn(rio *r, sds *remaining) { void rioFreeConn(rio *r, sds *remaining) {
if (remaining && (size_t)r->io.conn.pos < sdslen(r->io.conn.buf)) { if (remaining && (size_t)r->io.conn.pos < sdslen(r->io.conn.buf)) {

View File

@ -618,9 +618,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
/* Check the ACLs. */ /* Check the ACLs. */
int acl_errpos; int acl_errpos;
int acl_retval = ACLCheckCommandPerm(c,&acl_errpos); int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
if (acl_retval == ACL_OK && c->cmd->proc == publishCommand)
acl_retval = ACLCheckPubsubPerm(c,1,1,0,&acl_errpos);
if (acl_retval != ACL_OK) { if (acl_retval != ACL_OK) {
addACLLogEntry(c,acl_retval,acl_errpos,NULL); addACLLogEntry(c,acl_retval,acl_errpos,NULL);
switch (acl_retval) { switch (acl_retval) {
@ -1463,6 +1461,14 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
if (g_pserver->lua_timedout) processEventsWhileBlocked(serverTL - g_pserver->rgthreadvar); if (g_pserver->lua_timedout) processEventsWhileBlocked(serverTL - g_pserver->rgthreadvar);
if (g_pserver->lua_kill) { if (g_pserver->lua_kill) {
serverLog(LL_WARNING,"Lua script killed by user with SCRIPT KILL."); serverLog(LL_WARNING,"Lua script killed by user with SCRIPT KILL.");
/*
* Set the hook to invoke all the time so the user
         * will not be able to catch the error with pcall and invoke
         * pcall again which will prevent the script from ever been killed
*/
lua_sethook(lua, luaMaskCountHook, LUA_MASKLINE, 0);
lua_pushstring(lua,"Script killed by user with SCRIPT KILL..."); lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
lua_error(lua); lua_error(lua);
} }
@ -1508,7 +1514,6 @@ void evalGenericCommand(client *c, int evalsha) {
g_pserver->lua_replicate_commands = g_pserver->lua_always_replicate_commands; g_pserver->lua_replicate_commands = g_pserver->lua_always_replicate_commands;
g_pserver->lua_multi_emitted = 0; g_pserver->lua_multi_emitted = 0;
g_pserver->lua_repl = PROPAGATE_AOF|PROPAGATE_REPL; g_pserver->lua_repl = PROPAGATE_AOF|PROPAGATE_REPL;
serverTL->in_eval = 1;
/* Get the number of arguments that are keys */ /* Get the number of arguments that are keys */
if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK) if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK)
@ -1580,6 +1585,7 @@ void evalGenericCommand(client *c, int evalsha) {
* *
* If we are debugging, we set instead a "line" hook so that the * If we are debugging, we set instead a "line" hook so that the
* debugger is call-back at every line executed by the script. */ * debugger is call-back at every line executed by the script. */
serverTL->in_eval = 1;
g_pserver->lua_caller = c; g_pserver->lua_caller = c;
g_pserver->lua_cur_script = funcname + 2; g_pserver->lua_cur_script = funcname + 2;
g_pserver->lua_time_start = mstime(); g_pserver->lua_time_start = mstime();
@ -1619,6 +1625,7 @@ void evalGenericCommand(client *c, int evalsha) {
queueClientForReprocessing(mi->master); queueClientForReprocessing(mi->master);
} }
} }
serverTL->in_eval = 0;
g_pserver->lua_caller = NULL; g_pserver->lua_caller = NULL;
g_pserver->lua_cur_script = NULL; g_pserver->lua_cur_script = NULL;
@ -1695,8 +1702,6 @@ void evalGenericCommand(client *c, int evalsha) {
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF); forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
} }
} }
serverTL->in_eval = 0;
} }
void evalCommand(client *c) { void evalCommand(client *c) {

View File

@ -1273,9 +1273,10 @@ static sds sdsTestTemplateCallback(sds varname, void *arg) {
else return NULL; else return NULL;
} }
int sdsTest(int argc, char **argv) { int sdsTest(int argc, char **argv, int accurate) {
UNUSED(argc); UNUSED(argc);
UNUSED(argv); UNUSED(argv);
UNUSED(accurate);
{ {
sds x = sdsnew("foo"), y; sds x = sdsnew("foo"), y;

View File

@ -336,7 +336,7 @@ void *sds_realloc(void *ptr, size_t size);
void sds_free(void *ptr); void sds_free(void *ptr);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int sdsTest(int argc, char *argv[]); int sdsTest(int argc, char *argv[], int accurate);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -217,6 +217,7 @@ typedef struct sentinelRedisInstance {
/* Slave specific. */ /* Slave specific. */
mstime_t master_link_down_time; /* Slave replication link down time. */ mstime_t master_link_down_time; /* Slave replication link down time. */
int slave_priority; /* Slave priority according to its INFO output. */ int slave_priority; /* Slave priority according to its INFO output. */
int replica_announced; /* Replica announcing according to its INFO output. */
mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */ mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */
struct sentinelRedisInstance *master; /* Master instance if it's slave. */ struct sentinelRedisInstance *master; /* Master instance if it's slave. */
char *slave_master_host; /* Master host as reported by INFO */ char *slave_master_host; /* Master host as reported by INFO */
@ -550,14 +551,12 @@ void initSentinel(void) {
g_pserver->sentinel_config = NULL; g_pserver->sentinel_config = NULL;
} }
/* This function gets called when the server is in Sentinel mode, started, /* This function is for checking whether sentinel config file has been set,
* loaded the configuration, and is ready for normal operations. */ * also checking whether we have write permissions. */
void sentinelIsRunning(void) { void sentinelCheckConfigFile(void) {
int j;
if (cserver.configfile == NULL) { if (cserver.configfile == NULL) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Sentinel started without a config file. Exiting..."); "Sentinel needs config file on disk to save state. Exiting...");
exit(1); exit(1);
} else if (access(cserver.configfile,W_OK) == -1) { } else if (access(cserver.configfile,W_OK) == -1) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
@ -565,6 +564,12 @@ void sentinelIsRunning(void) {
cserver.configfile,strerror(errno)); cserver.configfile,strerror(errno));
exit(1); exit(1);
} }
}
/* This function gets called when the server is in Sentinel mode, started,
* loaded the configuration, and is ready for normal operations. */
void sentinelIsRunning(void) {
int j;
/* If this Sentinel has yet no ID set in the configuration file, we /* If this Sentinel has yet no ID set in the configuration file, we
* pick a random one and persist the config on disk. From now on this * pick a random one and persist the config on disk. From now on this
@ -872,6 +877,7 @@ void sentinelRunPendingScripts(void) {
sj->pid = 0; sj->pid = 0;
} else if (pid == 0) { } else if (pid == 0) {
/* Child */ /* Child */
tlsCleanup();
execve(sj->argv[0],sj->argv,environ); execve(sj->argv[0],sj->argv,environ);
/* If we are here an error occurred. */ /* If we are here an error occurred. */
_exit(2); /* Don't retry execution. */ _exit(2); /* Don't retry execution. */
@ -905,7 +911,7 @@ void sentinelCollectTerminatedScripts(void) {
int statloc; int statloc;
pid_t pid; pid_t pid;
while ((pid = wait3(&statloc,WNOHANG,NULL)) > 0) { while ((pid = waitpid(-1, &statloc, WNOHANG)) > 0) {
int exitcode = WEXITSTATUS(statloc); int exitcode = WEXITSTATUS(statloc);
int bysignal = 0; int bysignal = 0;
listNode *ln; listNode *ln;
@ -917,7 +923,7 @@ void sentinelCollectTerminatedScripts(void) {
ln = sentinelGetScriptListNodeByPid(pid); ln = sentinelGetScriptListNodeByPid(pid);
if (ln == NULL) { if (ln == NULL) {
serverLog(LL_WARNING,"wait3() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid); serverLog(LL_WARNING,"waitpid() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid);
continue; continue;
} }
sj = (sentinelScriptJob*)ln->value; sj = (sentinelScriptJob*)ln->value;
@ -1336,6 +1342,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
ri->auth_pass = NULL; ri->auth_pass = NULL;
ri->auth_user = NULL; ri->auth_user = NULL;
ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY; ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY;
ri->replica_announced = 1;
ri->slave_reconf_sent_time = 0; ri->slave_reconf_sent_time = 0;
ri->slave_master_host = NULL; ri->slave_master_host = NULL;
ri->slave_master_port = 0; ri->slave_master_port = 0;
@ -1721,19 +1728,12 @@ void sentinelPropagateDownAfterPeriod(sentinelRedisInstance *master) {
} }
} }
const char *sentinelGetInstanceTypeString(sentinelRedisInstance *ri) {
if (ri->flags & SRI_MASTER) return "master";
else if (ri->flags & SRI_SLAVE) return "slave";
else if (ri->flags & SRI_SENTINEL) return "sentinel";
else return "unknown";
}
/* This function is used in order to send commands to Redis instances: the /* This function is used in order to send commands to Redis instances: the
* commands we send from Sentinel may be renamed, a common case is a master * commands we send from Sentinel may be renamed, a common case is a master
* with CONFIG and SLAVEOF commands renamed for security concerns. In that * with CONFIG and SLAVEOF commands renamed for security concerns. In that
* case we check the ri->renamed_command table (or if the instance is a slave, * case we check the ri->renamed_command table (or if the instance is a slave,
* we check the one of the master), and map the command that we should send * we check the one of the master), and map the command that we should send
* to the set of renamed commads. However, if the command was not renamed, * to the set of renamed commands. However, if the command was not renamed,
* we just return "command" itself. */ * we just return "command" itself. */
const char *sentinelInstanceMapCommand(sentinelRedisInstance *ri, const char *command) { const char *sentinelInstanceMapCommand(sentinelRedisInstance *ri, const char *command) {
sds sc = sdsnew(command); sds sc = sdsnew(command);
@ -1766,9 +1766,8 @@ const char *sentinelCheckCreateInstanceErrors(int role) {
return "Can't resolve instance hostname."; return "Can't resolve instance hostname.";
case EINVAL: case EINVAL:
return "Invalid port number."; return "Invalid port number.";
default:
return "Unknown Error for creating instances.";
} }
return "Unknown Error for creating instances.";
} }
/* init function for g_pserver->sentinel_config */ /* init function for g_pserver->sentinel_config */
@ -2047,12 +2046,12 @@ const char *sentinelHandleConfiguration(char **argv, int argc) {
} else if (!strcasecmp(argv[0],"resolve-hostnames") && argc == 2) { } else if (!strcasecmp(argv[0],"resolve-hostnames") && argc == 2) {
/* resolve-hostnames <yes|no> */ /* resolve-hostnames <yes|no> */
if ((sentinel.resolve_hostnames = yesnotoi(argv[1])) == -1) { if ((sentinel.resolve_hostnames = yesnotoi(argv[1])) == -1) {
return "Please specify yes or not for the resolve-hostnames option."; return "Please specify yes or no for the resolve-hostnames option.";
} }
} else if (!strcasecmp(argv[0],"announce-hostnames") && argc == 2) { } else if (!strcasecmp(argv[0],"announce-hostnames") && argc == 2) {
/* announce-hostnames <yes|no> */ /* announce-hostnames <yes|no> */
if ((sentinel.announce_hostnames = yesnotoi(argv[1])) == -1) { if ((sentinel.announce_hostnames = yesnotoi(argv[1])) == -1) {
return "Please specify yes or not for the announce-hostnames option."; return "Please specify yes or no for the announce-hostnames option.";
} }
} else { } else {
return "Unrecognized sentinel configuration statement."; return "Unrecognized sentinel configuration statement.";
@ -2329,8 +2328,8 @@ void sentinelFlushConfig(void) {
return; return;
werr: werr:
if (fd != -1) close(fd);
serverLog(LL_WARNING,"WARNING: Sentinel was not able to save the new configuration on disk!!!: %s", strerror(errno)); serverLog(LL_WARNING,"WARNING: Sentinel was not able to save the new configuration on disk!!!: %s", strerror(errno));
if (fd != -1) close(fd);
} }
/* ====================== hiredis connection handling ======================= */ /* ====================== hiredis connection handling ======================= */
@ -2431,8 +2430,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Commands connection. */ /* Commands connection. */
if (link->cc == NULL) { if (link->cc == NULL) {
link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR); link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
if (!link->cc->err) anetCloexec(link->cc->c.fd); if (link->cc && !link->cc->err) anetCloexec(link->cc->c.fd);
if (!link->cc->err && g_pserver->tls_replication && if (!link->cc) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to establish connection");
} else if (!link->cc->err && g_pserver->tls_replication &&
(instanceLinkNegotiateTLS(link->cc) == C_ERR)) { (instanceLinkNegotiateTLS(link->cc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS"); sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");
instanceLinkCloseConnection(link,link->cc); instanceLinkCloseConnection(link,link->cc);
@ -2459,8 +2460,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Pub / Sub */ /* Pub / Sub */
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) { if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR); link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
if (!link->pc->err) anetCloexec(link->pc->c.fd); if (link->pc && !link->pc->err) anetCloexec(link->pc->c.fd);
if (!link->pc->err && g_pserver->tls_replication && if (!link->pc) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to establish connection");
} else if (!link->pc->err && g_pserver->tls_replication &&
(instanceLinkNegotiateTLS(link->pc) == C_ERR)) { (instanceLinkNegotiateTLS(link->pc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS"); sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");
} else if (link->pc->err) { } else if (link->pc->err) {
@ -2633,6 +2636,10 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
/* slave_repl_offset:<offset> */ /* slave_repl_offset:<offset> */
if (sdslen(l) >= 18 && !memcmp(l,"slave_repl_offset:",18)) if (sdslen(l) >= 18 && !memcmp(l,"slave_repl_offset:",18))
ri->slave_repl_offset = strtoull(l+18,NULL,10); ri->slave_repl_offset = strtoull(l+18,NULL,10);
/* replica_announced:<announcement> */
if (sdslen(l) >= 18 && !memcmp(l,"replica_announced:",18))
ri->replica_announced = atoi(l+18);
} }
} }
ri->info_refresh = mstime(); ri->info_refresh = mstime();
@ -2653,8 +2660,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
((ri->flags & (SRI_MASTER|SRI_SLAVE)) == role) ? ((ri->flags & (SRI_MASTER|SRI_SLAVE)) == role) ?
"+role-change" : "-role-change", "+role-change" : "-role-change",
ri, "%@ new reported role is %s", ri, "%@ new reported role is %s",
role == SRI_MASTER ? "master" : "slave", role == SRI_MASTER ? "master" : "slave");
ri->flags & SRI_MASTER ? "master" : "slave");
} }
/* None of the following conditions are processed when in tilt mode, so /* None of the following conditions are processed when in tilt mode, so
@ -3300,6 +3306,8 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
if (ri->flags & SRI_RECONF_SENT) flags = sdscat(flags,"reconf_sent,"); if (ri->flags & SRI_RECONF_SENT) flags = sdscat(flags,"reconf_sent,");
if (ri->flags & SRI_RECONF_INPROG) flags = sdscat(flags,"reconf_inprog,"); if (ri->flags & SRI_RECONF_INPROG) flags = sdscat(flags,"reconf_inprog,");
if (ri->flags & SRI_RECONF_DONE) flags = sdscat(flags,"reconf_done,"); if (ri->flags & SRI_RECONF_DONE) flags = sdscat(flags,"reconf_done,");
if (ri->flags & SRI_FORCE_FAILOVER) flags = sdscat(flags,"force_failover,");
if (ri->flags & SRI_SCRIPT_KILL_SENT) flags = sdscat(flags,"script_kill_sent,");
if (sdslen(flags) != 0) sdsrange(flags,0,-2); /* remove last "," */ if (sdslen(flags) != 0) sdsrange(flags,0,-2); /* remove last "," */
addReplyBulkCString(c,flags); addReplyBulkCString(c,flags);
@ -3352,7 +3360,8 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
/* Masters and Slaves */ /* Masters and Slaves */
if (ri->flags & (SRI_MASTER|SRI_SLAVE)) { if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
addReplyBulkCString(c,"info-refresh"); addReplyBulkCString(c,"info-refresh");
addReplyBulkLongLong(c,mstime() - ri->info_refresh); addReplyBulkLongLong(c,
ri->info_refresh ? (mstime() - ri->info_refresh) : 0);
fields++; fields++;
addReplyBulkCString(c,"role-reported"); addReplyBulkCString(c,"role-reported");
@ -3432,6 +3441,10 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
addReplyBulkCString(c,"slave-repl-offset"); addReplyBulkCString(c,"slave-repl-offset");
addReplyBulkLongLong(c,ri->slave_repl_offset); addReplyBulkLongLong(c,ri->slave_repl_offset);
fields++; fields++;
addReplyBulkCString(c,"replica-announced");
addReplyBulkLongLong(c,ri->replica_announced);
fields++;
} }
/* Only sentinels */ /* Only sentinels */
@ -3457,15 +3470,20 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
void addReplyDictOfRedisInstances(client *c, dict *instances) { void addReplyDictOfRedisInstances(client *c, dict *instances) {
dictIterator *di; dictIterator *di;
dictEntry *de; dictEntry *de;
long slaves = 0;
void *replylen = addReplyDeferredLen(c);
di = dictGetIterator(instances); di = dictGetIterator(instances);
addReplyArrayLen(c,dictSize(instances));
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = (sentinelRedisInstance*)dictGetVal(de); sentinelRedisInstance *ri = (sentinelRedisInstance*)dictGetVal(de);
/* don't announce unannounced replicas */
if (ri->flags & SRI_SLAVE && !ri->replica_announced) continue;
addReplySentinelRedisInstance(c,ri); addReplySentinelRedisInstance(c,ri);
slaves++;
} }
dictReleaseIterator(di); dictReleaseIterator(di);
setDeferredArrayLen(c, replylen, slaves);
} }
/* Lookup the named master into sentinel.masters. /* Lookup the named master into sentinel.masters.
@ -3719,17 +3737,7 @@ NULL
ri = createSentinelRedisInstance(szFromObj(c->argv[2]),SRI_MASTER, ri = createSentinelRedisInstance(szFromObj(c->argv[2]),SRI_MASTER,
szFromObj(c->argv[3]),port,quorum,NULL); szFromObj(c->argv[3]),port,quorum,NULL);
if (ri == NULL) { if (ri == NULL) {
switch(errno) { addReplyError(c,sentinelCheckCreateInstanceErrors(SRI_MASTER));
case EBUSY:
addReplyError(c,"Duplicated master name");
break;
case EINVAL:
addReplyError(c,"Invalid port number");
break;
default:
addReplyError(c,"Unspecified error adding the instance");
break;
}
} else { } else {
sentinelFlushConfig(); sentinelFlushConfig();
sentinelEvent(LL_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum); sentinelEvent(LL_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
@ -3829,7 +3837,8 @@ NULL
addReplyBulkCBuffer(c,ri->name,strlen(ri->name)); addReplyBulkCBuffer(c,ri->name,strlen(ri->name));
addReplyArrayLen(c,dictSize(ri->slaves) + 1); /* +1 for self */ addReplyArrayLen(c,dictSize(ri->slaves) + 1); /* +1 for self */
addReplyArrayLen(c,2); addReplyArrayLen(c,2);
addReplyLongLong(c, now - ri->info_refresh); addReplyLongLong(c,
ri->info_refresh ? (now - ri->info_refresh) : 0);
if (ri->info) if (ri->info)
addReplyBulkCBuffer(c,ri->info,sdslen(ri->info)); addReplyBulkCBuffer(c,ri->info,sdslen(ri->info));
else else
@ -3841,7 +3850,8 @@ NULL
while ((sde = dictNext(sdi)) != NULL) { while ((sde = dictNext(sdi)) != NULL) {
sentinelRedisInstance *sri = (sentinelRedisInstance*)dictGetVal(sde); sentinelRedisInstance *sri = (sentinelRedisInstance*)dictGetVal(sde);
addReplyArrayLen(c,2); addReplyArrayLen(c,2);
addReplyLongLong(c, now - sri->info_refresh); addReplyLongLong(c,
ri->info_refresh ? (now - sri->info_refresh) : 0);
if (sri->info) if (sri->info)
addReplyBulkCBuffer(c,sri->info,sdslen(sri->info)); addReplyBulkCBuffer(c,sri->info,sdslen(sri->info));
else else

View File

@ -940,7 +940,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"hello",helloCommand,-1, {"hello",helloCommand,-1,
"no-auth no-script fast no-monitor ok-loading ok-stale no-slowlog @connection", "no-auth no-script fast no-monitor ok-loading ok-stale @connection",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
/* EVAL can modify the dataset, however it is not flagged as a write /* EVAL can modify the dataset, however it is not flagged as a write
@ -1126,7 +1126,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"acl",aclCommand,-2, {"acl",aclCommand,-2,
"admin no-script no-slowlog ok-loading ok-stale", "admin no-script ok-loading ok-stale",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"rreplay",replicaReplayCommand,-3, {"rreplay",replicaReplayCommand,-3,
@ -1177,6 +1177,11 @@ void processClients();
/* Low level logging. To use only for very big messages, otherwise /* Low level logging. To use only for very big messages, otherwise
* serverLog() is to prefer. */ * serverLog() is to prefer. */
#if defined(__has_feature)
# if __has_feature(thread_sanitizer)
__attribute__((no_sanitize("thread")))
# endif
#endif
void serverLogRaw(int level, const char *msg) { void serverLogRaw(int level, const char *msg) {
const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING }; const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING };
const char *c = ".-*# "; const char *c = ".-*# ";
@ -1225,12 +1230,15 @@ void serverLogRaw(int level, const char *msg) {
/* Like serverLogRaw() but with printf-alike support. This is the function that /* Like serverLogRaw() but with printf-alike support. This is the function that
* is used across the code. The raw version is only used in order to dump * is used across the code. The raw version is only used in order to dump
* the INFO output on crash. */ * the INFO output on crash. */
void serverLog(int level, const char *fmt, ...) { #if defined(__has_feature)
# if __has_feature(thread_sanitizer)
__attribute__((no_sanitize("thread")))
# endif
#endif
void _serverLog(int level, const char *fmt, ...) {
va_list ap; va_list ap;
char msg[LOG_MAX_LEN]; char msg[LOG_MAX_LEN];
if ((level&0xff) < cserver.verbosity) return;
va_start(ap, fmt); va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap); vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap); va_end(ap);
@ -1244,6 +1252,11 @@ void serverLog(int level, const char *fmt, ...) {
* We actually use this only for signals that are not fatal from the point * We actually use this only for signals that are not fatal from the point
* of view of Redis. Signals that are going to kill the server anyway and * of view of Redis. Signals that are going to kill the server anyway and
* where we need printf-alike features are served by serverLog(). */ * where we need printf-alike features are served by serverLog(). */
#if defined(__has_feature)
# if __has_feature(thread_sanitizer)
__attribute__((no_sanitize("thread")))
# endif
#endif
void serverLogFromHandler(int level, const char *msg) { void serverLogFromHandler(int level, const char *msg) {
int fd; int fd;
int log_to_stdout = g_pserver->logfile[0] == '\0'; int log_to_stdout = g_pserver->logfile[0] == '\0';
@ -1397,21 +1410,14 @@ uint64_t dictEncObjHash(const void *key) {
if (sdsEncodedObject(o)) { if (sdsEncodedObject(o)) {
return dictGenHashFunction(ptrFromObj(o), sdslen((sds)ptrFromObj(o))); return dictGenHashFunction(ptrFromObj(o), sdslen((sds)ptrFromObj(o)));
} else { } else if (o->encoding == OBJ_ENCODING_INT) {
if (o->encoding == OBJ_ENCODING_INT) {
char buf[32]; char buf[32];
int len; int len;
len = ll2string(buf,32,(long)ptrFromObj(o)); len = ll2string(buf,32,(long)ptrFromObj(o));
return dictGenHashFunction((unsigned char*)buf, len); return dictGenHashFunction((unsigned char*)buf, len);
} else { } else {
uint64_t hash; serverPanic("Unknown string encoding");
o = getDecodedObject(o);
hash = dictGenHashFunction(ptrFromObj(o), sdslen((sds)ptrFromObj(o)));
decrRefCount(o);
return hash;
}
} }
} }
@ -1678,6 +1684,7 @@ void resetChildState() {
g_pserver->child_type = CHILD_TYPE_NONE; g_pserver->child_type = CHILD_TYPE_NONE;
g_pserver->child_pid = -1; g_pserver->child_pid = -1;
g_pserver->stat_current_cow_bytes = 0; g_pserver->stat_current_cow_bytes = 0;
g_pserver->stat_current_cow_updated = 0;
g_pserver->stat_current_save_keys_processed = 0; g_pserver->stat_current_save_keys_processed = 0;
g_pserver->stat_module_progress = 0; g_pserver->stat_module_progress = 0;
g_pserver->stat_current_save_keys_total = 0; g_pserver->stat_current_save_keys_total = 0;
@ -1787,33 +1794,17 @@ int clientsCronResizeQueryBuffer(client *c) {
* When we want to know what was recently the peak memory usage, we just scan * When we want to know what was recently the peak memory usage, we just scan
* such few slots searching for the maximum value. */ * such few slots searching for the maximum value. */
#define CLIENTS_PEAK_MEM_USAGE_SLOTS 8 #define CLIENTS_PEAK_MEM_USAGE_SLOTS 8
size_t ClientsPeakMemInput[CLIENTS_PEAK_MEM_USAGE_SLOTS]; size_t ClientsPeakMemInput[CLIENTS_PEAK_MEM_USAGE_SLOTS] = {0};
size_t ClientsPeakMemOutput[CLIENTS_PEAK_MEM_USAGE_SLOTS]; size_t ClientsPeakMemOutput[CLIENTS_PEAK_MEM_USAGE_SLOTS] = {0};
int clientsCronTrackExpansiveClients(client *c) { int clientsCronTrackExpansiveClients(client *c, int time_idx) {
size_t in_usage = sdsZmallocSize(c->querybuf) + c->argv_len_sum + size_t in_usage = sdsZmallocSize(c->querybuf) + c->argv_len_sum +
(c->argv ? zmalloc_size(c->argv) : 0); (c->argv ? zmalloc_size(c->argv) : 0);
size_t out_usage = getClientOutputBufferMemoryUsage(c); size_t out_usage = getClientOutputBufferMemoryUsage(c);
int i = g_pserver->unixtime % CLIENTS_PEAK_MEM_USAGE_SLOTS;
int zeroidx = (i+1) % CLIENTS_PEAK_MEM_USAGE_SLOTS;
/* Always zero the next sample, so that when we switch to that second, we'll
* only register samples that are greater in that second without considering
* the history of such slot.
*
* Note: our index may jump to any random position if serverCron() is not
* called for some reason with the normal frequency, for instance because
* some slow command is called taking multiple seconds to execute. In that
* case our array may end containing data which is potentially older
* than CLIENTS_PEAK_MEM_USAGE_SLOTS seconds: however this is not a problem
* since here we want just to track if "recently" there were very expansive
* clients from the POV of memory usage. */
ClientsPeakMemInput[zeroidx] = 0;
ClientsPeakMemOutput[zeroidx] = 0;
/* Track the biggest values observed so far in this slot. */ /* Track the biggest values observed so far in this slot. */
if (in_usage > ClientsPeakMemInput[i]) ClientsPeakMemInput[i] = in_usage; if (in_usage > ClientsPeakMemInput[time_idx]) ClientsPeakMemInput[time_idx] = in_usage;
if (out_usage > ClientsPeakMemOutput[i]) ClientsPeakMemOutput[i] = out_usage; if (out_usage > ClientsPeakMemOutput[time_idx]) ClientsPeakMemOutput[time_idx] = out_usage;
return 0; /* This function never terminates the client. */ return 0; /* This function never terminates the client. */
} }
@ -1886,6 +1877,24 @@ void clientsCron(int iel) {
iterations = (numclients < CLIENTS_CRON_MIN_ITERATIONS) ? iterations = (numclients < CLIENTS_CRON_MIN_ITERATIONS) ?
numclients : CLIENTS_CRON_MIN_ITERATIONS; numclients : CLIENTS_CRON_MIN_ITERATIONS;
int curr_peak_mem_usage_slot = g_pserver->unixtime % CLIENTS_PEAK_MEM_USAGE_SLOTS;
/* Always zero the next sample, so that when we switch to that second, we'll
* only register samples that are greater in that second without considering
* the history of such slot.
*
* Note: our index may jump to any random position if serverCron() is not
* called for some reason with the normal frequency, for instance because
* some slow command is called taking multiple seconds to execute. In that
* case our array may end containing data which is potentially older
* than CLIENTS_PEAK_MEM_USAGE_SLOTS seconds: however this is not a problem
* since here we want just to track if "recently" there were very expansive
* clients from the POV of memory usage. */
int zeroidx = (curr_peak_mem_usage_slot+1) % CLIENTS_PEAK_MEM_USAGE_SLOTS;
ClientsPeakMemInput[zeroidx] = 0;
ClientsPeakMemOutput[zeroidx] = 0;
while(listLength(g_pserver->clients) && iterations--) { while(listLength(g_pserver->clients) && iterations--) {
client *c; client *c;
listNode *head; listNode *head;
@ -1904,7 +1913,7 @@ void clientsCron(int iel) {
* terminated. */ * terminated. */
if (clientsCronHandleTimeout(c,now)) continue; // Client free'd so don't release the lock if (clientsCronHandleTimeout(c,now)) continue; // Client free'd so don't release the lock
if (clientsCronResizeQueryBuffer(c)) goto LContinue; if (clientsCronResizeQueryBuffer(c)) goto LContinue;
if (clientsCronTrackExpansiveClients(c)) goto LContinue; if (clientsCronTrackExpansiveClients(c, curr_peak_mem_usage_slot)) goto LContinue;
if (clientsCronTrackClientsMemUsage(c)) goto LContinue; if (clientsCronTrackClientsMemUsage(c)) goto LContinue;
LContinue: LContinue:
fastlock_unlock(&c->lock); fastlock_unlock(&c->lock);
@ -2013,11 +2022,11 @@ void updateCachedTime(int update_daylight_info) {
} }
void checkChildrenDone(void) { void checkChildrenDone(void) {
int statloc; int statloc = 0;
pid_t pid; pid_t pid;
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) { if ((pid = waitpid(-1, &statloc, WNOHANG)) != 0) {
int exitcode = WEXITSTATUS(statloc); int exitcode = WIFEXITED(statloc) ? WEXITSTATUS(statloc) : -1;
int bysignal = 0; int bysignal = 0;
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc); if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
@ -2025,15 +2034,14 @@ void checkChildrenDone(void) {
/* sigKillChildHandler catches the signal and calls exit(), but we /* sigKillChildHandler catches the signal and calls exit(), but we
* must make sure not to flag lastbgsave_status, etc incorrectly. * must make sure not to flag lastbgsave_status, etc incorrectly.
* We could directly terminate the child process via SIGUSR1 * We could directly terminate the child process via SIGUSR1
* without handling it, but in this case Valgrind will log an * without handling it */
* annoying error. */
if (exitcode == SERVER_CHILD_NOERROR_RETVAL) { if (exitcode == SERVER_CHILD_NOERROR_RETVAL) {
bysignal = SIGUSR1; bysignal = SIGUSR1;
exitcode = 1; exitcode = 1;
} }
if (pid == -1) { if (pid == -1) {
serverLog(LL_WARNING,"wait3() returned an error: %s. " serverLog(LL_WARNING,"waitpid() returned an error: %s. "
"child_type: %s, child_pid = %d", "child_type: %s, child_pid = %d",
strerror(errno), strerror(errno),
strChildType(g_pserver->child_type), strChildType(g_pserver->child_type),
@ -2504,8 +2512,9 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
if (ProcessingEventsWhileBlocked) { if (ProcessingEventsWhileBlocked) {
uint64_t processed = 0; uint64_t processed = 0;
processed += tlsProcessPendingData(); processed += tlsProcessPendingData();
int aof_state = g_pserver->aof_state;
locker.disarm(); locker.disarm();
processed += handleClientsWithPendingWrites(iel, g_pserver->aof_state); processed += handleClientsWithPendingWrites(iel, aof_state);
locker.arm(); locker.arm();
processed += freeClientsInAsyncFreeQueue(iel); processed += freeClientsInAsyncFreeQueue(iel);
g_pserver->events_processed_while_blocked += processed; g_pserver->events_processed_while_blocked += processed;
@ -2867,6 +2876,7 @@ void initServerConfig(void) {
g_pserver->aof_rewrite_scheduled = 0; g_pserver->aof_rewrite_scheduled = 0;
g_pserver->aof_flush_sleep = 0; g_pserver->aof_flush_sleep = 0;
g_pserver->aof_last_fsync = time(NULL); g_pserver->aof_last_fsync = time(NULL);
atomicSet(g_pserver->aof_bio_fsync_status,C_OK);
g_pserver->aof_rewrite_time_last = -1; g_pserver->aof_rewrite_time_last = -1;
g_pserver->aof_rewrite_time_start = -1; g_pserver->aof_rewrite_time_start = -1;
g_pserver->aof_lastbgrewrite_status = C_OK; g_pserver->aof_lastbgrewrite_status = C_OK;
@ -3275,14 +3285,15 @@ int listenToPort(int port, socketFds *sfd, int fReusePort, int fFirstListen) {
sfd->fd[sfd->count] = anetTcpServer(serverTL->neterr,port,addr,g_pserver->tcp_backlog,fReusePort,fFirstListen); sfd->fd[sfd->count] = anetTcpServer(serverTL->neterr,port,addr,g_pserver->tcp_backlog,fReusePort,fFirstListen);
} }
if (sfd->fd[sfd->count] == ANET_ERR) { if (sfd->fd[sfd->count] == ANET_ERR) {
int net_errno = errno;
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Could not create server TCP listening socket %s:%d: %s", "Warning: Could not create server TCP listening socket %s:%d: %s",
addr, port, serverTL->neterr); addr, port, serverTL->neterr);
if (errno == EADDRNOTAVAIL && optional) if (net_errno == EADDRNOTAVAIL && optional)
continue; continue;
if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT || if (net_errno == ENOPROTOOPT || net_errno == EPROTONOSUPPORT ||
errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT || net_errno == ESOCKTNOSUPPORT || net_errno == EPFNOSUPPORT ||
errno == EAFNOSUPPORT) net_errno == EAFNOSUPPORT)
continue; continue;
/* Rollback successful listens before exiting */ /* Rollback successful listens before exiting */
@ -3354,12 +3365,16 @@ static void initNetworkingThread(int iel, int fReusePort)
if (fReusePort || (iel == IDX_EVENT_LOOP_MAIN)) if (fReusePort || (iel == IDX_EVENT_LOOP_MAIN))
{ {
if (g_pserver->port != 0 && if (g_pserver->port != 0 &&
listenToPort(g_pserver->port,&g_pserver->rgthreadvar[iel].ipfd, fReusePort, (iel == IDX_EVENT_LOOP_MAIN)) == C_ERR) listenToPort(g_pserver->port,&g_pserver->rgthreadvar[iel].ipfd, fReusePort, (iel == IDX_EVENT_LOOP_MAIN)) == C_ERR) {
serverLog(LL_WARNING, "Failed listening on port %u (TCP), aborting.", g_pserver->port);
exit(1); exit(1);
}
if (g_pserver->tls_port != 0 && if (g_pserver->tls_port != 0 &&
listenToPort(g_pserver->tls_port,&g_pserver->rgthreadvar[iel].tlsfd, fReusePort, (iel == IDX_EVENT_LOOP_MAIN)) == C_ERR) listenToPort(g_pserver->tls_port,&g_pserver->rgthreadvar[iel].tlsfd, fReusePort, (iel == IDX_EVENT_LOOP_MAIN)) == C_ERR) {
serverLog(LL_WARNING, "Failed listening on port %u (TLS), aborting.", g_pserver->port);
exit(1); exit(1);
} }
}
else else
{ {
// We use the main threads file descriptors // We use the main threads file descriptors
@ -3516,6 +3531,7 @@ void initServer(void) {
g_pserver->paused_clients = listCreate(); g_pserver->paused_clients = listCreate();
g_pserver->events_processed_while_blocked = 0; g_pserver->events_processed_while_blocked = 0;
g_pserver->blocked_last_cron = 0; g_pserver->blocked_last_cron = 0;
g_pserver->replication_allowed = 1;
g_pserver->blocking_op_nesting = 0; g_pserver->blocking_op_nesting = 0;
@ -3560,6 +3576,7 @@ void initServer(void) {
cserver.stat_starttime = time(NULL); cserver.stat_starttime = time(NULL);
g_pserver->stat_peak_memory = 0; g_pserver->stat_peak_memory = 0;
g_pserver->stat_current_cow_bytes = 0; g_pserver->stat_current_cow_bytes = 0;
g_pserver->stat_current_cow_updated = 0;
g_pserver->stat_current_save_keys_processed = 0; g_pserver->stat_current_save_keys_processed = 0;
g_pserver->stat_current_save_keys_total = 0; g_pserver->stat_current_save_keys_total = 0;
g_pserver->stat_rdb_cow_bytes = 0; g_pserver->stat_rdb_cow_bytes = 0;
@ -3792,6 +3809,7 @@ void redisOpArrayFree(redisOpArray *oa) {
zfree(op->argv); zfree(op->argv);
} }
zfree(oa->ops); zfree(oa->ops);
oa->ops = NULL;
} }
/* ====================== Commands lookup and execution ===================== */ /* ====================== Commands lookup and execution ===================== */
@ -3843,6 +3861,9 @@ void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
int flags) int flags)
{ {
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
if (!g_pserver->replication_allowed)
return;
/* Propagate a MULTI request once we encounter the first command which /* Propagate a MULTI request once we encounter the first command which
* is a write command. * is a write command.
* This way we'll deliver the MULTI/..../EXEC block as a whole and * This way we'll deliver the MULTI/..../EXEC block as a whole and
@ -3905,6 +3926,12 @@ void preventCommandPropagation(client *c) {
c->flags |= CLIENT_PREVENT_PROP; c->flags |= CLIENT_PREVENT_PROP;
} }
/* Avoid logging any information about this client's arguments
* since they contain sensitive information. */
void preventCommandLogging(client *c) {
c->flags |= CLIENT_PREVENT_LOGGING;
}
/* AOF specific version of preventCommandPropagation(). */ /* AOF specific version of preventCommandPropagation(). */
void preventCommandAOF(client *c) { void preventCommandAOF(client *c) {
c->flags |= CLIENT_PREVENT_AOF_PROP; c->flags |= CLIENT_PREVENT_AOF_PROP;
@ -3915,6 +3942,19 @@ void preventCommandReplication(client *c) {
c->flags |= CLIENT_PREVENT_REPL_PROP; c->flags |= CLIENT_PREVENT_REPL_PROP;
} }
/* Log the last command a client executed into the slowlog. */
void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t duration) {
/* Some commands may contain sensitive data that should not be available in the slowlog. */
if ((c->flags & CLIENT_PREVENT_LOGGING) || (cmd->flags & CMD_SKIP_SLOWLOG))
return;
/* If command argument vector was rewritten, use the original
* arguments. */
robj **argv = c->original_argv ? c->original_argv : c->argv;
int argc = c->original_argv ? c->original_argc : c->argc;
slowlogPushEntryIfNeeded(c,argv,argc,duration);
}
/* Call() is the core of Redis execution of a command. /* Call() is the core of Redis execution of a command.
* *
* The following flags can be passed: * The following flags can be passed:
@ -4035,27 +4075,31 @@ void call(client *c, int flags) {
g_pserver->lua_caller->flags |= CLIENT_FORCE_AOF; g_pserver->lua_caller->flags |= CLIENT_FORCE_AOF;
} }
/* Log the command into the Slow log if needed, and populate the /* Note: the code below uses the real command that was executed
* per-command statistics that we show in INFO commandstats. */ * c->cmd and c->lastcmd may be different, in case of MULTI-EXEC or
if (flags & CMD_CALL_SLOWLOG && !(c->cmd->flags & CMD_SKIP_SLOWLOG)) { * re-written commands such as EXPIRE, GEOADD, etc. */
const char *latency_event = (c->cmd->flags & CMD_FAST) ?
/* Record the latency this command induced on the main thread.
* unless instructed by the caller not to log. (happens when processing
* a MULTI-EXEC from inside an AOF). */
if (flags & CMD_CALL_SLOWLOG) {
const char *latency_event = (real_cmd->flags & CMD_FAST) ?
"fast-command" : "command"; "fast-command" : "command";
latencyAddSampleIfNeeded(latency_event,duration/1000); latencyAddSampleIfNeeded(latency_event,duration/1000);
/* If command argument vector was rewritten, use the original
* arguments. */
robj **argv = c->original_argv ? c->original_argv : c->argv;
int argc = c->original_argv ? c->original_argc : c->argc;
/* If the client is blocked we will handle slowlog when it is unblocked . */
if (!(c->flags & CLIENT_BLOCKED)) {
slowlogPushEntryIfNeeded(c,argv,argc,duration);
}
} }
/* Log the command into the Slow log if needed.
* If the client is blocked we will handle slowlog when it is unblocked. */
if ((flags & CMD_CALL_SLOWLOG) && !(c->flags & CLIENT_BLOCKED))
slowlogPushCurrentCommand(c, real_cmd, duration);
/* Clear the original argv.
* If the client is blocked we will handle slowlog when it is unblocked. */
if (!(c->flags & CLIENT_BLOCKED))
freeClientOriginalArgv(c); freeClientOriginalArgv(c);
/* populate the per-command statistics that we show in INFO commandstats. */
if (flags & CMD_CALL_STATS) { if (flags & CMD_CALL_STATS) {
/* use the real command that was executed (cmd and lastamc) may be
* different, in case of MULTI-EXEC or re-written commands such as
* EXPIRE, GEOADD, etc. */
real_cmd->microseconds += duration; real_cmd->microseconds += duration;
real_cmd->calls++; real_cmd->calls++;
} }
@ -4227,6 +4271,15 @@ static int cmdHasMovableKeys(struct redisCommand *cmd) {
int processCommand(client *c, int callFlags) { int processCommand(client *c, int callFlags) {
AssertCorrectThread(c); AssertCorrectThread(c);
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
if (!g_pserver->lua_timedout) {
/* Both EXEC and EVAL call call() directly so there should be
* no way in_exec or in_eval or propagate_in_transaction is 1.
* That is unless lua_timedout, in which case client may run
* some commands. */
serverAssert(!g_pserver->propagate_in_transaction);
serverAssert(!serverTL->in_exec);
serverAssert(!serverTL->in_eval);
}
if (moduleHasCommandFilters()) if (moduleHasCommandFilters())
{ {
@ -4289,18 +4342,30 @@ int processCommand(client *c, int callFlags) {
/* Check if the user can run this command according to the current /* Check if the user can run this command according to the current
* ACLs. */ * ACLs. */
int acl_keypos; int acl_errpos;
int acl_retval = ACLCheckCommandPerm(c,&acl_keypos); int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
if (acl_retval != ACL_OK) { if (acl_retval != ACL_OK) {
addACLLogEntry(c,acl_retval,acl_keypos,NULL); addACLLogEntry(c,acl_retval,acl_errpos,NULL);
if (acl_retval == ACL_DENIED_CMD) switch (acl_retval) {
case ACL_DENIED_CMD:
rejectCommandFormat(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 break;
case ACL_DENIED_KEY:
rejectCommandFormat(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");
break;
case ACL_DENIED_CHANNEL:
rejectCommandFormat(c,
"-NOPERM this user has no permissions to access "
"one of the channels used as arguments");
break;
default:
rejectCommandFormat(c, "no permission");
break;
}
return C_OK; return C_OK;
} }
@ -4464,6 +4529,7 @@ int processCommand(client *c, int callFlags) {
c->cmd->proc != discardCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != watchCommand && c->cmd->proc != watchCommand &&
c->cmd->proc != unwatchCommand && c->cmd->proc != unwatchCommand &&
c->cmd->proc != resetCommand &&
!(c->cmd->proc == shutdownCommand && !(c->cmd->proc == shutdownCommand &&
c->argc == 2 && c->argc == 2 &&
tolower(((char*)ptrFromObj(c->argv[1]))[0]) == 'n') && tolower(((char*)ptrFromObj(c->argv[1]))[0]) == 'n') &&
@ -4620,7 +4686,10 @@ int prepareForShutdown(int flags) {
/* Append only file: flush buffers and fsync() the AOF at exit */ /* Append only file: flush buffers and fsync() the AOF at exit */
serverLog(LL_NOTICE,"Calling fsync() on the AOF file."); serverLog(LL_NOTICE,"Calling fsync() on the AOF file.");
flushAppendOnlyFile(1); flushAppendOnlyFile(1);
redis_fsync(g_pserver->aof_fd); if (redis_fsync(g_pserver->aof_fd) == -1) {
serverLog(LL_WARNING,"Fail to fsync the AOF file: %s.",
strerror(errno));
}
} }
/* Create a new RDB file before exiting. */ /* Create a new RDB file before exiting. */
@ -4694,13 +4763,20 @@ int writeCommandsDeniedByDiskError(void) {
g_pserver->lastbgsave_status == C_ERR) g_pserver->lastbgsave_status == C_ERR)
{ {
return DISK_ERROR_TYPE_RDB; return DISK_ERROR_TYPE_RDB;
} else if (g_pserver->aof_state != AOF_OFF && } else if (g_pserver->aof_state != AOF_OFF) {
g_pserver->aof_last_write_status == C_ERR) if (g_pserver->aof_last_write_status == C_ERR) {
{
return DISK_ERROR_TYPE_AOF; return DISK_ERROR_TYPE_AOF;
} else {
return DISK_ERROR_TYPE_NONE;
} }
/* AOF fsync error. */
int aof_bio_fsync_status;
atomicGet(g_pserver->aof_bio_fsync_status,aof_bio_fsync_status);
if (aof_bio_fsync_status == C_ERR) {
atomicGet(g_pserver->aof_bio_fsync_errno,g_pserver->aof_last_write_errno);
return DISK_ERROR_TYPE_AOF;
}
}
return DISK_ERROR_TYPE_NONE;
} }
/* The PING command. It works in a different way if the client is in /* The PING command. It works in a different way if the client is in
@ -5165,12 +5241,15 @@ sds genRedisInfoString(const char *section) {
} else if (g_pserver->stat_current_save_keys_total) { } else if (g_pserver->stat_current_save_keys_total) {
fork_perc = ((double)g_pserver->stat_current_save_keys_processed / g_pserver->stat_current_save_keys_total) * 100; fork_perc = ((double)g_pserver->stat_current_save_keys_processed / g_pserver->stat_current_save_keys_total) * 100;
} }
int aof_bio_fsync_status;
atomicGet(g_pserver->aof_bio_fsync_status,aof_bio_fsync_status);
info = sdscatprintf(info, info = sdscatprintf(info,
"# Persistence\r\n" "# Persistence\r\n"
"loading:%d\r\n" "loading:%d\r\n"
"current_cow_size:%zu\r\n" "current_cow_size:%zu\r\n"
"current_fork_perc:%.2f%%\r\n" "current_cow_size_age:%lu\r\n"
"current_fork_perc:%.2f\r\n"
"current_save_keys_processed:%zu\r\n" "current_save_keys_processed:%zu\r\n"
"current_save_keys_total:%zu\r\n" "current_save_keys_total:%zu\r\n"
"rdb_changes_since_last_save:%lld\r\n" "rdb_changes_since_last_save:%lld\r\n"
@ -5192,6 +5271,7 @@ sds genRedisInfoString(const char *section) {
"module_fork_last_cow_size:%zu\r\n", "module_fork_last_cow_size:%zu\r\n",
!!g_pserver->loading.load(std::memory_order_relaxed), /* Note: libraries expect 1 or 0 here so coerce our enum */ !!g_pserver->loading.load(std::memory_order_relaxed), /* Note: libraries expect 1 or 0 here so coerce our enum */
g_pserver->stat_current_cow_bytes, g_pserver->stat_current_cow_bytes,
g_pserver->stat_current_cow_updated ? (unsigned long) elapsedMs(g_pserver->stat_current_cow_updated) / 1000 : 0,
fork_perc, fork_perc,
g_pserver->stat_current_save_keys_processed, g_pserver->stat_current_save_keys_processed,
g_pserver->stat_current_save_keys_total, g_pserver->stat_current_save_keys_total,
@ -5210,7 +5290,8 @@ sds genRedisInfoString(const char *section) {
(intmax_t)((g_pserver->child_type != CHILD_TYPE_AOF) ? (intmax_t)((g_pserver->child_type != CHILD_TYPE_AOF) ?
-1 : time(NULL)-g_pserver->aof_rewrite_time_start), -1 : time(NULL)-g_pserver->aof_rewrite_time_start),
(g_pserver->aof_lastbgrewrite_status == C_OK) ? "ok" : "err", (g_pserver->aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
(g_pserver->aof_last_write_status == C_OK) ? "ok" : "err", (g_pserver->aof_last_write_status == C_OK &&
aof_bio_fsync_status == C_OK) ? "ok" : "err",
g_pserver->stat_aof_cow_bytes, g_pserver->stat_aof_cow_bytes,
g_pserver->child_type == CHILD_TYPE_MODULE, g_pserver->child_type == CHILD_TYPE_MODULE,
g_pserver->stat_module_cow_bytes); g_pserver->stat_module_cow_bytes);
@ -5430,15 +5511,18 @@ sds genRedisInfoString(const char *section) {
if (mi->repl_state != REPL_STATE_CONNECTED) { if (mi->repl_state != REPL_STATE_CONNECTED) {
info = sdscatprintf(info, info = sdscatprintf(info,
"master%s_link_down_since_seconds:%jd\r\n", "master%s_link_down_since_seconds:%jd\r\n",
master_prefix, (intmax_t)g_pserver->unixtime-mi->repl_down_since); master_prefix, mi->repl_down_since ?
(intmax_t)(g_pserver->unixtime-mi->repl_down_since) : -1);
} }
++cmasters; ++cmasters;
} }
info = sdscatprintf(info, info = sdscatprintf(info,
"slave_priority:%d\r\n" "slave_priority:%d\r\n"
"slave_read_only:%d\r\n", "slave_read_only:%d\r\n"
"replica_announced:%d\r\n",
g_pserver->slave_priority, g_pserver->slave_priority,
g_pserver->repl_slave_ro); g_pserver->repl_slave_ro,
g_pserver->replica_announced);
} }
info = sdscatprintf(info, info = sdscatprintf(info,
@ -6168,6 +6252,7 @@ int redisFork(int purpose) {
g_pserver->child_pid = childpid; g_pserver->child_pid = childpid;
g_pserver->child_type = purpose; g_pserver->child_type = purpose;
g_pserver->stat_current_cow_bytes = 0; g_pserver->stat_current_cow_bytes = 0;
g_pserver->stat_current_cow_updated = 0;
g_pserver->stat_current_save_keys_processed = 0; g_pserver->stat_current_save_keys_processed = 0;
g_pserver->stat_module_progress = 0; g_pserver->stat_module_progress = 0;
g_pserver->stat_current_save_keys_total = dbTotalServerKeyCount(); g_pserver->stat_current_save_keys_total = dbTotalServerKeyCount();
@ -6502,6 +6587,36 @@ int iAmMaster(void) {
(g_pserver->cluster_enabled && nodeIsMaster(g_pserver->cluster->myself))); (g_pserver->cluster_enabled && nodeIsMaster(g_pserver->cluster->myself)));
} }
#ifdef REDIS_TEST
typedef int redisTestProc(int argc, char **argv, int accurate);
struct redisTest {
char *name;
redisTestProc *proc;
int failed;
} redisTests[] = {
{"ziplist", ziplistTest},
{"quicklist", quicklistTest},
{"intset", intsetTest},
{"zipmap", zipmapTest},
{"sha1test", sha1Test},
{"util", utilTest},
{"endianconv", endianconvTest},
{"crc64", crc64Test},
{"zmalloc", zmalloc_test},
{"sds", sdsTest},
{"dict", dictTest}
};
redisTestProc *getTestProcByName(const char *name) {
int numtests = sizeof(redisTests)/sizeof(struct redisTest);
for (int j = 0; j < numtests; j++) {
if (!strcasecmp(name,redisTests[j].name)) {
return redisTests[j].proc;
}
}
return NULL;
}
#endif
int main(int argc, char **argv) { int main(int argc, char **argv) {
struct timeval tv; struct timeval tv;
int j; int j;
@ -6514,30 +6629,42 @@ int main(int argc, char **argv) {
#endif #endif
#ifdef REDIS_TEST #ifdef REDIS_TEST
if (argc == 3 && !strcasecmp(argv[1], "test")) { if (argc >= 3 && !strcasecmp(argv[1], "test")) {
if (!strcasecmp(argv[2], "ziplist")) { int accurate = 0;
return ziplistTest(argc, argv); for (j = 3; j < argc; j++) {
} else if (!strcasecmp(argv[2], "quicklist")) { if (!strcasecmp(argv[j], "--accurate")) {
quicklistTest(argc, argv); accurate = 1;
} else if (!strcasecmp(argv[2], "intset")) { }
return intsetTest(argc, argv);
} else if (!strcasecmp(argv[2], "zipmap")) {
return zipmapTest(argc, argv);
} else if (!strcasecmp(argv[2], "sha1test")) {
return sha1Test(argc, argv);
} else if (!strcasecmp(argv[2], "util")) {
return utilTest(argc, argv);
} else if (!strcasecmp(argv[2], "endianconv")) {
return endianconvTest(argc, argv);
} else if (!strcasecmp(argv[2], "crc64")) {
return crc64Test(argc, argv);
} else if (!strcasecmp(argv[2], "zmalloc")) {
return zmalloc_test(argc, argv);
} else if (!strcasecmp(argv[2], "sds")) {
return sdsTest(argc, argv);
} }
return -1; /* test not found */ if (!strcasecmp(argv[2], "all")) {
int numtests = sizeof(redisTests)/sizeof(struct redisTest);
for (j = 0; j < numtests; j++) {
redisTests[j].failed = (redisTests[j].proc(argc,argv,accurate) != 0);
}
/* Report tests result */
int failed_num = 0;
for (j = 0; j < numtests; j++) {
if (redisTests[j].failed) {
failed_num++;
printf("[failed] Test - %s\n", redisTests[j].name);
} else {
printf("[ok] Test - %s\n", redisTests[j].name);
}
}
printf("%d tests, %d passed, %d failed\n", numtests,
numtests-failed_num, failed_num);
return failed_num == 0 ? 0 : 1;
} else {
redisTestProc *proc = getTestProcByName(argv[2]);
if (!proc) return -1; /* test not found */
return proc(argc,argv,accurate);
}
return 0;
} }
#endif #endif
@ -6628,7 +6755,6 @@ int main(int argc, char **argv) {
cserver.exec_argv[1] = zstrdup(cserver.configfile); cserver.exec_argv[1] = zstrdup(cserver.configfile);
j = 2; // Skip this arg when parsing options j = 2; // Skip this arg when parsing options
} }
while(j < argc) { while(j < argc) {
/* Either first or last argument - Should we read config from stdin? */ /* Either first or last argument - Should we read config from stdin? */
if (argv[j][0] == '-' && argv[j][1] == '\0' && (j == 1 || j == argc-1)) { if (argv[j][0] == '-' && argv[j][1] == '\0' && (j == 1 || j == argc-1)) {
@ -6651,11 +6777,6 @@ int main(int argc, char **argv) {
j++; j++;
} }
if (g_pserver->sentinel_mode && ! cserver.configfile) {
serverLog(LL_WARNING,
"Sentinel needs config file on disk to save state. Exiting...");
exit(1);
}
loadServerConfig(cserver.configfile, config_from_stdin, options); loadServerConfig(cserver.configfile, config_from_stdin, options);
if (g_pserver->sentinel_mode) loadSentinelConfigFromQueue(); if (g_pserver->sentinel_mode) loadSentinelConfigFromQueue();
sdsfree(options); sdsfree(options);
@ -6670,6 +6791,7 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (g_pserver->sentinel_mode) sentinelCheckConfigFile();
cserver.supervised = redisIsSupervised(cserver.supervised_mode); cserver.supervised = redisIsSupervised(cserver.supervised_mode);
int background = cserver.daemonize && !cserver.supervised; int background = cserver.daemonize && !cserver.supervised;
if (background) daemonize(); if (background) daemonize();
@ -6684,7 +6806,7 @@ int main(int argc, char **argv) {
(int)getpid()); (int)getpid());
if (argc == 1) { if (argc == 1) {
serverLog(LL_WARNING, "WARNING: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], g_pserver->sentinel_mode ? "sentinel" : "keydb"); serverLog(LL_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/keydb.conf", argv[0]);
} else { } else {
serverLog(LL_WARNING, "Configuration loaded"); serverLog(LL_WARNING, "Configuration loaded");
} }
@ -6765,10 +6887,10 @@ int main(int argc, char **argv) {
if (cserver.supervised_mode == SUPERVISED_SYSTEMD) { if (cserver.supervised_mode == SUPERVISED_SYSTEMD) {
if (!listLength(g_pserver->masters)) { if (!listLength(g_pserver->masters)) {
redisCommunicateSystemd("STATUS=Ready to accept connections\n"); redisCommunicateSystemd("STATUS=Ready to accept connections\n");
redisCommunicateSystemd("READY=1\n");
} else { } else {
redisCommunicateSystemd("STATUS=Waiting for MASTER <-> REPLICA sync\n"); redisCommunicateSystemd("STATUS=Ready to accept connections in read-only mode. Waiting for MASTER <-> REPLICA sync\n");
} }
redisCommunicateSystemd("READY=1\n");
} }
} else { } else {
ACLLoadUsersAtStartup(); ACLLoadUsersAtStartup();

View File

@ -319,6 +319,11 @@ inline bool operator!=(const void *p, const robj_sharedptr &rhs)
* special code. */ * special code. */
#define SERVER_CHILD_NOERROR_RETVAL 255 #define SERVER_CHILD_NOERROR_RETVAL 255
/* Reading copy-on-write info is sometimes expensive and may slow down child
* processes that report it continuously. We measure the cost of obtaining it
* and hold back additional reading based on this factor. */
#define CHILD_COW_DUTY_CYCLE 100
/* Instantaneous metrics tracking. */ /* Instantaneous metrics tracking. */
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */ #define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */ #define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
@ -465,7 +470,8 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
and AOF client */ and AOF client */
#define CLIENT_REPL_RDBONLY (1ULL<<42) /* This client is a replica that only wants #define CLIENT_REPL_RDBONLY (1ULL<<42) /* This client is a replica that only wants
RDB without replication buffer. */ RDB without replication buffer. */
#define CLIENT_FORCE_REPLY (1ULL<<43) /* Should addReply be forced to write the text? */ #define CLIENT_PREVENT_LOGGING (1ULL<<43) /* Prevent logging of command to slowlog */
#define CLIENT_FORCE_REPLY (1ULL<<44) /* Should addReply be forced to write the text? */
/* Client block type (btype field in client structure) /* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */ * if CLIENT_BLOCKED flag is set. */
@ -676,7 +682,8 @@ typedef enum {
#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_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_MODULE (1<<13) /* d, module key space notification */
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE) /* A flag */
/* Get the first bind addr or NULL */ /* Get the first bind addr or NULL */
#define NET_FIRST_BIND_ADDR (g_pserver->bindaddr_count ? g_pserver->bindaddr[0] : NULL) #define NET_FIRST_BIND_ADDR (g_pserver->bindaddr_count ? g_pserver->bindaddr[0] : NULL)
@ -1011,8 +1018,6 @@ typedef struct multiState {
int cmd_inv_flags; /* Same as cmd_flags, OR-ing the ~flags. so that it 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 is possible to know if all the commands have a
certain flag. */ certain flag. */
int minreplicas; /* MINREPLICAS for synchronous replication */
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
} multiState; } multiState;
struct listPos { struct listPos {
@ -1041,7 +1046,6 @@ typedef struct blockingState {
size_t xread_count; /* XREAD COUNT option. */ size_t xread_count; /* XREAD COUNT option. */
robj *xread_group; /* XREADGROUP group name. */ robj *xread_group; /* XREADGROUP group name. */
robj *xread_consumer; /* XREADGROUP consumer name. */ robj *xread_consumer; /* XREADGROUP consumer name. */
mstime_t xread_retry_time, xread_retry_ttl;
int xread_group_noack; int xread_group_noack;
/* BLOCKED_WAIT */ /* BLOCKED_WAIT */
@ -1120,7 +1124,7 @@ typedef struct {
the flag ALLKEYS is set in the user. */ the flag ALLKEYS is set in the user. */
list *channels; /* A list of allowed Pub/Sub channel patterns. If this list *channels; /* A list of allowed Pub/Sub channel patterns. If this
field is NULL the user cannot mention any channel in a field is NULL the user cannot mention any channel in a
`PUBLISH` or [P][UNSUSBSCRIBE] command, unless the flag `PUBLISH` or [P][UNSUBSCRIBE] command, unless the flag
ALLCHANNELS is set in the user. */ ALLCHANNELS is set in the user. */
} user; } user;
@ -1178,8 +1182,9 @@ typedef struct client {
sds replpreamble; /* Replication DB preamble. */ sds replpreamble; /* Replication DB preamble. */
long long read_reploff; /* Read replication offset if this is a master. */ long long read_reploff; /* Read replication offset if this is a master. */
long long reploff; /* Applied replication offset if this is a master. */ long long reploff; /* Applied replication offset if this is a master. */
long long repl_ack_off; /* Replication ack offset, if this is a replica. */ long long repl_ack_off; /* Replication ack offset, if this is a slave. */
long long repl_ack_time;/* Replication ack time, if this is a replica. */ long long repl_ack_time;/* Replication ack time, if this is a slave. */
long long repl_last_partial_write; /* The last time the server did a partial write from the RDB child pipe to this replica */
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
copying this replica output buffer copying this replica output buffer
should use. */ should use. */
@ -1426,8 +1431,10 @@ typedef struct socketFds {
typedef struct redisTLSContextConfig { typedef struct redisTLSContextConfig {
char *cert_file; /* Server side and optionally client side cert file name */ char *cert_file; /* Server side and optionally client side cert file name */
char *key_file; /* Private key filename for cert_file */ char *key_file; /* Private key filename for cert_file */
char *key_file_pass; /* Optional password for key_file */
char *client_cert_file; /* Certificate to use as a client; if none, use cert_file */ char *client_cert_file; /* Certificate to use as a client; if none, use cert_file */
char *client_key_file; /* Private key filename for client_cert_file */ char *client_key_file; /* Private key filename for client_cert_file */
char *client_key_file_pass; /* Optional password for client_key_file */
char *dh_params_file; char *dh_params_file;
char *ca_cert_file; char *ca_cert_file;
char *ca_cert_dir; char *ca_cert_dir;
@ -1685,6 +1692,7 @@ struct redisServer {
std::atomic<long long> stat_net_input_bytes; /* Bytes read from network. */ std::atomic<long long> stat_net_input_bytes; /* Bytes read from network. */
std::atomic<long long> stat_net_output_bytes; /* Bytes written to network. */ std::atomic<long long> stat_net_output_bytes; /* Bytes written to network. */
size_t stat_current_cow_bytes; /* Copy on write bytes while child is active. */ size_t stat_current_cow_bytes; /* Copy on write bytes while child is active. */
monotime stat_current_cow_updated; /* Last update time of stat_current_cow_bytes */
size_t stat_current_save_keys_processed; /* Processed keys while child is active. */ size_t stat_current_save_keys_processed; /* Processed keys while child is active. */
size_t stat_current_save_keys_total; /* Number of keys when child started. */ size_t stat_current_save_keys_total; /* Number of keys when child started. */
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */ size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
@ -1731,9 +1739,11 @@ struct redisServer {
int aof_rewrite_incremental_fsync;/* fsync incrementally while aof rewriting? */ int aof_rewrite_incremental_fsync;/* fsync incrementally while aof rewriting? */
int rdb_save_incremental_fsync; /* fsync incrementally while rdb saving? */ int rdb_save_incremental_fsync; /* fsync incrementally while rdb saving? */
int aof_last_write_status; /* C_OK or C_ERR */ int aof_last_write_status; /* C_OK or C_ERR */
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */ int aof_last_write_errno; /* Valid if aof write/fsync status is ERR */
int aof_load_truncated; /* Don't stop on unexpected AOF EOF. */ int aof_load_truncated; /* Don't stop on unexpected AOF EOF. */
int aof_use_rdb_preamble; /* Use RDB preamble on AOF rewrites. */ int aof_use_rdb_preamble; /* Use RDB preamble on AOF rewrites. */
redisAtomic int aof_bio_fsync_status; /* Status of AOF fsync in bio job. */
redisAtomic int aof_bio_fsync_errno; /* Errno of AOF fsync in bio job. */
/* AOF pipes used to communicate between parent and child during rewrite. */ /* AOF pipes used to communicate between parent and child during rewrite. */
int aof_pipe_write_data_to_child; int aof_pipe_write_data_to_child;
int aof_pipe_read_data_from_parent; int aof_pipe_read_data_from_parent;
@ -1784,6 +1794,7 @@ struct redisServer {
int child_info_nread; /* Num of bytes of the last read from pipe */ int child_info_nread; /* Num of bytes of the last read from pipe */
/* Propagation of commands in AOF / replication */ /* Propagation of commands in AOF / replication */
redisOpArray also_propagate; /* Additional command to propagate. */ redisOpArray also_propagate; /* Additional command to propagate. */
int replication_allowed; /* Are we allowed to replicate? */
/* Logging */ /* Logging */
char *logfile; /* Path of log file */ char *logfile; /* Path of log file */
int syslog_enabled; /* Is syslog enabled? */ int syslog_enabled; /* Is syslog enabled? */
@ -1830,6 +1841,7 @@ struct redisServer {
int repl_slave_ro; /* Slave is read only? */ int repl_slave_ro; /* Slave is read only? */
int repl_slave_ignore_maxmemory; /* If true slaves do not evict. */ int repl_slave_ignore_maxmemory; /* If true slaves do not evict. */
int slave_priority; /* Reported in INFO and used by Sentinel. */ int slave_priority; /* Reported in INFO and used by Sentinel. */
int replica_announced; /* If true, replica is announced by Sentinel */
int slave_announce_port; /* Give the master this listening port. */ int slave_announce_port; /* Give the master this listening port. */
char *slave_announce_ip; /* Give the master this ip address. */ char *slave_announce_ip; /* Give the master this ip address. */
int repl_slave_lazy_flush; /* Lazy FLUSHALL before loading DB? */ int repl_slave_lazy_flush; /* Lazy FLUSHALL before loading DB? */
@ -1897,6 +1909,7 @@ struct redisServer {
char *cluster_configfile; /* Cluster auto-generated config file name. */ char *cluster_configfile; /* Cluster auto-generated config file name. */
struct clusterState *cluster; /* State of the cluster */ struct clusterState *cluster; /* State of the cluster */
int cluster_migration_barrier; /* Cluster replicas migration barrier. */ int cluster_migration_barrier; /* Cluster replicas migration barrier. */
int cluster_allow_replica_migration; /* Automatic replica migrations to orphaned masters and from empty masters */
int cluster_slave_validity_factor; /* Slave max data age for failover. */ int cluster_slave_validity_factor; /* Slave max data age for failover. */
int cluster_require_full_coverage; /* If true, put the cluster down if int cluster_require_full_coverage; /* If true, put the cluster down if
there is at least an uncovered slot.*/ there is at least an uncovered slot.*/
@ -1904,6 +1917,7 @@ struct redisServer {
if the master is in failure state. */ if the master is in failure state. */
char *cluster_announce_ip; /* IP address to announce on cluster bus. */ char *cluster_announce_ip; /* IP address to announce on cluster bus. */
int cluster_announce_port; /* base port to announce on cluster bus. */ int cluster_announce_port; /* base port to announce on cluster bus. */
int cluster_announce_tls_port; /* TLS port to announce on cluster bus. */
int cluster_announce_bus_port; /* bus port to announce on cluster bus. */ int cluster_announce_bus_port; /* bus port to announce on cluster bus. */
int cluster_module_flags; /* Set of flags that Redis modules are able int cluster_module_flags; /* Set of flags that Redis modules are able
to set in order to suppress certain to set in order to suppress certain
@ -1947,7 +1961,7 @@ struct redisServer {
sds requirepass; /* Remember the cleartext password set with sds requirepass; /* Remember the cleartext password set with
the old "requirepass" directive for the old "requirepass" directive for
backward compatibility with Redis <= 5. */ backward compatibility with Redis <= 5. */
int acl_pubusub_default; /* Default ACL pub/sub channels flag */ int acl_pubsub_default; /* Default ACL pub/sub channels flag */
/* Assert & bug reporting */ /* Assert & bug reporting */
int watchdog_period; /* Software watchdog period in ms. 0 = off */ int watchdog_period; /* Software watchdog period in ms. 0 = off */
@ -2125,6 +2139,7 @@ void moduleLoadFromQueue(void);
int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
moduleType *moduleTypeLookupModuleByID(uint64_t id); moduleType *moduleTypeLookupModuleByID(uint64_t id);
void moduleTypeNameByID(char *name, uint64_t moduleid); void moduleTypeNameByID(char *name, uint64_t moduleid);
const char *moduleTypeModuleName(moduleType *mt);
void moduleFreeContext(struct RedisModuleCtx *ctx); void moduleFreeContext(struct RedisModuleCtx *ctx);
void unblockClientFromModule(client *c); void unblockClientFromModule(client *c);
void moduleHandleBlockedClients(int iel); void moduleHandleBlockedClients(int iel);
@ -2318,7 +2333,8 @@ void flagTransaction(client *c);
void execCommandAbort(client *c, sds error); void execCommandAbort(client *c, sds error);
void execCommandPropagateMulti(int dbid); void execCommandPropagateMulti(int dbid);
void execCommandPropagateExec(int dbid); void execCommandPropagateExec(int dbid);
void beforePropagateMultiOrExec(int multi); void beforePropagateMulti();
void afterPropagateExec();
/* Redis object implementation */ /* Redis object implementation */
void decrRefCount(robj_roptr o); void decrRefCount(robj_roptr o);
@ -2475,7 +2491,7 @@ int isMutuallyExclusiveChildType(int type);
extern rax *Users; extern rax *Users;
extern user *DefaultUser; extern user *DefaultUser;
void ACLInit(void); void ACLInit(void);
/* Return values for ACLCheckCommandPerm() and ACLCheckPubsubPerm(). */ /* Return values for ACLCheckAllPerm(). */
#define ACL_OK 0 #define ACL_OK 0
#define ACL_DENIED_CMD 1 #define ACL_DENIED_CMD 1
#define ACL_DENIED_KEY 2 #define ACL_DENIED_KEY 2
@ -2486,8 +2502,7 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password);
unsigned long ACLGetCommandID(const char *cmdname); unsigned long ACLGetCommandID(const char *cmdname);
void ACLClearCommandID(void); void ACLClearCommandID(void);
user *ACLGetUserByName(const char *name, size_t namelen); user *ACLGetUserByName(const char *name, size_t namelen);
int ACLCheckCommandPerm(client *c, int *keyidxptr); int ACLCheckAllPerm(client *c, int *idxptr);
int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr);
int ACLSetUser(user *u, const char *op, ssize_t oplen); int ACLSetUser(user *u, const char *op, ssize_t oplen);
sds ACLDefaultUserFirstPassword(void); sds ACLDefaultUserFirstPassword(void);
uint64_t ACLGetCommandCategoryFlagByName(const char *name); uint64_t ACLGetCommandCategoryFlagByName(const char *name);
@ -2505,21 +2520,18 @@ void ACLUpdateDefaultUserPassword(sds password);
/* Sorted sets data type */ /* Sorted sets data type */
/* Input flags. */ /* Input flags. */
#define ZADD_NONE 0 #define ZADD_IN_NONE 0
#define ZADD_INCR (1<<0) /* Increment the score instead of setting it. */ #define ZADD_IN_INCR (1<<0) /* Increment the score instead of setting it. */
#define ZADD_NX (1<<1) /* Don't touch elements not already existing. */ #define ZADD_IN_NX (1<<1) /* Don't touch elements not already existing. */
#define ZADD_XX (1<<2) /* Only touch elements already existing. */ #define ZADD_IN_XX (1<<2) /* Only touch elements already existing. */
#define ZADD_GT (1<<7) /* Only update existing when new scores are higher. */ #define ZADD_IN_GT (1<<3) /* Only update existing when new scores are higher. */
#define ZADD_LT (1<<8) /* Only update existing when new scores are lower. */ #define ZADD_IN_LT (1<<4) /* Only update existing when new scores are lower. */
/* Output flags. */ /* Output flags. */
#define ZADD_NOP (1<<3) /* Operation not performed because of conditionals.*/ #define ZADD_OUT_NOP (1<<0) /* Operation not performed because of conditionals.*/
#define ZADD_NAN (1<<4) /* Only touch elements already existing. */ #define ZADD_OUT_NAN (1<<1) /* Only touch elements already existing. */
#define ZADD_ADDED (1<<5) /* The element was new and was added. */ #define ZADD_OUT_ADDED (1<<2) /* The element was new and was added. */
#define ZADD_UPDATED (1<<6) /* The element already existed, score updated. */ #define ZADD_OUT_UPDATED (1<<3) /* The element already existed, score updated. */
/* Flags only used by the ZADD command but not by zsetAdd() API: */
#define ZADD_CH (1<<16) /* Return num of elements added or updated. */
/* Struct to hold an inclusive/exclusive range spec by score comparison. */ /* Struct to hold an inclusive/exclusive range spec by score comparison. */
typedef struct { typedef struct {
@ -2550,7 +2562,7 @@ void zsetConvert(robj *zobj, int encoding);
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen); void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen);
int zsetScore(robj_roptr zobj, sds member, double *score); int zsetScore(robj_roptr zobj, sds member, double *score);
unsigned long zslGetRank(zskiplist *zsl, double score, sds o); unsigned long zslGetRank(zskiplist *zsl, double score, sds o);
int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore); int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore);
long zsetRank(robj_roptr zobj, sds ele, int reverse); long zsetRank(robj_roptr zobj, sds ele, int reverse);
int zsetDel(robj *zobj, sds ele); int zsetDel(robj *zobj, sds ele);
robj *zsetDup(robj *o); robj *zsetDup(robj *o);
@ -2591,14 +2603,16 @@ void redisOpArrayInit(redisOpArray *oa);
void redisOpArrayFree(redisOpArray *oa); void redisOpArrayFree(redisOpArray *oa);
void forceCommandPropagation(client *c, int flags); void forceCommandPropagation(client *c, int flags);
void preventCommandPropagation(client *c); void preventCommandPropagation(client *c);
void preventCommandLogging(client *c);
void preventCommandAOF(client *c); void preventCommandAOF(client *c);
void preventCommandReplication(client *c); void preventCommandReplication(client *c);
void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t duration);
int prepareForShutdown(int flags); int prepareForShutdown(int flags);
#ifdef __GNUC__ #ifdef __GNUC__
void serverLog(int level, const char *fmt, ...) void _serverLog(int level, const char *fmt, ...)
__attribute__((format(printf, 2, 3))); __attribute__((format(printf, 2, 3)));
#else #else
void serverLog(int level, const char *fmt, ...); void _serverLog(int level, const char *fmt, ...);
#endif #endif
void serverLogRaw(int level, const char *msg); void serverLogRaw(int level, const char *msg);
void serverLogFromHandler(int level, const char *msg); void serverLogFromHandler(int level, const char *msg);
@ -2792,6 +2806,7 @@ const char *sentinelHandleConfiguration(char **argv, int argc);
void queueSentinelConfig(sds *argv, int argc, int linenum, sds line); void queueSentinelConfig(sds *argv, int argc, int linenum, sds line);
void loadSentinelConfigFromQueue(void); void loadSentinelConfigFromQueue(void);
void sentinelIsRunning(void); void sentinelIsRunning(void);
void sentinelCheckConfigFile(void);
/* keydb-check-rdb & aof */ /* keydb-check-rdb & aof */
int redis_check_rdb(const char *rdbfilename, FILE *fp); int redis_check_rdb(const char *rdbfilename, FILE *fp);
@ -3179,9 +3194,17 @@ void killIOThreads(void);
void killThreads(void); void killThreads(void);
void makeThreadKillable(void); void makeThreadKillable(void);
/* Use macro for checking log level to avoid evaluating arguments in cases log
* should be ignored due to low level. */
#define serverLog(level, ...) do {\
if (((level)&0xff) < cserver.verbosity) break;\
_serverLog(level, __VA_ARGS__);\
} while(0)
/* TLS stuff */ /* TLS stuff */
void tlsInit(void); void tlsInit(void);
void tlsInitThread(); void tlsInitThread();
void tlsCleanup(void);
int tlsConfigure(redisTLSContextConfig *ctx_config); int tlsConfigure(redisTLSContextConfig *ctx_config);

View File

@ -9,9 +9,11 @@ extern "C" void _serverPanic(const char *file, int line, const char *msg, ...)
extern "C" void _serverPanic(const char *file, int line, const char *msg, ...); extern "C" void _serverPanic(const char *file, int line, const char *msg, ...);
#endif #endif
extern int g_fInCrash;
/* We can print the stacktrace, so our assert is defined this way: */ /* We can print the stacktrace, so our assert is defined this way: */
#define serverAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_serverAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1))) #define serverAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_serverAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1)))
#define serverAssert(_e) ((_e)?(void)0 : (_serverAssert(#_e,__FILE__,__LINE__),_exit(1))) #define serverAssert(_e) (((_e) || g_fInCrash) ?(void)0 : (_serverAssert(#_e,__FILE__,__LINE__),_exit(1)))
#ifdef _DEBUG #ifdef _DEBUG
#define serverAssertDebug(_e) serverAssert(_e) #define serverAssertDebug(_e) serverAssert(_e)
#else #else

View File

@ -201,7 +201,7 @@ void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
#define BUFSIZE 4096 #define BUFSIZE 4096
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
int sha1Test(int argc, char **argv) int sha1Test(int argc, char **argv, int accurate)
{ {
SHA1_CTX ctx; SHA1_CTX ctx;
unsigned char hash[20], buf[BUFSIZE]; unsigned char hash[20], buf[BUFSIZE];
@ -209,6 +209,7 @@ int sha1Test(int argc, char **argv)
UNUSED(argc); UNUSED(argc);
UNUSED(argv); UNUSED(argv);
UNUSED(accurate);
for(i=0;i<BUFSIZE;i++) for(i=0;i<BUFSIZE;i++)
buf[i] = i; buf[i] = i;

View File

@ -23,7 +23,7 @@ void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
void SHA1Final(unsigned char digest[20], SHA1_CTX* context); void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int sha1Test(int argc, char **argv); int sha1Test(int argc, char **argv, int accurate);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -574,7 +574,7 @@ static int _hashZiplistEntryValidation(unsigned char *p, void *userdata) {
return 1; return 1;
} }
/* Validate the integrity of the data stracture. /* Validate the integrity of the data structure.
* when `deep` is 0, only the integrity of the header is validated. * when `deep` is 0, only the integrity of the header is validated.
* when `deep` is 1, we scan all the entries one by one. */ * when `deep` is 1, we scan all the entries one by one. */
int hashZiplistValidateIntegrity(unsigned char *zl, size_t size, int deep) { int hashZiplistValidateIntegrity(unsigned char *zl, size_t size, int deep) {

View File

@ -592,20 +592,14 @@ void lposCommand(client *c) {
} }
} else if (!strcasecmp(opt,"COUNT") && moreargs) { } else if (!strcasecmp(opt,"COUNT") && moreargs) {
j++; j++;
if (getLongFromObjectOrReply(c, c->argv[j], &count, NULL) != C_OK) if (getPositiveLongFromObjectOrReply(c, c->argv[j], &count,
"COUNT can't be negative") != C_OK)
return; return;
if (count < 0) {
addReplyError(c,"COUNT can't be negative");
return;
}
} else if (!strcasecmp(opt,"MAXLEN") && moreargs) { } else if (!strcasecmp(opt,"MAXLEN") && moreargs) {
j++; j++;
if (getLongFromObjectOrReply(c, c->argv[j], &maxlen, NULL) != C_OK) if (getPositiveLongFromObjectOrReply(c, c->argv[j], &maxlen,
"MAXLEN can't be negative") != C_OK)
return; return;
if (maxlen < 0) {
addReplyError(c,"MAXLEN can't be negative");
return;
}
} else { } else {
addReplyErrorObject(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;

View File

@ -860,7 +860,7 @@ int64_t streamTrimByID(stream *s, streamID minid, int approx) {
return streamTrim(s, &args); return streamTrim(s, &args);
} }
/* Parse the arguements of XADD/XTRIM. /* Parse the arguments of XADD/XTRIM.
* *
* See streamAddTrimArgs for more details about the arguments handled. * See streamAddTrimArgs for more details about the arguments handled.
* *
@ -1312,7 +1312,8 @@ void streamLastValidID(stream *s, streamID *maxid)
streamIterator si; streamIterator si;
streamIteratorStart(&si,s,NULL,NULL,1); streamIteratorStart(&si,s,NULL,NULL,1);
int64_t numfields; int64_t numfields;
streamIteratorGetID(&si,maxid,&numfields); if (!streamIteratorGetID(&si,maxid,&numfields) && s->length)
serverPanic("Corrupt stream, length is %llu, but no max id", (unsigned long long)s->length);
streamIteratorStop(&si); streamIteratorStop(&si);
} }
@ -3056,12 +3057,8 @@ void xautoclaimCommand(client *c) {
int moreargs = (c->argc-1) - j; /* Number of additional arguments. */ int moreargs = (c->argc-1) - j; /* Number of additional arguments. */
char *opt = szFromObj(c->argv[j]); char *opt = szFromObj(c->argv[j]);
if (!strcasecmp(opt,"COUNT") && moreargs) { if (!strcasecmp(opt,"COUNT") && moreargs) {
if (getPositiveLongFromObjectOrReply(c,c->argv[j+1],&count,NULL) != C_OK) if (getRangeLongFromObjectOrReply(c,c->argv[j+1],1,LONG_MAX,&count,"COUNT must be > 0") != C_OK)
return; return;
if (count == 0) {
addReplyError(c,"COUNT must be > 0");
return;
}
j++; j++;
} else if (!strcasecmp(opt,"JUSTID")) { } else if (!strcasecmp(opt,"JUSTID")) {
justid = 1; justid = 1;
@ -3126,6 +3123,8 @@ void xautoclaimCommand(client *c) {
/* Update the consumer and idle time. */ /* Update the consumer and idle time. */
nack->delivery_time = now; nack->delivery_time = now;
/* Increment the delivery attempts counter unless JUSTID option provided */
if (!justid)
nack->delivery_count++; nack->delivery_count++;
if (nack->consumer != consumer) { if (nack->consumer != consumer) {
@ -3154,6 +3153,9 @@ void xautoclaimCommand(client *c) {
g_pserver->dirty++; g_pserver->dirty++;
} }
/* We need to return the next entry as a cursor for the next XAUTOCLAIM call */
raxNext(&ri);
streamID endid; streamID endid;
if (raxEOF(&ri)) { if (raxEOF(&ri)) {
endid.ms = endid.seq = 0; endid.ms = endid.seq = 0;
@ -3553,8 +3555,8 @@ NULL
} }
} }
/* Validate the integrity stream listpack entries stracture. Both in term of a /* Validate the integrity stream listpack entries structure. Both in term of a
* valid listpack, but also that the stracture of the entires matches a valid * valid listpack, but also that the structure of the entires matches a valid
* stream. return 1 if valid 0 if not valid. */ * stream. return 1 if valid 0 if not valid. */
int streamValidateListpackIntegrity(unsigned char *lp, size_t size, int deep) { int streamValidateListpackIntegrity(unsigned char *lp, size_t size, int deep) {
int valid_record; int valid_record;

View File

@ -724,7 +724,7 @@ void stralgoCommand(client *c) {
} }
} }
/* STRALGO <algo> [IDX] [MINMATCHLEN <len>] [WITHMATCHLEN] /* STRALGO <algo> [IDX] [LEN] [MINMATCHLEN <len>] [WITHMATCHLEN]
* STRINGS <string> <string> | KEYS <keya> <keyb> * STRINGS <string> <string> | KEYS <keya> <keyb>
*/ */
void stralgoLCS(client *c) { void stralgoLCS(client *c) {

View File

@ -388,9 +388,8 @@ unsigned long zslDeleteRangeByScore(zskiplist *zsl, zrangespec *range, dict *dic
x = zsl->header; x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) { for (i = zsl->level-1; i >= 0; i--) {
while (x->level(i)->forward && (range->minex ? while (x->level(i)->forward &&
x->level(i)->forward->score <= range->min : !zslValueGteMin(x->level(i)->forward->score, range))
x->level(i)->forward->score < range->min))
x = x->level(i)->forward; x = x->level(i)->forward;
update[i] = x; update[i] = x;
} }
@ -399,9 +398,7 @@ unsigned long zslDeleteRangeByScore(zskiplist *zsl, zrangespec *range, dict *dic
x = x->level(0)->forward; x = x->level(0)->forward;
/* Delete nodes while in range. */ /* Delete nodes while in range. */
while (x && while (x && zslValueLteMax(x->score, range)) {
(range->maxex ? x->score < range->max : x->score <= range->max))
{
zskiplistNode *next = x->level(0)->forward; zskiplistNode *next = x->level(0)->forward;
zslDeleteNode(zsl,x,update); zslDeleteNode(zsl,x,update);
dictDelete(dict,x->ele); dictDelete(dict,x->ele);
@ -1279,9 +1276,7 @@ int zsetScore(robj_roptr zobj, sds member, double *score) {
/* Add a new element or update the score of an existing element in a sorted /* Add a new element or update the score of an existing element in a sorted
* set, regardless of its encoding. * set, regardless of its encoding.
* *
* The set of flags change the command behavior. They are passed with an integer * The set of flags change the command behavior.
* pointer since the function will clear the flags and populate them with
* other flags to indicate different conditions.
* *
* The input flags are the following: * The input flags are the following:
* *
@ -1323,19 +1318,19 @@ int zsetScore(robj_roptr zobj, sds member, double *score) {
* *
* The function does not take ownership of the 'ele' SDS string, but copies * The function does not take ownership of the 'ele' SDS string, but copies
* it if needed. */ * it if needed. */
int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) { int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore) {
/* Turn options into simple to check vars. */ /* Turn options into simple to check vars. */
int incr = (*flags & ZADD_INCR) != 0; int incr = (in_flags & ZADD_IN_INCR) != 0;
int nx = (*flags & ZADD_NX) != 0; int nx = (in_flags & ZADD_IN_NX) != 0;
int xx = (*flags & ZADD_XX) != 0; int xx = (in_flags & ZADD_IN_XX) != 0;
int gt = (*flags & ZADD_GT) != 0; int gt = (in_flags & ZADD_IN_GT) != 0;
int lt = (*flags & ZADD_LT) != 0; int lt = (in_flags & ZADD_IN_LT) != 0;
*flags = 0; /* We'll return our response flags. */ *out_flags = 0; /* We'll return our response flags. */
double curscore; double curscore;
/* NaN as input is an error regardless of all the other parameters. */ /* NaN as input is an error regardless of all the other parameters. */
if (std::isnan(score)) { if (std::isnan(score)) {
*flags = ZADD_NAN; *out_flags = ZADD_OUT_NAN;
return 0; return 0;
} }
@ -1346,7 +1341,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
if ((eptr = zzlFind((unsigned char*)zobj->m_ptr,ele,&curscore)) != NULL) { if ((eptr = zzlFind((unsigned char*)zobj->m_ptr,ele,&curscore)) != NULL) {
/* NX? Return, same element already exists. */ /* NX? Return, same element already exists. */
if (nx) { if (nx) {
*flags |= ZADD_NOP; *out_flags |= ZADD_OUT_NOP;
return 1; return 1;
} }
@ -1354,22 +1349,24 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
if (incr) { if (incr) {
score += curscore; score += curscore;
if (std::isnan(score)) { if (std::isnan(score)) {
*flags |= ZADD_NAN; *out_flags |= ZADD_OUT_NAN;
return 0; return 0;
} }
if (newscore) *newscore = score;
} }
/* GT/LT? Only update if score is greater/less than current. */
if ((lt && score >= curscore) || (gt && score <= curscore)) {
*out_flags |= ZADD_OUT_NOP;
return 1;
}
if (newscore) *newscore = score;
/* Remove and re-insert when score changed. */ /* Remove and re-insert when score changed. */
if (score != curscore && if (score != curscore) {
/* LT? Only update if score is less than current. */
(!lt || score < curscore) &&
/* GT? Only update if score is greater than current. */
(!gt || score > curscore))
{
zobj->m_ptr = zzlDelete((unsigned char*)zobj->m_ptr,eptr); zobj->m_ptr = zzlDelete((unsigned char*)zobj->m_ptr,eptr);
zobj->m_ptr = zzlInsert((unsigned char*)zobj->m_ptr,ele,score); zobj->m_ptr = zzlInsert((unsigned char*)zobj->m_ptr,ele,score);
*flags |= ZADD_UPDATED; *out_flags |= ZADD_OUT_UPDATED;
} }
return 1; return 1;
} else if (!xx) { } else if (!xx) {
@ -1380,10 +1377,10 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
sdslen(ele) > g_pserver->zset_max_ziplist_value) sdslen(ele) > g_pserver->zset_max_ziplist_value)
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST); zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
if (newscore) *newscore = score; if (newscore) *newscore = score;
*flags |= ZADD_ADDED; *out_flags |= ZADD_OUT_ADDED;
return 1; return 1;
} else { } else {
*flags |= ZADD_NOP; *out_flags |= ZADD_OUT_NOP;
return 1; return 1;
} }
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
@ -1395,45 +1392,48 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
if (de != NULL) { if (de != NULL) {
/* NX? Return, same element already exists. */ /* NX? Return, same element already exists. */
if (nx) { if (nx) {
*flags |= ZADD_NOP; *out_flags |= ZADD_OUT_NOP;
return 1; return 1;
} }
curscore = *(double*)dictGetVal(de); curscore = *(double*)dictGetVal(de);
/* Prepare the score for the increment if needed. */ /* Prepare the score for the increment if needed. */
if (incr) { if (incr) {
score += curscore; score += curscore;
if (std::isnan(score)) { if (std::isnan(score)) {
*flags |= ZADD_NAN; *out_flags |= ZADD_OUT_NAN;
return 0; return 0;
} }
if (newscore) *newscore = score;
} }
/* GT/LT? Only update if score is greater/less than current. */
if ((lt && score >= curscore) || (gt && score <= curscore)) {
*out_flags |= ZADD_OUT_NOP;
return 1;
}
if (newscore) *newscore = score;
/* Remove and re-insert when score changes. */ /* Remove and re-insert when score changes. */
if (score != curscore && if (score != curscore) {
/* LT? Only update if score is less than current. */
(!lt || score < curscore) &&
/* GT? Only update if score is greater than current. */
(!gt || score > curscore))
{
znode = zslUpdateScore(zs->zsl,curscore,ele,score); znode = zslUpdateScore(zs->zsl,curscore,ele,score);
/* Note that we did not removed the original element from /* Note that we did not removed the original element from
* the hash table representing the sorted set, so we just * the hash table representing the sorted set, so we just
* update the score. */ * update the score. */
dictGetVal(de) = &znode->score; /* Update score ptr. */ dictGetVal(de) = &znode->score; /* Update score ptr. */
*flags |= ZADD_UPDATED; *out_flags |= ZADD_OUT_UPDATED;
} }
return 1; return 1;
} else if (!xx) { } else if (!xx) {
ele = sdsdup(ele); ele = sdsdup(ele);
znode = zslInsert(zs->zsl,score,ele); znode = zslInsert(zs->zsl,score,ele);
serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK); serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
*flags |= ZADD_ADDED; *out_flags |= ZADD_OUT_ADDED;
if (newscore) *newscore = score; if (newscore) *newscore = score;
return 1; return 1;
} else { } else {
*flags |= ZADD_NOP; *out_flags |= ZADD_OUT_NOP;
return 1; return 1;
} }
} else { } else {
@ -1638,7 +1638,7 @@ static int _zsetZiplistValidateIntegrity(unsigned char *p, void *userdata) {
return 1; return 1;
} }
/* Validate the integrity of the data stracture. /* Validate the integrity of the data structure.
* when `deep` is 0, only the integrity of the header is validated. * when `deep` is 0, only the integrity of the header is validated.
* when `deep` is 1, we scan all the entries one by one. */ * when `deep` is 1, we scan all the entries one by one. */
int zsetZiplistValidateIntegrity(unsigned char *zl, size_t size, int deep) { int zsetZiplistValidateIntegrity(unsigned char *zl, size_t size, int deep) {
@ -1711,7 +1711,7 @@ void zaddGenericCommand(client *c, int flags) {
robj *zobj; robj *zobj;
sds ele; sds ele;
double score = 0, *scores = NULL; double score = 0, *scores = NULL;
int j, elements; int j, elements, ch = 0;
int scoreidx = 0; int scoreidx = 0;
/* The following vars are used in order to track what the command actually /* The following vars are used in order to track what the command actually
* did during the execution, to reply to the client and to trigger the * did during the execution, to reply to the client and to trigger the
@ -1726,23 +1726,22 @@ void zaddGenericCommand(client *c, int flags) {
scoreidx = 2; scoreidx = 2;
while(scoreidx < c->argc) { while(scoreidx < c->argc) {
char *opt = szFromObj(c->argv[scoreidx]); char *opt = szFromObj(c->argv[scoreidx]);
if (!strcasecmp(opt,"nx")) flags |= ZADD_NX; if (!strcasecmp(opt,"nx")) flags |= ZADD_IN_NX;
else if (!strcasecmp(opt,"xx")) flags |= ZADD_XX; else if (!strcasecmp(opt,"xx")) flags |= ZADD_IN_XX;
else if (!strcasecmp(opt,"ch")) flags |= ZADD_CH; else if (!strcasecmp(opt,"ch")) ch = 1; /* Return num of elements added or updated. */
else if (!strcasecmp(opt,"incr")) flags |= ZADD_INCR; else if (!strcasecmp(opt,"incr")) flags |= ZADD_IN_INCR;
else if (!strcasecmp(opt,"gt")) flags |= ZADD_GT; else if (!strcasecmp(opt,"gt")) flags |= ZADD_IN_GT;
else if (!strcasecmp(opt,"lt")) flags |= ZADD_LT; else if (!strcasecmp(opt,"lt")) flags |= ZADD_IN_LT;
else break; else break;
scoreidx++; scoreidx++;
} }
/* Turn options into simple to check vars. */ /* Turn options into simple to check vars. */
int incr = (flags & ZADD_INCR) != 0; int incr = (flags & ZADD_IN_INCR) != 0;
int nx = (flags & ZADD_NX) != 0; int nx = (flags & ZADD_IN_NX) != 0;
int xx = (flags & ZADD_XX) != 0; int xx = (flags & ZADD_IN_XX) != 0;
int ch = (flags & ZADD_CH) != 0; int gt = (flags & ZADD_IN_GT) != 0;
int gt = (flags & ZADD_GT) != 0; int lt = (flags & ZADD_IN_LT) != 0;
int lt = (flags & ZADD_LT) != 0;
/* After the options, we expect to have an even number of args, since /* After the options, we expect to have an even number of args, since
* we expect any number of score-element pairs. */ * we expect any number of score-element pairs. */
@ -1800,17 +1799,17 @@ void zaddGenericCommand(client *c, int flags) {
for (j = 0; j < elements; j++) { for (j = 0; j < elements; j++) {
double newscore; double newscore;
score = scores[j]; score = scores[j];
int retflags = flags; int retflags = 0;
ele = szFromObj(c->argv[scoreidx+1+j*2]); ele = szFromObj(c->argv[scoreidx+1+j*2]);
int retval = zsetAdd(zobj, score, ele, &retflags, &newscore); int retval = zsetAdd(zobj, score, ele, flags, &retflags, &newscore);
if (retval == 0) { if (retval == 0) {
addReplyError(c,nanerr); addReplyError(c,nanerr);
goto cleanup; goto cleanup;
} }
if (retflags & ZADD_ADDED) added++; if (retflags & ZADD_OUT_ADDED) added++;
if (retflags & ZADD_UPDATED) updated++; if (retflags & ZADD_OUT_UPDATED) updated++;
if (!(retflags & ZADD_NOP)) processed++; if (!(retflags & ZADD_OUT_NOP)) processed++;
score = newscore; score = newscore;
} }
g_pserver->dirty += (added+updated); g_pserver->dirty += (added+updated);
@ -1835,11 +1834,11 @@ cleanup:
} }
void zaddCommand(client *c) { void zaddCommand(client *c) {
zaddGenericCommand(c,ZADD_NONE); zaddGenericCommand(c,ZADD_IN_NONE);
} }
void zincrbyCommand(client *c) { void zincrbyCommand(client *c) {
zaddGenericCommand(c,ZADD_INCR); zaddGenericCommand(c,ZADD_IN_INCR);
} }
void zremCommand(client *c) { void zremCommand(client *c) {
@ -2576,8 +2575,8 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
return; return;
if (setnum < 1) { if (setnum < 1) {
addReplyError(c, addReplyErrorFormat(c,
"at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE/ZDIFFSTORE"); "at least 1 input key is needed for %s", c->cmd->name);
return; return;
} }
@ -2940,7 +2939,7 @@ static void zrangeResultEmitCBufferForStore(zrange_result_handler *handler,
double newscore; double newscore;
int retflags = 0; int retflags = 0;
sds ele = sdsnewlen(value, value_length_in_bytes); sds ele = sdsnewlen(value, value_length_in_bytes);
int retval = zsetAdd(handler->dstobj, score, ele, &retflags, &newscore); int retval = zsetAdd(handler->dstobj, score, ele, ZADD_IN_NONE, &retflags, &newscore);
sdsfree(ele); sdsfree(ele);
serverAssert(retval); serverAssert(retval);
} }
@ -2951,7 +2950,7 @@ static void zrangeResultEmitLongLongForStore(zrange_result_handler *handler,
double newscore; double newscore;
int retflags = 0; int retflags = 0;
sds ele = sdsfromlonglong(value); sds ele = sdsfromlonglong(value);
int retval = zsetAdd(handler->dstobj, score, ele, &retflags, &newscore); int retval = zsetAdd(handler->dstobj, score, ele, ZADD_IN_NONE, &retflags, &newscore);
sdsfree(ele); sdsfree(ele);
serverAssert(retval); serverAssert(retval);
} }

View File

@ -191,7 +191,7 @@ void tlsInit(void) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L #if OPENSSL_VERSION_NUMBER < 0x10100000L
OPENSSL_config(NULL); OPENSSL_config(NULL);
#else #else
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL); OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG|OPENSSL_INIT_ATFORK, NULL);
#endif #endif
ERR_load_crypto_strings(); ERR_load_crypto_strings();
SSL_load_error_strings(); SSL_load_error_strings();
@ -214,11 +214,43 @@ void tlsInitThread(void)
pending_list = listCreate(); pending_list = listCreate();
} }
void tlsCleanup(void) {
if (redis_tls_ctx) {
SSL_CTX_free(redis_tls_ctx);
redis_tls_ctx = NULL;
}
if (redis_tls_client_ctx) {
SSL_CTX_free(redis_tls_client_ctx);
redis_tls_client_ctx = NULL;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
OPENSSL_cleanup();
#endif
}
/* Callback for passing a keyfile password stored as an sds to OpenSSL */
static int tlsPasswordCallback(char *buf, int size, int rwflag, void *u) {
UNUSED(rwflag);
const char *pass = u;
size_t pass_len;
if (!pass) return -1;
pass_len = strlen(pass);
if (pass_len > (size_t) size) return -1;
memcpy(buf, pass, pass_len);
return (int) pass_len;
}
/* Create a *base* SSL_CTX using the SSL configuration provided. The base context /* Create a *base* SSL_CTX using the SSL configuration provided. The base context
* includes everything that's common for both client-side and server-side connections. * includes everything that's common for both client-side and server-side connections.
*/ */
static SSL_CTX *createSSLContext(redisTLSContextConfig *ctx_config, int protocols, static SSL_CTX *createSSLContext(redisTLSContextConfig *ctx_config, int protocols, int client) {
const char *cert_file, const char *key_file) { const char *cert_file = client ? ctx_config->client_cert_file : ctx_config->cert_file;
const char *key_file = client ? ctx_config->client_key_file : ctx_config->key_file;
const char *key_file_pass = client ? ctx_config->client_key_file_pass : ctx_config->key_file_pass;
char errbuf[256]; char errbuf[256];
SSL_CTX *ctx = NULL; SSL_CTX *ctx = NULL;
int protocols; int protocols;
@ -251,6 +283,9 @@ static SSL_CTX *createSSLContext(redisTLSContextConfig *ctx_config, int protocol
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
SSL_CTX_set_default_passwd_cb(ctx, tlsPasswordCallback);
SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) key_file_pass);
if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) { if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load certificate: %s: %s", cert_file, errbuf); serverLog(LL_WARNING, "Failed to load certificate: %s: %s", cert_file, errbuf);
@ -317,7 +352,7 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
if (protocols == -1) goto error; if (protocols == -1) goto error;
/* Create server side/generla context */ /* Create server side/generla context */
ctx = createSSLContext(ctx_config, protocols, ctx_config->cert_file, ctx_config->key_file); ctx = createSSLContext(ctx_config, protocols, 0);
if (!ctx) goto error; if (!ctx) goto error;
if (ctx_config->session_caching) { if (ctx_config->session_caching) {
@ -368,7 +403,7 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
/* If a client-side certificate is configured, create an explicit client context */ /* If a client-side certificate is configured, create an explicit client context */
if (ctx_config->client_cert_file && ctx_config->client_key_file) { if (ctx_config->client_cert_file && ctx_config->client_key_file) {
client_ctx = createSSLContext(ctx_config, protocols, ctx_config->client_cert_file, ctx_config->client_key_file); client_ctx = createSSLContext(ctx_config, protocols, 1);
if (!client_ctx) goto error; if (!client_ctx) goto error;
} }
@ -1092,6 +1127,9 @@ sds connTLSGetPeerCert(connection *conn_) {
void tlsInit(void) { void tlsInit(void) {
} }
void tlsCleanup(void) {
}
int tlsConfigure(redisTLSContextConfig *ctx_config) { int tlsConfigure(redisTLSContextConfig *ctx_config) {
UNUSED(ctx_config); UNUSED(ctx_config);
return C_OK; return C_OK;

View File

@ -946,9 +946,10 @@ static void test_ll2string(void) {
} }
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
int utilTest(int argc, char **argv) { int utilTest(int argc, char **argv, int accurate) {
UNUSED(argc); UNUSED(argc);
UNUSED(argv); UNUSED(argv);
UNUSED(accurate);
test_string2ll(); test_string2ll();
test_string2l(); test_string2l();

View File

@ -70,7 +70,7 @@ long getTimeZone(void);
int pathIsBaseName(char *path); int pathIsBaseName(char *path);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int utilTest(int argc, char **argv); int utilTest(int argc, char **argv, int accurate);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -1472,7 +1472,7 @@ void ziplistRepr(unsigned char *zl) {
printf("{end}\n\n"); printf("{end}\n\n");
} }
/* Validate the integrity of the data stracture. /* Validate the integrity of the data structure.
* when `deep` is 0, only the integrity of the header is validated. * when `deep` is 0, only the integrity of the header is validated.
* when `deep` is 1, we scan all the entries one by one. */ * when `deep` is 1, we scan all the entries one by one. */
int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep, int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep,
@ -1823,15 +1823,17 @@ static size_t strEntryBytesLarge(size_t slen) {
return slen + zipStorePrevEntryLength(NULL, ZIP_BIG_PREVLEN) + zipStoreEntryEncoding(NULL, 0, slen); return slen + zipStorePrevEntryLength(NULL, ZIP_BIG_PREVLEN) + zipStoreEntryEncoding(NULL, 0, slen);
} }
int ziplistTest(int argc, char **argv) { /* ./redis-server test ziplist <randomseed> --accurate */
int ziplistTest(int argc, char **argv, int accurate) {
unsigned char *zl, *p; unsigned char *zl, *p;
unsigned char *entry; unsigned char *entry;
unsigned int elen; unsigned int elen;
long long value; long long value;
int iteration;
/* If an argument is given, use it as the random seed. */ /* If an argument is given, use it as the random seed. */
if (argc == 2) if (argc >= 4)
srand(atoi(argv[1])); srand(atoi(argv[3]));
zl = createIntList(); zl = createIntList();
ziplistRepr(zl); ziplistRepr(zl);
@ -2339,7 +2341,8 @@ int ziplistTest(int argc, char **argv) {
unsigned int slen; unsigned int slen;
long long sval; long long sval;
for (i = 0; i < 20000; i++) { iteration = accurate ? 20000 : 20;
for (i = 0; i < iteration; i++) {
zl = ziplistNew(); zl = ziplistNew();
ref = listCreate(); ref = listCreate();
listSetFreeMethod(ref,(void (*)(void*))sdsfree); listSetFreeMethod(ref,(void (*)(void*))sdsfree);
@ -2405,15 +2408,17 @@ int ziplistTest(int argc, char **argv) {
printf("Stress with variable ziplist size:\n"); printf("Stress with variable ziplist size:\n");
{ {
unsigned long long start = usec(); unsigned long long start = usec();
stress(ZIPLIST_HEAD,100000,16384,256); int maxsize = accurate ? 16384 : 16;
stress(ZIPLIST_TAIL,100000,16384,256); stress(ZIPLIST_HEAD,100000,maxsize,256);
stress(ZIPLIST_TAIL,100000,maxsize,256);
printf("Done. usec=%lld\n\n", usec()-start); printf("Done. usec=%lld\n\n", usec()-start);
} }
/* Benchmarks */ /* Benchmarks */
{ {
zl = ziplistNew(); zl = ziplistNew();
for (int i=0; i<100000; i++) { iteration = accurate ? 100000 : 100;
for (int i=0; i<iteration; i++) {
char buf[4096] = "asdf"; char buf[4096] = "asdf";
zl = ziplistPush(zl, (unsigned char*)buf, 4, ZIPLIST_TAIL); zl = ziplistPush(zl, (unsigned char*)buf, 4, ZIPLIST_TAIL);
zl = ziplistPush(zl, (unsigned char*)buf, 40, ZIPLIST_TAIL); zl = ziplistPush(zl, (unsigned char*)buf, 40, ZIPLIST_TAIL);
@ -2462,7 +2467,8 @@ int ziplistTest(int argc, char **argv) {
{ {
char data[ZIP_BIG_PREVLEN]; char data[ZIP_BIG_PREVLEN];
zl = ziplistNew(); zl = ziplistNew();
for (int i = 0; i < 100000; i++) { iteration = accurate ? 100000 : 100;
for (int i = 0; i < iteration; i++) {
zl = ziplistPush(zl, (unsigned char*)data, ZIP_BIG_PREVLEN-4, ZIPLIST_TAIL); zl = ziplistPush(zl, (unsigned char*)data, ZIP_BIG_PREVLEN-4, ZIPLIST_TAIL);
} }
unsigned long long start = usec(); unsigned long long start = usec();

View File

@ -71,7 +71,7 @@ void ziplistRandomPairs(unsigned char *zl, unsigned int count, ziplistEntry *key
unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals); unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int ziplistTest(int argc, char *argv[]); int ziplistTest(int argc, char *argv[], int accurate);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -374,7 +374,7 @@ size_t zipmapBlobLen(unsigned char *zm) {
return totlen; return totlen;
} }
/* Validate the integrity of the data stracture. /* Validate the integrity of the data structure.
* when `deep` is 0, only the integrity of the header is validated. * when `deep` is 0, only the integrity of the header is validated.
* when `deep` is 1, we scan all the entries one by one. */ * when `deep` is 1, we scan all the entries one by one. */
int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) { int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) {
@ -473,11 +473,12 @@ static void zipmapRepr(unsigned char *p) {
} }
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
int zipmapTest(int argc, char *argv[]) { int zipmapTest(int argc, char *argv[], int accurate) {
unsigned char *zm; unsigned char *zm;
UNUSED(argc); UNUSED(argc);
UNUSED(argv); UNUSED(argv);
UNUSED(accurate);
zm = zipmapNew(); zm = zipmapNew();
@ -532,6 +533,7 @@ int zipmapTest(int argc, char *argv[]) {
printf(" %d:%.*s => %d:%.*s\n", klen, klen, key, vlen, vlen, value); printf(" %d:%.*s => %d:%.*s\n", klen, klen, key, vlen, vlen, value);
} }
} }
zfree(zm);
return 0; return 0;
} }
#endif #endif

View File

@ -52,7 +52,7 @@ void zipmapRepr(unsigned char *p);
int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep); int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int zipmapTest(int argc, char *argv[]); int zipmapTest(int argc, char *argv[], int accurate);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -426,9 +426,9 @@ size_t zmalloc_get_rss(void) {
if (sysctl(mib, 4, &info, &infolen, NULL, 0) == 0) if (sysctl(mib, 4, &info, &infolen, NULL, 0) == 0)
#if defined(__FreeBSD__) #if defined(__FreeBSD__)
return (size_t)info.ki_rssize; return (size_t)info.ki_rssize * getpagesize();
#else #else
return (size_t)info.kp_vm_rssize; return (size_t)info.kp_vm_rssize * getpagesize();
#endif #endif
return 0L; return 0L;
@ -448,7 +448,7 @@ size_t zmalloc_get_rss(void) {
mib[4] = sizeof(info); mib[4] = sizeof(info);
mib[5] = 1; mib[5] = 1;
if (sysctl(mib, 4, &info, &infolen, NULL, 0) == 0) if (sysctl(mib, 4, &info, &infolen, NULL, 0) == 0)
return (size_t)info.p_vm_rssize; return (size_t)info.p_vm_rssize * getpagesize();
return 0L; return 0L;
} }
@ -625,6 +625,11 @@ size_t zmalloc_get_smap_bytes_by_field(const char *field, long pid) {
} }
#endif #endif
/* Return the total number bytes in pages marked as Private Dirty.
*
* Note: depending on the platform and memory footprint of the process, this
* call can be slow, exceeding 1000ms!
*/
size_t zmalloc_get_private_dirty(long pid) { size_t zmalloc_get_private_dirty(long pid) {
return zmalloc_get_smap_bytes_by_field("Private_Dirty:",pid); return zmalloc_get_smap_bytes_by_field("Private_Dirty:",pid);
} }
@ -687,11 +692,12 @@ size_t zmalloc_get_memory_size(void) {
#ifdef REDIS_TEST #ifdef REDIS_TEST
#define UNUSED(x) ((void)(x)) #define UNUSED(x) ((void)(x))
int zmalloc_test(int argc, char **argv) { int zmalloc_test(int argc, char **argv, int accurate) {
void *ptr; void *ptr;
UNUSED(argc); UNUSED(argc);
UNUSED(argv); UNUSED(argv);
UNUSED(accurate);
printf("Malloc prefix size: %d\n", (int) PREFIX_SIZE); printf("Malloc prefix size: %d\n", (int) PREFIX_SIZE);
printf("Initial used memory: %zu\n", zmalloc_used_memory()); printf("Initial used memory: %zu\n", zmalloc_used_memory());
ptr = zmalloc(123); ptr = zmalloc(123);

View File

@ -76,12 +76,21 @@
*/ */
#ifndef ZMALLOC_LIB #ifndef ZMALLOC_LIB
#define ZMALLOC_LIB "libc" #define ZMALLOC_LIB "libc"
#if !defined(NO_MALLOC_USABLE_SIZE) && \ #if !defined(NO_MALLOC_USABLE_SIZE) && \
(defined(__GLIBC__) || defined(__FreeBSD__) || \ (defined(__GLIBC__) || defined(__FreeBSD__) || \
defined(USE_MALLOC_USABLE_SIZE)) defined(USE_MALLOC_USABLE_SIZE))
/* Includes for malloc_usable_size() */
#ifdef __FreeBSD__
#include <malloc_np.h>
#else
#include <malloc.h> #include <malloc.h>
#endif
#define HAVE_MALLOC_SIZE 1 #define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_usable_size(p) #define zmalloc_size(p) malloc_usable_size(p)
#endif #endif
#endif #endif
@ -141,7 +150,7 @@ size_t zmalloc_usable_size(void *ptr);
#endif #endif
#ifdef REDIS_TEST #ifdef REDIS_TEST
int zmalloc_test(int argc, char **argv); int zmalloc_test(int argc, char **argv, int accurate);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -0,0 +1,2 @@
user alice on nopass ~* +@all
user bob on nopass ~* &* +@all

View File

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

View File

@ -4,6 +4,10 @@
# This software is released under the BSD License. See the COPYING file for # This software is released under the BSD License. See the COPYING file for
# more information. # more information.
# Track cluster configuration as created by create_cluster below
set ::cluster_master_nodes 0
set ::cluster_replica_nodes 0
# Returns a parsed CLUSTER NODES output as a list of dictionaries. # Returns a parsed CLUSTER NODES output as a list of dictionaries.
proc get_cluster_nodes id { proc get_cluster_nodes id {
set lines [split [R $id cluster nodes] "\r\n"] set lines [split [R $id cluster nodes] "\r\n"]
@ -120,6 +124,9 @@ proc create_cluster {masters slaves} {
cluster_allocate_slaves $masters $slaves cluster_allocate_slaves $masters $slaves
} }
assert_cluster_state ok assert_cluster_state ok
set ::cluster_master_nodes $masters
set ::cluster_replica_nodes $slaves
} }
# Set the cluster node-timeout to all the reachalbe nodes. # Set the cluster node-timeout to all the reachalbe nodes.
@ -143,3 +150,28 @@ proc cluster_write_test {id} {
} }
$cluster close $cluster close
} }
# Check if cluster configuration is consistent.
proc cluster_config_consistent {} {
for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
if {$j == 0} {
set base_cfg [R $j cluster slots]
} else {
set cfg [R $j cluster slots]
if {$cfg != $base_cfg} {
return 0
}
}
}
return 1
}
# Wait for cluster configuration to propagate and be consistent across nodes.
proc wait_for_cluster_propagation {} {
wait_for_condition 50 100 {
[cluster_config_consistent] eq 1
} else {
fail "cluster config did not reach a consistent state"
}
}

View File

@ -54,7 +54,17 @@ proc process_is_running {pid} {
set numkeys 50000 set numkeys 50000
set numops 200000 set numops 200000
set cluster [redis_cluster 127.0.0.1:[get_instance_attrib redis 0 port]] set start_node_port [get_instance_attrib redis 0 port]
set cluster [redis_cluster 127.0.0.1:$start_node_port]
if {$::tls} {
# setup a non-TLS cluster client to the TLS cluster
set plaintext_port [get_instance_attrib redis 0 plaintext-port]
set cluster_plaintext [redis_cluster 127.0.0.1:$plaintext_port 0]
puts "Testing TLS cluster on start node 127.0.0.1:$start_node_port, plaintext port $plaintext_port"
} else {
set cluster_plaintext $cluster
puts "Testing using non-TLS cluster"
}
catch {unset content} catch {unset content}
array set content {} array set content {}
set tribpid {} set tribpid {}
@ -94,8 +104,11 @@ test "Cluster consistency during live resharding" {
# This way we are able to stress Lua -> Redis command invocation # This way we are able to stress Lua -> Redis command invocation
# as well, that has tests to prevent Lua to write into wrong # as well, that has tests to prevent Lua to write into wrong
# hash slots. # hash slots.
if {$listid % 2} { # We also use both TLS and plaintext connections.
if {$listid % 3 == 0} {
$cluster rpush $key $ele $cluster rpush $key $ele
} elseif {$listid % 3 == 1} {
$cluster_plaintext rpush $key $ele
} else { } else {
$cluster eval {redis.call("rpush",KEYS[1],ARGV[1])} 1 $key $ele $cluster eval {redis.call("rpush",KEYS[1],ARGV[1])} 1 $key $ele
} }

View File

@ -29,6 +29,12 @@ test "Each master should have at least two replicas attached" {
} }
} }
test "Set allow-replica-migration yes" {
foreach_redis_id id {
R $id CONFIG SET cluster-allow-replica-migration yes
}
}
set master0_id [dict get [get_myself 0] id] set master0_id [dict get [get_myself 0] id]
test "Resharding all the master #0 slots away from it" { test "Resharding all the master #0 slots away from it" {
set output [exec \ set output [exec \

View File

@ -0,0 +1,71 @@
# Replica migration test #2.
#
# Check that if 'cluster-allow-replica-migration' is set to 'no', slaves do not
# migrate when master becomes empty.
source "../tests/includes/init-tests.tcl"
# Create a cluster with 5 master and 15 slaves, to make sure there are no
# empty masters and make rebalancing simpler to handle during the test.
test "Create a 5 nodes cluster" {
create_cluster 5 15
}
test "Cluster is up" {
assert_cluster_state ok
}
test "Each master should have at least two replicas attached" {
foreach_redis_id id {
if {$id < 5} {
wait_for_condition 1000 50 {
[llength [lindex [R 0 role] 2]] >= 2
} else {
fail "Master #$id does not have 2 slaves as expected"
}
}
}
}
test "Set allow-replica-migration no" {
foreach_redis_id id {
R $id CONFIG SET cluster-allow-replica-migration no
}
}
set master0_id [dict get [get_myself 0] id]
test "Resharding all the master #0 slots away from it" {
set output [exec \
../../../src/redis-cli --cluster rebalance \
127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
--cluster-weight ${master0_id}=0 >@ stdout ]
}
test "Wait cluster to be stable" {
wait_for_condition 1000 50 {
[catch {exec ../../../src/redis-cli --cluster \
check 127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
}] == 0
} else {
fail "Cluster doesn't stabilize"
}
}
test "Master #0 stil should have its replicas" {
assert { [llength [lindex [R 0 role] 2]] >= 2 }
}
test "Each master should have at least two replicas attached" {
foreach_redis_id id {
if {$id < 5} {
wait_for_condition 1000 50 {
[llength [lindex [R 0 role] 2]] >= 2
} else {
fail "Master #$id does not have 2 slaves as expected"
}
}
}
}

View File

@ -48,3 +48,16 @@ test "client can handle keys with hash tag" {
$cluster set foo{tag} bar $cluster set foo{tag} bar
$cluster close $cluster close
} }
if {$::tls} {
test {CLUSTER SLOTS from non-TLS client in TLS cluster} {
set slots_tls [R 0 cluster slots]
set host [get_instance_attrib redis 0 host]
set plaintext_port [get_instance_attrib redis 0 plaintext-port]
set client_plain [redis $host $plaintext_port 0 0]
set slots_plain [$client_plain cluster slots]
$client_plain close
# Compare the ports in the first row
assert_no_match [lindex $slots_tls 0 3 1] [lindex $slots_plain 0 3 1]
}
}

View File

@ -36,7 +36,7 @@ test "Right to restore backups when fail to diskless load " {
# Write a key that belongs to slot 0 # Write a key that belongs to slot 0
set slot0_key "06S" set slot0_key "06S"
$master set $slot0_key 1 $master set $slot0_key 1
after 100 wait_for_ofs_sync $master $replica
assert_equal {1} [$replica get $slot0_key] assert_equal {1} [$replica get $slot0_key]
assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1] assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1]
@ -73,6 +73,13 @@ test "Right to restore backups when fail to diskless load " {
# Kill master, abort full sync # Kill master, abort full sync
kill_instance redis $master_id kill_instance redis $master_id
# Start full sync, wait till the replica detects the disconnection
wait_for_condition 500 10 {
[s $replica_id loading] eq 0
} else {
fail "Fail to full sync"
}
# Replica keys and keys to slots map still both are right # Replica keys and keys to slots map still both are right
assert_equal {1} [$replica get $slot0_key] assert_equal {1} [$replica get $slot0_key]
assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1] assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1]

View File

@ -37,26 +37,35 @@ set master2 [Rn 1]
test "Continuous slots distribution" { test "Continuous slots distribution" {
assert_match "* 0-8191*" [$master1 CLUSTER NODES] assert_match "* 0-8191*" [$master1 CLUSTER NODES]
assert_match "* 8192-16383*" [$master2 CLUSTER NODES] assert_match "* 8192-16383*" [$master2 CLUSTER NODES]
assert_match "*0 8191*" [$master1 CLUSTER SLOTS]
assert_match "*8192 16383*" [$master2 CLUSTER SLOTS]
$master1 CLUSTER DELSLOTS 4096 $master1 CLUSTER DELSLOTS 4096
assert_match "* 0-4095 4097-8191*" [$master1 CLUSTER NODES] assert_match "* 0-4095 4097-8191*" [$master1 CLUSTER NODES]
assert_match "*0 4095*4097 8191*" [$master1 CLUSTER SLOTS]
$master2 CLUSTER DELSLOTS 12288 $master2 CLUSTER DELSLOTS 12288
assert_match "* 8192-12287 12289-16383*" [$master2 CLUSTER NODES] assert_match "* 8192-12287 12289-16383*" [$master2 CLUSTER NODES]
assert_match "*8192 12287*12289 16383*" [$master2 CLUSTER SLOTS]
} }
test "Discontinuous slots distribution" { test "Discontinuous slots distribution" {
# Remove middle slots # Remove middle slots
$master1 CLUSTER DELSLOTS 4092 4094 $master1 CLUSTER DELSLOTS 4092 4094
assert_match "* 0-4091 4093 4095 4097-8191*" [$master1 CLUSTER NODES] assert_match "* 0-4091 4093 4095 4097-8191*" [$master1 CLUSTER NODES]
assert_match "*0 4091*4093 4093*4095 4095*4097 8191*" [$master1 CLUSTER SLOTS]
$master2 CLUSTER DELSLOTS 12284 12286 $master2 CLUSTER DELSLOTS 12284 12286
assert_match "* 8192-12283 12285 12287 12289-16383*" [$master2 CLUSTER NODES] assert_match "* 8192-12283 12285 12287 12289-16383*" [$master2 CLUSTER NODES]
assert_match "*8192 12283*12285 12285*12287 12287*12289 16383*" [$master2 CLUSTER SLOTS]
# Remove head slots # Remove head slots
$master1 CLUSTER DELSLOTS 0 2 $master1 CLUSTER DELSLOTS 0 2
assert_match "* 1 3-4091 4093 4095 4097-8191*" [$master1 CLUSTER NODES] assert_match "* 1 3-4091 4093 4095 4097-8191*" [$master1 CLUSTER NODES]
assert_match "*1 1*3 4091*4093 4093*4095 4095*4097 8191*" [$master1 CLUSTER SLOTS]
# Remove tail slots # Remove tail slots
$master2 CLUSTER DELSLOTS 16380 16382 16383 $master2 CLUSTER DELSLOTS 16380 16382 16383
assert_match "* 8192-12283 12285 12287 12289-16379 16381*" [$master2 CLUSTER NODES] assert_match "* 8192-12283 12285 12287 12289-16379 16381*" [$master2 CLUSTER NODES]
assert_match "*8192 12283*12285 12285*12287 12287*12289 16379*16381 16381*" [$master2 CLUSTER SLOTS]
} }

View File

@ -0,0 +1,98 @@
# Tests for fixing migrating slot at all stages:
# 1. when migration is half inited on "migrating" node
# 2. when migration is half inited on "importing" node
# 3. migration inited, but not finished
# 4. migration is half finished on "migrating" node
# 5. migration is half finished on "importing" node
# TODO: Test is currently disabled until it is stabilized (fixing the test
# itself or real issues in Redis).
if {false} {
source "../tests/includes/init-tests.tcl"
source "../tests/includes/utils.tcl"
test "Create a 2 nodes cluster" {
create_cluster 2 0
config_set_all_nodes cluster-allow-replica-migration no
}
test "Cluster is up" {
assert_cluster_state ok
}
set cluster [redis_cluster 127.0.0.1:[get_instance_attrib redis 0 port]]
catch {unset nodefrom}
catch {unset nodeto}
proc reset_cluster {} {
uplevel 1 {
$cluster refresh_nodes_map
array set nodefrom [$cluster masternode_for_slot 609]
array set nodeto [$cluster masternode_notfor_slot 609]
}
}
reset_cluster
$cluster set aga xyz
test "Half init migration in 'migrating' is fixable" {
assert_equal {OK} [$nodefrom(link) cluster setslot 609 migrating $nodeto(id)]
fix_cluster $nodefrom(addr)
assert_equal "xyz" [$cluster get aga]
}
test "Half init migration in 'importing' is fixable" {
assert_equal {OK} [$nodeto(link) cluster setslot 609 importing $nodefrom(id)]
fix_cluster $nodefrom(addr)
assert_equal "xyz" [$cluster get aga]
}
test "Init migration and move key" {
assert_equal {OK} [$nodefrom(link) cluster setslot 609 migrating $nodeto(id)]
assert_equal {OK} [$nodeto(link) cluster setslot 609 importing $nodefrom(id)]
assert_equal {OK} [$nodefrom(link) migrate $nodeto(host) $nodeto(port) aga 0 10000]
wait_for_cluster_propagation
assert_equal "xyz" [$cluster get aga]
fix_cluster $nodefrom(addr)
assert_equal "xyz" [$cluster get aga]
}
reset_cluster
test "Move key again" {
wait_for_cluster_propagation
assert_equal {OK} [$nodefrom(link) cluster setslot 609 migrating $nodeto(id)]
assert_equal {OK} [$nodeto(link) cluster setslot 609 importing $nodefrom(id)]
assert_equal {OK} [$nodefrom(link) migrate $nodeto(host) $nodeto(port) aga 0 10000]
wait_for_cluster_propagation
assert_equal "xyz" [$cluster get aga]
}
test "Half-finish migration" {
# half finish migration on 'migrating' node
assert_equal {OK} [$nodefrom(link) cluster setslot 609 node $nodeto(id)]
fix_cluster $nodefrom(addr)
assert_equal "xyz" [$cluster get aga]
}
reset_cluster
test "Move key back" {
# 'aga' key is in 609 slot
assert_equal {OK} [$nodefrom(link) cluster setslot 609 migrating $nodeto(id)]
assert_equal {OK} [$nodeto(link) cluster setslot 609 importing $nodefrom(id)]
assert_equal {OK} [$nodefrom(link) migrate $nodeto(host) $nodeto(port) aga 0 10000]
assert_equal "xyz" [$cluster get aga]
}
test "Half-finish importing" {
# Now we half finish 'importing' node
assert_equal {OK} [$nodeto(link) cluster setslot 609 node $nodeto(id)]
fix_cluster $nodefrom(addr)
assert_equal "xyz" [$cluster get aga]
}
config_set_all_nodes cluster-allow-replica-migration yes
}

View File

@ -0,0 +1,64 @@
# Tests for many simlutaneous migrations.
# TODO: Test is currently disabled until it is stabilized (fixing the test
# itself or real issues in Redis).
if {false} {
source "../tests/includes/init-tests.tcl"
source "../tests/includes/utils.tcl"
# TODO: This test currently runs without replicas, as failovers (which may
# happen on lower-end CI platforms) are still not handled properly by the
# cluster during slot migration (related to #6339).
test "Create a 10 nodes cluster" {
create_cluster 10 0
config_set_all_nodes cluster-allow-replica-migration no
}
test "Cluster is up" {
assert_cluster_state ok
}
set cluster [redis_cluster 127.0.0.1:[get_instance_attrib redis 0 port]]
catch {unset nodefrom}
catch {unset nodeto}
$cluster refresh_nodes_map
test "Set many keys" {
for {set i 0} {$i < 40000} {incr i} {
$cluster set key:$i val:$i
}
}
test "Keys are accessible" {
for {set i 0} {$i < 40000} {incr i} {
assert { [$cluster get key:$i] eq "val:$i" }
}
}
test "Init migration of many slots" {
for {set slot 0} {$slot < 1000} {incr slot} {
array set nodefrom [$cluster masternode_for_slot $slot]
array set nodeto [$cluster masternode_notfor_slot $slot]
$nodefrom(link) cluster setslot $slot migrating $nodeto(id)
$nodeto(link) cluster setslot $slot importing $nodefrom(id)
}
}
test "Fix cluster" {
wait_for_cluster_propagation
fix_cluster $nodefrom(addr)
}
test "Keys are accessible" {
for {set i 0} {$i < 40000} {incr i} {
assert { [$cluster get key:$i] eq "val:$i" }
}
}
config_set_all_nodes cluster-allow-replica-migration yes
}

View File

@ -0,0 +1,25 @@
source "../../../tests/support/cli.tcl"
proc config_set_all_nodes {keyword value} {
foreach_redis_id id {
R $id config set $keyword $value
}
}
proc fix_cluster {addr} {
set code [catch {
exec ../../../src/redis-cli {*}[rediscli_tls_config "../../../tests"] --cluster fix $addr << yes
} result]
if {$code != 0} {
puts "redis-cli --cluster fix returns non-zero exit code, output below:\n$result"
}
# Note: redis-cli --cluster fix may return a non-zero exit code if nodes don't agree,
# but we can ignore that and rely on the check below.
assert_cluster_state ok
wait_for_condition 100 100 {
[catch {exec ../../../src/redis-cli {*}[rediscli_tls_config "../../../tests"] --cluster check $addr} result] == 0
} else {
puts "redis-cli --cluster check returns non-zero exit code, output below:\n$result"
fail "Cluster could not settle with configuration"
}
}

View File

@ -64,6 +64,8 @@ proc exec_instance {type dirname cfgfile} {
proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} { proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
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]
# plaintext port (only used for TLS cluster)
set pport 0
# Create a directory for this instance. # Create a directory for this instance.
set dirname "${type}_${j}" set dirname "${type}_${j}"
lappend ::dirs $dirname lappend ::dirs $dirname
@ -83,7 +85,9 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
puts $cfg "tls-port $port" puts $cfg "tls-port $port"
puts $cfg "tls-replication yes" puts $cfg "tls-replication yes"
puts $cfg "tls-cluster yes" puts $cfg "tls-cluster yes"
puts $cfg "port 0" # plaintext port, only used by plaintext clients in a TLS cluster
set pport [find_available_port $base_port $::redis_port_count]
puts $cfg "port $pport"
puts $cfg [format "tls-cert-file %s/../../tls/server.crt" [pwd]] puts $cfg [format "tls-cert-file %s/../../tls/server.crt" [pwd]]
puts $cfg [format "tls-key-file %s/../../tls/server.key" [pwd]] puts $cfg [format "tls-key-file %s/../../tls/server.key" [pwd]]
puts $cfg [format "tls-client-cert-file %s/../../tls/client.crt" [pwd]] puts $cfg [format "tls-client-cert-file %s/../../tls/client.crt" [pwd]]
@ -118,6 +122,8 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
set cfg [open $cfgfile a+] set cfg [open $cfgfile a+]
if {$::tls} { if {$::tls} {
puts $cfg "tls-port $port" puts $cfg "tls-port $port"
set pport [find_available_port $base_port $::redis_port_count]
puts $cfg "port $pport"
} else { } else {
puts $cfg "port $port" puts $cfg "port $port"
} }
@ -143,6 +149,7 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
pid $pid \ pid $pid \
host $::host \ host $::host \
port $port \ port $port \
plaintext-port $pport \
link $link \ link $link \
] ]
} }
@ -492,6 +499,14 @@ proc RI {n field} {
get_info_field [R $n info] $field get_info_field [R $n info] $field
} }
proc RPort {n} {
if {$::tls} {
return [lindex [R $n config get tls-port] 1]
} else {
return [lindex [R $n config get port] 1]
}
}
# Iterate over IDs of sentinel or redis instances. # Iterate over IDs of sentinel or redis instances.
proc foreach_instance_id {instances idvar code} { proc foreach_instance_id {instances idvar code} {
upvar 1 $idvar id upvar 1 $idvar id

View File

@ -518,5 +518,16 @@ test {corrupt payload: fuzzer findings - HRANDFIELD on bad ziplist} {
} }
} }
test {corrupt payload: fuzzer findings - stream with no records} {
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
r config set sanitize-dump-payload no
r debug set-skip-checksum-validation 1
r restore _stream 0 "\x0F\x01\x10\x00\x00\x01\x78\x4D\x55\x68\x09\x00\x00\x00\x00\x00\x00\x00\x00\x40\x42\x42\x00\x00\x00\x18\x00\x02\x01\x01\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x00\x01\x02\x01\x00\x01\x00\x01\x01\x01\x00\x01\x05\x01\x03\x01\x3E\x01\x00\x01\x01\x01\x82\x5F\x31\x03\x05\x01\x02\x01\x50\x01\x00\x01\x01\x01\x02\x01\x05\x23\xFF\x02\x81\x00\x00\x01\x78\x4D\x55\x68\x59\x00\x01\x07\x6D\x79\x67\x72\x6F\x75\x70\x81\x00\x00\x01\x78\x4D\x55\x68\x47\x00\x01\x00\x00\x01\x78\x4D\x55\x68\x47\x00\x00\x00\x00\x00\x00\x00\x00\x9F\x68\x55\x4D\x78\x01\x00\x00\x01\x01\x05\x41\x6C\x69\x63\x65\x85\x68\x55\x4D\x78\x01\x00\x00\x01\x00\x00\x01\x78\x4D\x55\x68\x47\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\xF1\xC0\x72\x70\x39\x40\x1E\xA9" replace
catch {r XREAD STREAMS _stream $}
assert_equal [count_log_message 0 "crashed by signal"] 0
assert_equal [count_log_message 0 "Guru Meditation"] 1
}
}
} ;# tags } ;# tags

View File

@ -111,7 +111,13 @@ start_server {} {
$replica1 replicaof no one $replica1 replicaof no one
$replica2 replicaof 127.0.0.1 1 ;# we can't promote it to master since that will cycle the replication id $replica2 replicaof 127.0.0.1 1 ;# we can't promote it to master since that will cycle the replication id
$master config set repl-ping-replica-period 1 $master config set repl-ping-replica-period 1
after 1500 set replofs [status $master master_repl_offset]
wait_for_condition 50 100 {
[status $replica3 master_repl_offset] > $replofs &&
[status $replica4 master_repl_offset] > $replofs
} else {
fail "replica didn't sync in time"
}
# make everyone sync from the replica1 that didn't get the last ping from the old master # make everyone sync from the replica1 that didn't get the last ping from the old master
# replica4 will keep syncing from the old master which now syncs from replica1 # replica4 will keep syncing from the old master which now syncs from replica1
@ -195,10 +201,16 @@ start_server {} {
fail "Chained replica not replicating from its master" fail "Chained replica not replicating from its master"
} }
# Do a write on the master, and wait for 3 seconds for the master to # Do a write on the master, and wait for the master to
# send some PINGs to its replica # send some PINGs to its replica
$R(0) INCR counter2 $R(0) INCR counter2
after 2000 set replofs [status $R(0) master_repl_offset]
wait_for_condition 50 100 {
[status $R(1) master_repl_offset] > $replofs &&
[status $R(2) master_repl_offset] > $replofs
} else {
fail "replica didn't sync in time"
}
set sync_partial_master [status $R(0) sync_partial_ok] set sync_partial_master [status $R(0) sync_partial_ok]
set sync_partial_replica [status $R(1) sync_partial_ok] set sync_partial_replica [status $R(1) sync_partial_ok]
$R(0) CONFIG SET repl-ping-replica-period 100 $R(0) CONFIG SET repl-ping-replica-period 100

View File

@ -207,6 +207,28 @@ start_server {tags {"cli"}} {
assert_equal "foo\nbar" [run_cli lrange list 0 -1] assert_equal "foo\nbar" [run_cli lrange list 0 -1]
} }
test_nontty_cli "Quoted input arguments" {
r set "\x00\x00" "value"
assert_equal "value" [run_cli --quoted-input get {"\x00\x00"}]
}
test_nontty_cli "No accidental unquoting of input arguments" {
run_cli --quoted-input set {"\x41\x41"} quoted-val
run_cli set {"\x41\x41"} unquoted-val
assert_equal "quoted-val" [r get AA]
assert_equal "unquoted-val" [r get {"\x41\x41"}]
}
test_nontty_cli "Invalid quoted input arguments" {
catch {run_cli --quoted-input set {"Unterminated}} err
assert_match {*exited abnormally*} $err
# A single arg that unquotes to two arguments is also not expected
catch {run_cli --quoted-input set {"arg1" "arg2"}} err
assert_match {*exited abnormally*} $err
}
test_nontty_cli "Read last argument from pipe" { test_nontty_cli "Read last argument from pipe" {
assert_equal "OK" [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]
@ -247,6 +269,20 @@ start_server {tags {"cli"}} {
test_redis_cli_rdb_dump test_redis_cli_rdb_dump
} }
test "Scan mode" {
r flushdb
populate 1000 key: 1
# basic use
assert_equal 1000 [llength [split [run_cli --scan]]]
# pattern
assert_equal {key:2} [run_cli --scan --pattern "*:2"]
# pattern matching with a quoted string
assert_equal {key:2} [run_cli --scan --quoted-pattern {"*:\x32"}]
}
test "Connecting as a replica" { test "Connecting as a replica" {
set fd [open_cli "--replica"] set fd [open_cli "--replica"]
wait_for_condition 500 500 { wait_for_condition 500 500 {

View File

@ -79,12 +79,16 @@ start_server {tags {"repl"}} {
$master config set min-slaves-max-lag 2 $master config set min-slaves-max-lag 2
$master config set min-slaves-to-write 1 $master config set min-slaves-to-write 1
assert {[$master set foo bar] eq {OK}} assert {[$master set foo bar] eq {OK}}
$slave deferred 1 exec kill -SIGSTOP [srv 0 pid]
$slave debug sleep 6 wait_for_condition 100 100 {
after 4000 [catch {$master set foo bar}] != 0
catch {$master set foo bar} e } else {
set e fail "Master didn't become readonly"
} {NOREPLICAS*} }
catch {$master set foo bar} err
assert_match {NOREPLICAS*} $err
exec kill -SIGCONT [srv 0 pid]
}
} }
} }

View File

@ -196,9 +196,11 @@ start_server {tags {"repl"}} {
} {master} } {master}
test {SLAVEOF should start with link status "down"} { test {SLAVEOF should start with link status "down"} {
r multi
r slaveof [srv -1 host] [srv -1 port] r slaveof [srv -1 host] [srv -1 port]
s master_link_status r info replication
} {down} r exec
} {*master_link_status:down*}
test {The role should immediately be changed to "replica"} { test {The role should immediately be changed to "replica"} {
s role s role
@ -595,9 +597,9 @@ start_server {tags {"repl"}} {
$master debug populate 20000 test 10000 $master debug populate 20000 test 10000
$master config set rdbcompression no $master config set rdbcompression no
# If running on Linux, we also measure utime/stime to detect possible I/O handling issues # If running on Linux, we also measure utime/stime to detect possible I/O handling issues
set os [catch {exec unamee}] set os [catch {exec uname}]
set measure_time [expr {$os == "Linux"} ? 1 : 0] set measure_time [expr {$os == "Linux"} ? 1 : 0]
foreach all_drop {no slow fast all} { foreach all_drop {no slow fast all timeout} {
test "diskless $all_drop replicas drop during rdb pipe" { test "diskless $all_drop replicas drop during rdb pipe" {
set replicas {} set replicas {}
set replicas_alive {} set replicas_alive {}
@ -614,7 +616,7 @@ start_server {tags {"repl"}} {
# 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] 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 ;# 20k keys and 100 microseconds sleep means at least 2 seconds
[lindex $replicas 0] replicaof $master_host $master_port [lindex $replicas 0] replicaof $master_host $master_port
[lindex $replicas 1] replicaof $master_host $master_port [lindex $replicas 1] replicaof $master_host $master_port
@ -645,6 +647,12 @@ start_server {tags {"repl"}} {
exec kill [srv -1 pid] exec kill [srv -1 pid]
set replicas_alive [lreplace $replicas_alive 0 0] set replicas_alive [lreplace $replicas_alive 0 0]
} }
if {$all_drop == "timeout"} {
$master config set repl-timeout 2
# we want the slow replica to hang on a key for very long so it'll reach repl-timeout
exec kill -SIGSTOP [srv -1 pid]
after 2000
}
# wait for rdb child to exit # wait for rdb child to exit
wait_for_condition 500 100 { wait_for_condition 500 100 {
@ -663,6 +671,14 @@ start_server {tags {"repl"}} {
if {$all_drop == "slow" || $all_drop == "fast"} { if {$all_drop == "slow" || $all_drop == "fast"} {
wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 1 replicas still up*"} $loglines 1 1 wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 1 replicas still up*"} $loglines 1 1
} }
if {$all_drop == "timeout"} {
wait_for_log_messages -2 {"*Disconnecting timedout replica (full sync)*"} $loglines 1 1
wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 1 replicas still up*"} $loglines 1 1
# master disconnected the slow replica, remove from array
set replicas_alive [lreplace $replicas_alive 0 0]
# release it
exec kill -SIGCONT [srv -1 pid]
}
# 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
if {$measure_time} { if {$measure_time} {
@ -676,7 +692,7 @@ start_server {tags {"repl"}} {
puts "master utime: $master_utime" puts "master utime: $master_utime"
puts "master stime: $master_stime" puts "master stime: $master_stime"
} }
if {!$::no_latency && ($all_drop == "all" || $all_drop == "slow")} { if {!$::no_latency && ($all_drop == "all" || $all_drop == "slow" || $all_drop == "timeout")} {
assert {$master_utime < 70} assert {$master_utime < 70}
assert {$master_stime < 70} assert {$master_stime < 70}
} }
@ -720,7 +736,7 @@ start_server {tags {"repl"}} {
test "diskless replication child being killed is collected" { test "diskless replication child being killed is collected" {
# when diskless master is waiting for the replica to become writable # when diskless master is waiting for the replica to become writable
# it removes the read event from the rdb pipe so if the child gets killed # it removes the read event from the rdb pipe so if the child gets killed
# the replica will hung. and the master may not collect the pid with wait3 # the replica will hung. and the master may not collect the pid with waitpid
start_server {tags {"repl"}} { start_server {tags {"repl"}} {
set master [srv 0 client] set master [srv 0 client]
set master_host [srv 0 host] set master_host [srv 0 host]

View File

@ -38,6 +38,8 @@
/** strores all the keys on which we got 'loaded' keyspace notification **/ /** strores all the keys on which we got 'loaded' keyspace notification **/
RedisModuleDict *loaded_event_log = NULL; RedisModuleDict *loaded_event_log = NULL;
/** stores all the keys on which we got 'module' keyspace notification **/
RedisModuleDict *module_event_log = NULL;
static int KeySpace_NotificationLoaded(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){ static int KeySpace_NotificationLoaded(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){
REDISMODULE_NOT_USED(ctx); REDISMODULE_NOT_USED(ctx);
@ -78,6 +80,50 @@ static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const cha
return REDISMODULE_OK; return REDISMODULE_OK;
} }
static int KeySpace_NotificationModule(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(type);
REDISMODULE_NOT_USED(event);
const char* keyName = RedisModule_StringPtrLen(key, NULL);
int nokey;
RedisModule_DictGetC(module_event_log, (void*)keyName, strlen(keyName), &nokey);
if(nokey){
RedisModule_DictSetC(module_event_log, (void*)keyName, strlen(keyName), RedisModule_HoldString(ctx, key));
}
return REDISMODULE_OK;
}
static int cmdNotify(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
if(argc != 2){
return RedisModule_WrongArity(ctx);
}
RedisModule_NotifyKeyspaceEvent(ctx, REDISMODULE_NOTIFY_MODULE, "notify", argv[1]);
RedisModule_ReplyWithNull(ctx);
return REDISMODULE_OK;
}
static int cmdIsModuleKeyNotified(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(module_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;
}
static int cmdIsKeyLoaded(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){ static int cmdIsKeyLoaded(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
if(argc != 2){ if(argc != 2){
return RedisModule_WrongArity(ctx); return RedisModule_WrongArity(ctx);
@ -171,6 +217,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
} }
loaded_event_log = RedisModule_CreateDict(ctx); loaded_event_log = RedisModule_CreateDict(ctx);
module_event_log = RedisModule_CreateDict(ctx);
int keySpaceAll = RedisModule_GetKeyspaceNotificationFlagsAll(); int keySpaceAll = RedisModule_GetKeyspaceNotificationFlagsAll();
@ -187,6 +234,18 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_ERR; return REDISMODULE_ERR;
} }
if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_MODULE, KeySpace_NotificationModule) != REDISMODULE_OK){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx,"keyspace.notify", cmdNotify,"",0,0,0) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx,"keyspace.is_module_key_notified", cmdIsModuleKeyNotified,"",0,0,0) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx,"keyspace.is_key_loaded", cmdIsKeyLoaded,"",0,0,0) == REDISMODULE_ERR){ if (RedisModule_CreateCommand(ctx,"keyspace.is_key_loaded", cmdIsKeyLoaded,"",0,0,0) == REDISMODULE_ERR){
return REDISMODULE_ERR; return REDISMODULE_ERR;
} }
@ -219,6 +278,16 @@ int RedisModule_OnUnload(RedisModuleCtx *ctx) {
RedisModule_FreeString(ctx, val); RedisModule_FreeString(ctx, val);
} }
RedisModule_FreeDict(ctx, loaded_event_log); RedisModule_FreeDict(ctx, loaded_event_log);
RedisModule_DictIteratorStop(iter);
loaded_event_log = NULL; loaded_event_log = NULL;
iter = RedisModule_DictIteratorStartC(module_event_log, "^", NULL, 0);
while((key = RedisModule_DictNextC(iter, &keyLen, (void**)&val))){
RedisModule_FreeString(ctx, val);
}
RedisModule_FreeDict(ctx, module_event_log);
RedisModule_DictIteratorStop(iter);
module_event_log = NULL;
return REDISMODULE_OK; return REDISMODULE_OK;
} }

View File

@ -70,6 +70,44 @@ int propagateTestTimerCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Timer callback. */
void timerNestedHandler(RedisModuleCtx *ctx, void *data) {
int repl = (long long)data;
/* The goal is the trigger a module command that calls RM_Replicate
* in order to test MULTI/EXEC structre */
RedisModule_Replicate(ctx,"INCRBY","cc","timer-nested-start","1");
RedisModuleCallReply *reply = RedisModule_Call(ctx,"propagate-test.nested", repl? "!" : "");
RedisModule_FreeCallReply(reply);
RedisModule_Replicate(ctx,"INCRBY","cc","timer-nested-end","1");
}
int propagateTestTimerNestedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleTimerID timer_id =
RedisModule_CreateTimer(ctx,100,timerNestedHandler,(void*)0);
REDISMODULE_NOT_USED(timer_id);
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
int propagateTestTimerNestedReplCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleTimerID timer_id =
RedisModule_CreateTimer(ctx,100,timerNestedHandler,(void*)1);
REDISMODULE_NOT_USED(timer_id);
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
/* The thread entry point. */ /* The thread entry point. */
void *threadMain(void *arg) { void *threadMain(void *arg) {
REDISMODULE_NOT_USED(arg); REDISMODULE_NOT_USED(arg);
@ -131,6 +169,42 @@ int propagateTestMixedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int propagateTestNestedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleCallReply *reply;
/* This test mixes multiple propagation systems. */
reply = RedisModule_Call(ctx, "INCR", "c!", "using-call");
RedisModule_FreeCallReply(reply);
reply = RedisModule_Call(ctx,"propagate-test.simple", "!");
RedisModule_FreeCallReply(reply);
RedisModule_Replicate(ctx,"INCR","c","counter-3");
RedisModule_Replicate(ctx,"INCR","c","counter-4");
reply = RedisModule_Call(ctx, "INCR", "c!", "after-call");
RedisModule_FreeCallReply(reply);
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
int propagateTestIncr(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argc);
RedisModuleCallReply *reply;
/* This test propagates the module command, not the INCR it executes. */
reply = RedisModule_Call(ctx, "INCR", "s", argv[1]);
RedisModule_ReplyWithCallReply(ctx,reply);
RedisModule_FreeCallReply(reply);
RedisModule_ReplicateVerbatim(ctx);
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);
@ -143,6 +217,16 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.timer-nested",
propagateTestTimerNestedCommand,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.timer-nested-repl",
propagateTestTimerNestedReplCommand,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.thread", if (RedisModule_CreateCommand(ctx,"propagate-test.thread",
propagateTestThreadCommand, propagateTestThreadCommand,
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
@ -158,5 +242,15 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.nested",
propagateTestNestedCommand,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.incr",
propagateTestIncr,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK; return REDISMODULE_OK;
} }

View File

@ -9,7 +9,7 @@ if {$::simulate_error} {
} }
test "Basic failover works if the master is down" { test "Basic failover works if the master is down" {
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}
kill_instance redis $master_id kill_instance redis $master_id
@ -53,7 +53,7 @@ test "ODOWN is not possible without N (quorum) Sentinels reports" {
foreach_sentinel_id id { foreach_sentinel_id id {
S $id SENTINEL SET mymaster quorum [expr $sentinels+1] S $id SENTINEL SET mymaster quorum [expr $sentinels+1]
} }
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}
kill_instance redis $master_id kill_instance redis $master_id

View File

@ -3,7 +3,7 @@
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
test "We can failover with Sentinel 1 crashed" { test "We can failover with Sentinel 1 crashed" {
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}

View File

@ -10,7 +10,7 @@ source "../tests/includes/init-tests.tcl"
proc 02_test_slaves_replication {} { proc 02_test_slaves_replication {} {
uplevel 1 { uplevel 1 {
test "Check that slaves replicate from current master" { test "Check that slaves replicate from current master" {
set master_port [RI $master_id tcp_port] set master_port [RPort $master_id]
foreach_redis_id id { foreach_redis_id id {
if {$id == $master_id} continue if {$id == $master_id} continue
if {[instance_is_killed redis $id]} continue if {[instance_is_killed redis $id]} continue
@ -28,7 +28,7 @@ proc 02_test_slaves_replication {} {
proc 02_crash_and_failover {} { proc 02_crash_and_failover {} {
uplevel 1 { uplevel 1 {
test "Crash the master and force a failover" { test "Crash the master and force a failover" {
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}
kill_instance redis $master_id kill_instance redis $master_id

View File

@ -3,7 +3,7 @@
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
test "Manual failover works" { test "Manual failover works" {
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}
catch {S 0 SENTINEL FAILOVER mymaster} reply catch {S 0 SENTINEL FAILOVER mymaster} reply

View File

@ -0,0 +1,73 @@
source "../tests/includes/init-tests.tcl"
test "Check acceptable replica-priority values" {
foreach_redis_id id {
if {$id == $master_id} continue
# ensure replica-announced accepts yes and no
catch {R $id CONFIG SET replica-announced no} e
if {$e ne "OK"} {
fail "Unable to set replica-announced to no"
}
catch {R $id CONFIG SET replica-announced yes} e
if {$e ne "OK"} {
fail "Unable to set replica-announced to yes"
}
# ensure a random value throw error
catch {R $id CONFIG SET replica-announced 321} e
if {$e eq "OK"} {
fail "Able to set replica-announced with something else than yes or no (321) whereas it should not be possible"
}
catch {R $id CONFIG SET replica-announced a3b2c1} e
if {$e eq "OK"} {
fail "Able to set replica-announced with something else than yes or no (a3b2c1) whereas it should not be possible"
}
# test only the first redis replica, no need to double test
break
}
}
proc 10_test_number_of_replicas {n_replicas_expected} {
test "Check sentinel replies with $n_replicas_expected replicas" {
# ensure sentinels replies with the right number of replicas
foreach_sentinel_id id {
# retries 40 x 500ms = 20s as SENTINEL_INFO_PERIOD = 10s
set len [llength [S $id SENTINEL REPLICAS mymaster]]
wait_for_condition 40 500 {
[llength [S $id SENTINEL REPLICAS mymaster]] == $n_replicas_expected
} else {
fail "Sentinel replies with a wrong number of replicas with replica-announced=yes (expected $n_replicas_expected but got $len) on sentinel $id"
}
}
}
}
proc 10_set_replica_announced {master_id announced n_replicas} {
test "Set replica-announced=$announced on $n_replicas replicas" {
set i 0
foreach_redis_id id {
if {$id == $master_id} continue
#puts "set replica-announce=$announced on redis #$id"
R $id CONFIG SET replica-announced "$announced"
incr i
if { $n_replicas!="all" && $i >= $n_replicas } { break }
}
}
}
# ensure all replicas are announced
10_set_replica_announced $master_id "yes" "all"
# ensure all replicas are announced by sentinels
10_test_number_of_replicas 4
# ensure the first 2 replicas are not announced
10_set_replica_announced $master_id "no" 2
# ensure sentinels are not announcing the first 2 replicas that have been set unannounced
10_test_number_of_replicas 2
# ensure all replicas are announced
10_set_replica_announced $master_id "yes" "all"
# ensure all replicas are not announced by sentinels
10_test_number_of_replicas 4

View File

@ -4,7 +4,7 @@
# #
# Example usage: # Example usage:
# #
# set c [redis_cluster 127.0.0.1 6379 127.0.0.1 6380] # set c [redis_cluster {127.0.0.1:6379 127.0.0.1:6380}]
# $c set foo # $c set foo
# $c get foo # $c get foo
# $c close # $c close
@ -17,6 +17,7 @@ set ::redis_cluster::id 0
array set ::redis_cluster::startup_nodes {} array set ::redis_cluster::startup_nodes {}
array set ::redis_cluster::nodes {} array set ::redis_cluster::nodes {}
array set ::redis_cluster::slots {} array set ::redis_cluster::slots {}
array set ::redis_cluster::tls {}
# List of "plain" commands, which are commands where the sole key is always # List of "plain" commands, which are commands where the sole key is always
# the first argument. # the first argument.
@ -34,11 +35,14 @@ set ::redis_cluster::plain_commands {
dump bitcount bitpos pfadd pfcount dump bitcount bitpos pfadd pfcount
} }
proc redis_cluster {nodes} { # Create a cluster client. The nodes are given as a list of host:port. The TLS
# parameter (1 or 0) is optional and defaults to the global $::tls.
proc redis_cluster {nodes {tls -1}} {
set id [incr ::redis_cluster::id] set id [incr ::redis_cluster::id]
set ::redis_cluster::startup_nodes($id) $nodes set ::redis_cluster::startup_nodes($id) $nodes
set ::redis_cluster::nodes($id) {} set ::redis_cluster::nodes($id) {}
set ::redis_cluster::slots($id) {} set ::redis_cluster::slots($id) {}
set ::redis_cluster::tls($id) [expr $tls == -1 ? $::tls : $tls]
set handle [interp alias {} ::redis_cluster::instance$id {} ::redis_cluster::__dispatch__ $id] set handle [interp alias {} ::redis_cluster::instance$id {} ::redis_cluster::__dispatch__ $id]
$handle refresh_nodes_map $handle refresh_nodes_map
return $handle return $handle
@ -60,9 +64,10 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
foreach start_node $::redis_cluster::startup_nodes($id) { foreach start_node $::redis_cluster::startup_nodes($id) {
set ip_port [lindex [split $start_node @] 0] set ip_port [lindex [split $start_node @] 0]
lassign [split $ip_port :] start_host start_port lassign [split $ip_port :] start_host start_port
set tls $::redis_cluster::tls($id)
if {[catch { if {[catch {
set r {} set r {}
set r [redis $start_host $start_port 0 $::tls] set r [redis $start_host $start_port 0 $tls]
set nodes_descr [$r cluster nodes] set nodes_descr [$r cluster nodes]
$r close $r close
} e]} { } e]} {
@ -107,7 +112,8 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
# Connect to the node # Connect to the node
set link {} set link {}
catch {set link [redis $host $port 0 $::tls]} set tls $::redis_cluster::tls($id)
catch {set link [redis $host $port 0 $tls]}
# Build this node description as an hash. # Build this node description as an hash.
set node [dict create \ set node [dict create \
@ -161,9 +167,32 @@ proc ::redis_cluster::__method__close {id} {
catch {unset ::redis_cluster::startup_nodes($id)} catch {unset ::redis_cluster::startup_nodes($id)}
catch {unset ::redis_cluster::nodes($id)} catch {unset ::redis_cluster::nodes($id)}
catch {unset ::redis_cluster::slots($id)} catch {unset ::redis_cluster::slots($id)}
catch {unset ::redis_cluster::tls($id)}
catch {interp alias {} ::redis_cluster::instance$id {}} catch {interp alias {} ::redis_cluster::instance$id {}}
} }
proc ::redis_cluster::__method__masternode_for_slot {id slot} {
# Get the node mapped to this slot.
set node_addr [dict get $::redis_cluster::slots($id) $slot]
if {$node_addr eq {}} {
error "No mapped node for slot $slot."
}
return [dict get $::redis_cluster::nodes($id) $node_addr]
}
proc ::redis_cluster::__method__masternode_notfor_slot {id slot} {
# Get a node that is not mapped to this slot.
set node_addr [dict get $::redis_cluster::slots($id) $slot]
set addrs [dict keys $::redis_cluster::nodes($id)]
foreach addr [lshuffle $addrs] {
set node [dict get $::redis_cluster::nodes($id) $addr]
if {$node_addr ne $addr && [dict get $node slaveof] eq "-"} {
return $node
}
}
error "Slot $slot is everywhere"
}
proc ::redis_cluster::__dispatch__ {id method args} { proc ::redis_cluster::__dispatch__ {id method args} {
if {[info command ::redis_cluster::__method__$method] eq {}} { if {[info command ::redis_cluster::__method__$method] eq {}} {
# Get the keys from the command. # Get the keys from the command.
@ -186,10 +215,15 @@ proc ::redis_cluster::__dispatch__ {id method args} {
# Execute the command in the node we think is the slot owner. # Execute the command in the node we think is the slot owner.
set retry 100 set retry 100
set asking 0
while {[incr retry -1]} { while {[incr retry -1]} {
if {$retry < 5} {after 100} if {$retry < 5} {after 100}
set node [dict get $::redis_cluster::nodes($id) $node_addr] set node [dict get $::redis_cluster::nodes($id) $node_addr]
set link [dict get $node link] set link [dict get $node link]
if {$asking} {
$link ASKING
set asking 0
}
if {[catch {$link $method {*}$args} e]} { if {[catch {$link $method {*}$args} e]} {
if {$link eq {} || \ if {$link eq {} || \
[string range $e 0 4] eq {MOVED} || \ [string range $e 0 4] eq {MOVED} || \
@ -202,6 +236,7 @@ proc ::redis_cluster::__dispatch__ {id method args} {
} elseif {[string range $e 0 2] eq {ASK}} { } elseif {[string range $e 0 2] eq {ASK}} {
# ASK redirection. # ASK redirection.
set node_addr [lindex $e 2] set node_addr [lindex $e 2]
set asking 1
continue continue
} else { } else {
# Non redirecting error. # Non redirecting error.

View File

@ -35,6 +35,7 @@ array set ::redis::addr {}
array set ::redis::blocking {} array set ::redis::blocking {}
array set ::redis::deferred {} array set ::redis::deferred {}
array set ::redis::reconnect {} array set ::redis::reconnect {}
array set ::redis::tls {}
array set ::redis::callback {} array set ::redis::callback {}
array set ::redis::state {} ;# State in non-blocking reply reading array set ::redis::state {} ;# State in non-blocking reply reading
array set ::redis::statestack {} ;# Stack of states, for nested mbulks array set ::redis::statestack {} ;# Stack of states, for nested mbulks
@ -58,7 +59,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}}} {
set ::redis::blocking($id) 1 set ::redis::blocking($id) 1
set ::redis::deferred($id) $defer set ::redis::deferred($id) $defer
set ::redis::reconnect($id) 0 set ::redis::reconnect($id) 0
set ::redis::tls $tls set ::redis::tls($id) $tls
::redis::redis_reset_state $id ::redis::redis_reset_state $id
interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id
} }
@ -83,7 +84,7 @@ proc ::redis::__dispatch__raw__ {id method argv} {
# Reconnect the link if needed. # Reconnect the link if needed.
if {$fd eq {}} { if {$fd eq {}} {
lassign $::redis::addr($id) host port lassign $::redis::addr($id) host port
if {$::redis::tls} { if {$::redis::tls($id)} {
set ::redis::fd($id) [::tls::socket $host $port] set ::redis::fd($id) [::tls::socket $host $port]
} else { } else {
set ::redis::fd($id) [socket $host $port] set ::redis::fd($id) [socket $host $port]
@ -158,6 +159,7 @@ proc ::redis::__method__close {id fd} {
catch {unset ::redis::blocking($id)} catch {unset ::redis::blocking($id)}
catch {unset ::redis::deferred($id)} catch {unset ::redis::deferred($id)}
catch {unset ::redis::reconnect($id)} catch {unset ::redis::reconnect($id)}
catch {unset ::redis::tls($id)}
catch {unset ::redis::state($id)} catch {unset ::redis::state($id)}
catch {unset ::redis::statestack($id)} catch {unset ::redis::statestack($id)}
catch {unset ::redis::callback($id)} catch {unset ::redis::callback($id)}

View File

@ -253,7 +253,7 @@ proc wait_server_started {config_file stdout pid} {
# Check if the port is actually busy and the server failed # Check if the port is actually busy and the server failed
# for this reason. # for this reason.
if {[regexp {Could not create server TCP} [exec cat $stdout]]} { if {[regexp {Failed listening on port} [exec cat $stdout]]} {
set port_busy 1 set port_busy 1
break break
} }
@ -511,6 +511,7 @@ proc start_server {options {code undefined}} {
set num_tests $::num_tests set num_tests $::num_tests
if {[catch { uplevel 1 $code } error]} { if {[catch { uplevel 1 $code } error]} {
set backtrace $::errorInfo set backtrace $::errorInfo
set assertion [string match "assertion:*" $error]
# fetch srv back from the server list, in case it was restarted by restart_server (new PID) # fetch srv back from the server list, in case it was restarted by restart_server (new PID)
set srv [lindex $::servers end] set srv [lindex $::servers end]
@ -522,17 +523,23 @@ proc start_server {options {code undefined}} {
dict set srv "skipleaks" 1 dict set srv "skipleaks" 1
kill_server $srv kill_server $srv
# Print warnings from log if {$::dump_logs && $assertion} {
puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]] # if we caught an assertion ($::num_failed isn't incremented yet)
set warnings [warnings_from_file [dict get $srv "stdout"]] # this happens when the test spawns a server and not the other way around
if {[string length $warnings] > 0} { dump_server_log $srv
puts "$warnings"
} else { } else {
puts "(none)" # Print crash report from log
} set crashlog [crashlog_from_file [dict get $srv "stdout"]]
if {[string length $crashlog] > 0} {
puts [format "\nLogged crash report (pid %d):" [dict get $srv "pid"]]
puts "$crashlog"
puts "" puts ""
}
}
if {$::durable} { if {!$assertion && $::durable} {
# durable is meant to prevent the whole tcl test from exiting on
# an exception. an assertion will be caught by the test proc.
set msg [string range $error 10 end] set msg [string range $error 10 end]
lappend details $msg lappend details $msg
lappend details $backtrace lappend details $backtrace

Some files were not shown because too many files have changed in this diff Show More