Initial merge of unstable 6

Former-commit-id: aac140de199646914cc02997a45111c9c695e55d
This commit is contained in:
John Sully 2020-04-16 16:36:16 -04:00
commit 05cc1fd3de
85 changed files with 4619 additions and 1037 deletions

View File

@ -3,25 +3,30 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
build-ubuntu: test-ubuntu-latest:
strategy: runs-on: ubuntu-latest
matrix:
platform: [ubuntu-latest, ubuntu-16.04]
runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: make - name: make
run: make run: |
sudo apt-get install uuid-dev libcurl4-openssl-dev
make
- name: test - name: test
run: | run: |
sudo apt-get install tcl8.5 sudo apt-get install tcl8.5
make test ./runtest --clients 2 --verbose
build-ubuntu-old:
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v1
- name: make
run: |
sudo apt-get install uuid-dev libcurl4-openssl-dev
make
build-macos-latest: build-macos-latest:
strategy: runs-on: macos-latest
matrix:
platform: [macos-latest, macOS-10.14]
runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: make - name: make

1
.gitignore vendored
View File

@ -40,3 +40,4 @@ deps/lua/src/liblua.a
*.dSYM *.dSYM
Makefile.dep Makefile.dep
.vscode/* .vscode/*
.idea/*

View File

@ -1,29 +0,0 @@
language: generic
os: osx
matrix:
include:
- os: linux
script: make
env: COMPILER_NAME=g++-6 CXX=g++-6 CC=gcc-6
addons:
apt:
packages:
- g++-6
- nasm
- uuid-dev
sources: &sources
- llvm-toolchain-precise-3.8
- ubuntu-toolchain-r-test
- os: linux
script: make MALLOC=libc
env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8 CXXFLAGS="-I/usr/include/libcxxabi/" LDFLAGS="-lc++"
addons:
apt:
packages:
- libc++abi-dev
- clang-3.8
- libc++-dev
- libc++abi-dev
- nasm
- uuid-dev
sources: *sources

View File

@ -11,6 +11,926 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
SECURITY: There are security fixes in the release. SECURITY: There are security fixes in the release.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
================================================================================
Redis 6.0-rc3 Released Tue Mar 31 17:42:39 CEST 2020
================================================================================
Upgrade urgency CRITICAL: A connection management bug introduced with the
SSL implementation can crash Redis easily.
Dear users, this is a list of the major changes in this release, please check
the list of commits for detail:
* Fix crash due to refactoring for SSL, for the connection code.
* Precise timeouts for blocking commands. Now the timeouts have HZ
resolution regardless of the number of connected clinets. New timeouts
are stored in a radix tree and sorted by expire time.
* Fix rare crash when resizing the event loop because of CONFIG maxclients.
* Fix systemd readiness after successful partial resync.
* Redis-cli ask password mode to be prompted at startup (for additional safety).
* Keyspace notifications added to MIGRATE / RESTORE.
* Threaded I/O bugs fixed.
* Implement new ACL style AUTH in Sentinel.
* Make 'requirepass' more backward compatible with Redis <= 5.
* ACL: Handle default user as disabled if it's off regardless of "nopass".
* Fix a potential inconsistency when upgrading an instance in Redis Cluster
and restarting it. The instance will act as a replica but will actually be
set as a master immediately. However the choice of what to do with already
expired keys, on loading, was made from the POV of replicas.
* Abort transactions after -READONLY error.
* Many different fixes to module APIs.
* BITFIELD_RO added to call the command on read only replicas.
* PSYNC2: meaningful offset implementation. Allow the disconnected master
that is still sending PINGs to replicas, to be able to successfully
PSYNC incrementally to new slaves, discarding the last part of the
replication backlog consisting only of PINGs.
* Fix pipelined MULTI/EXEC during Lua scripts are in BUSY state.
* Re-fix propagation API in modules, broken again after other changes.
antirez in commit ef1b1f01:
cast raxSize() to avoid warning with format spec.
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit 9f347fab:
Minor changes to #7037.
2 files changed, 14 insertions(+), 5 deletions(-)
Guy Benoish in commit a509400d:
Modules: Test MULTI/EXEC replication of RM_Replicate
6 files changed, 49 insertions(+), 9 deletions(-)
Guy Benoish in commit 805c8c94:
RENAME can unblock XREADGROUP
3 files changed, 25 insertions(+), 1 deletion(-)
antirez in commit 97b80b57:
Fix the propagate Tcl test after module changes.
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit 4f6b6b80:
Modify the propagate unit test to show more cases.
1 file changed, 30 insertions(+), 2 deletions(-)
antirez in commit 616b1cb7:
Fix module commands propagation double MULTI bug.
4 files changed, 25 insertions(+), 8 deletions(-)
antirez in commit 08fdef4b:
Fix RM_Call() stale comment due to cut&paste.
1 file changed, 1 insertion(+), 3 deletions(-)
OMG-By in commit 26b79ca1:
fix: dict.c->dictResize()->minimal type
1 file changed, 1 insertion(+), 1 deletion(-)
zhaozhao.zz in commit fa418637:
PSYNC2: reset backlog_idx and master_repl_offset correctly
1 file changed, 10 insertions(+), 5 deletions(-)
antirez in commit bbbc80ac:
Precise timeouts: reference client pointer directly.
1 file changed, 13 insertions(+), 16 deletions(-)
antirez in commit c3b268a0:
timeout.c created: move client timeouts code there.
5 files changed, 198 insertions(+), 167 deletions(-)
Oran Agra in commit 0f7dfc37:
AOFRW on an empty stream created with MKSTREAM loads badkly
2 files changed, 15 insertions(+), 1 deletion(-)
antirez in commit 67643ead:
Precise timeouts: cleaup the table on unblock.
3 files changed, 21 insertions(+), 2 deletions(-)
antirez in commit ad94066e:
Precise timeouts: fix comments after functional change.
2 files changed, 6 insertions(+), 6 deletions(-)
antirez in commit a443ec2e:
Precise timeouts: use only radix tree for timeouts.
3 files changed, 15 insertions(+), 38 deletions(-)
antirez in commit 6862fd70:
Precise timeouts: fast exit for clientsHandleShortTimeout().
1 file changed, 1 insertion(+)
antirez in commit 30f1df8c:
Precise timeouts: fix bugs in initial implementation.
2 files changed, 5 insertions(+), 1 deletion(-)
antirez in commit 7add0f24:
Precise timeouts: working initial implementation.
3 files changed, 110 insertions(+), 28 deletions(-)
antirez in commit 9d6d1779:
Precise timeouts: refactor unblocking on timeout.
2 files changed, 33 insertions(+), 13 deletions(-)
antirez in commit 316a8f15:
PSYNC2: fix backlog_idx when adjusting for meaningful offset
1 file changed, 3 insertions(+)
伯成 in commit 11db53f8:
Boost up performance for redis PUB-SUB patterns matching
3 files changed, 43 insertions(+), 11 deletions(-)
antirez in commit e257f121:
PSYNC2: meaningful offset test.
2 files changed, 62 insertions(+)
antirez in commit 5f72f696:
PSYNC2: meaningful offset implemented.
3 files changed, 40 insertions(+), 1 deletion(-)
antirez in commit 8caa2714:
Explain why we allow transactions in -BUSY state.
1 file changed, 9 insertions(+), 2 deletions(-)
Oran Agra in commit e43cd831:
MULTI/EXEC during LUA script timeout are messed up
2 files changed, 73 insertions(+)
antirez in commit 34b89832:
Improve comments of replicationCacheMasterUsingMyself().
1 file changed, 6 insertions(+), 1 deletion(-)
antirez in commit 70a98a43:
Fix BITFIELD_RO test.
2 files changed, 5 insertions(+), 5 deletions(-)
antirez in commit 8783304a:
Abort transactions after -READONLY error. Fix #7014.
1 file changed, 1 insertion(+)
antirez in commit ec9cf002:
Minor changes to BITFIELD_RO PR #6951.
1 file changed, 9 insertions(+), 6 deletions(-)
bodong.ybd in commit b3e4abf0:
Added BITFIELD_RO variants for read-only operations.
4 files changed, 54 insertions(+), 1 deletion(-)
antirez in commit 50f8f950:
Modules: updated function doc after #7003.
1 file changed, 6 insertions(+), 1 deletion(-)
Guy Benoish in commit f2f3dc5e:
Allow RM_GetContextFlags to work with ctx==NULL
1 file changed, 16 insertions(+), 14 deletions(-)
hwware in commit eb808879:
fix potentical memory leak in redis-cli
1 file changed, 2 insertions(+)
Yossi Gottlieb in commit cdcab0e8:
Fix crashes related to failed/rejected accepts.
1 file changed, 6 insertions(+), 5 deletions(-)
Yossi Gottlieb in commit 50dcd9f9:
Cluster: fix misleading accept errors.
1 file changed, 4 insertions(+), 3 deletions(-)
Yossi Gottlieb in commit 87dbd8f5:
Conns: Fix connClose() / connAccept() behavior.
3 files changed, 48 insertions(+), 32 deletions(-)
hwware in commit 81e8686c:
remove redundant Semicolon
1 file changed, 1 insertion(+), 1 deletion(-)
hwware in commit c7524a7e:
clean CLIENT_TRACKING_CACHING flag when disabled caching
1 file changed, 1 insertion(+), 1 deletion(-)
hwware in commit 2dd1ca6a:
add missing commands in cluster help
1 file changed, 2 insertions(+), 1 deletion(-)
artix in commit 95324b81:
Support Redis Cluster Proxy PROXY INFO command
1 file changed, 5 insertions(+), 1 deletion(-)
박승현 in commit 04c53fa1:
Update redis.conf
1 file changed, 1 insertion(+), 1 deletion(-)
WuYunlong in commit 0578157d:
Fix master replica inconsistency for upgrading scenario.
3 files changed, 9 insertions(+), 2 deletions(-)
WuYunlong in commit 299f1d02:
Add 14-consistency-check.tcl to prove there is a data consistency issue.
1 file changed, 87 insertions(+)
antirez in commit 61b98f32:
Regression test for #7011.
1 file changed, 7 insertions(+)
antirez in commit 34ea2f4e:
ACL: default user off should not allow automatic authentication.
2 files changed, 3 insertions(+), 2 deletions(-)
antirez in commit cbbf9b39:
Sentinel: document auth-user directive.
1 file changed, 12 insertions(+)
antirez in commit 9c2e42dd:
ACL: Make Redis 6 more backward compatible with requirepass.
4 files changed, 17 insertions(+), 15 deletions(-)
antirez in commit d387f67d:
Sentinel: implement auth-user directive for ACLs.
1 file changed, 38 insertions(+), 7 deletions(-)
zhaozhao.zz in commit 7c078416:
Threaded IO: bugfix client kill may crash redis
1 file changed, 11 insertions(+), 5 deletions(-)
zhaozhao.zz in commit 9cc7038e:
Threaded IO: handle pending reads clients ASAP after event loop
1 file changed, 3 insertions(+), 1 deletion(-)
antirez in commit da8c7c49:
Example sentinel conf: document requirepass.
1 file changed, 8 insertions(+)
antirez in commit bdb338cf:
Aesthetic changes in PR #6989.
1 file changed, 9 insertions(+), 5 deletions(-)
zhaozhao.zz in commit b3e03054:
Threaded IO: bugfix #6988 process events while blocked
1 file changed, 5 insertions(+)
antirez in commit e628f944:
Restore newline at the end of redis-cli.c
1 file changed, 2 insertions(+), 1 deletion(-)
chendianqiang in commit 5d4c4df3:
use correct list for moduleUnregisterUsedAPI
1 file changed, 1 insertion(+), 1 deletion(-)
guodongxiaren in commit da14982d:
string literal should be const char*
1 file changed, 1 insertion(+), 1 deletion(-)
Itamar Haber in commit dc8885a1:
Adds keyspace notifications to migrate and restore
1 file changed, 3 insertions(+), 1 deletion(-)
bodong.ybd in commit bfb18e55:
Remove duplicate obj files in Makefile
1 file changed, 2 insertions(+), 2 deletions(-)
bodong.ybd in commit 76d57161:
Fix bug of tcl test using external server
2 files changed, 8 insertions(+), 2 deletions(-)
fengpf in commit 0e5820d8:
fix comments in latency.c
2 files changed, 2 insertions(+), 1 deletion(-)
antirez in commit 916dd79f:
Update linenoise.
1 file changed, 2 insertions(+), 1 deletion(-)
lifubang in commit c0c67c9b:
add askpass mode
1 file changed, 19 insertions(+), 1 deletion(-)
lifubang in commit e1c29434:
update linenoise to https://github.com/antirez/linenoise/tree/fc9667a81d43911a6690fb1e68c16e6e3bb8df05
4 files changed, 59 insertions(+), 4 deletions(-)
Jamie Scott in commit e5a063bc:
Remove default guidance in Redis.conf
1 file changed, 1 insertion(+), 2 deletions(-)
Jamie Scott in commit d28cbaf7:
Update Redis.conf to improve TLS usability
1 file changed, 2 insertions(+), 1 deletion(-)
Johannes Truschnigg in commit 23d5e8b8:
Signal systemd readiness atfer Partial Resync
1 file changed, 4 insertions(+)
Oran Agra in commit 61738154:
fix for flaky psync2 test
1 file changed, 21 insertions(+)
antirez in commit 70e0e499:
ae.c: fix crash when resizing the event loop.
1 file changed, 6 insertions(+), 2 deletions(-)
antirez in commit b3e4aa67:
Fix release notes spelling mistake.
1 file changed, 1 insertion(+), 1 deletion(-)
================================================================================
Redis 6.0 RC2 Released Thu Mar 05 15:40:53 CET 2020
================================================================================
Upgrade urgency MODERATE: Normal bugfixing release of a non-GA branch.
Hi Redis users, Redis 6 is approaching and will be released 30th of April.
New release candidates will be released at the end of March, then another
one mid April, to finally reach the GA at the end of April.
Redis 6 RC2 brings many fixes and new things, especially in the area of
client side caching. This is the list of big changes in this release. As
usually you can find the full list of commits at the end:
New features and improvements:
* ACL LOG: log denied commands, keys accesses and authentications.
* Client side caching redesigned. Now we use keys not caching slots.
* Client side caching: Broadcasting mode implemented.
* Client side caching: OPTIN/OPTOUT modes implemented.
* Remove RDB files used for replication in persistence-less instances (option).
Fixes (only selected ones, see commits for all the fixes):
* Different fixes to streams in edge cases.
* Fix duplicated CLIENT SETNAME reply because of RESP3 changes.
* Fix crash due to new active expire division by zero.
* Avoid sentinel changes promoted_slave to be its own replica.
* Fix bug on KEYS command where pattern starts with * followed by \x00.
* Threaded I/O: now the main thread is used as well to do I/O.
* Many fixes to modules APIs, and more to come in the next RCs.
* ld2string should fail if string contains \0 in the middle.
* Make the Redis test more reliable.
* Fix SPOP returning nil (see #4709). WARNING: API change.
qetu3790 in commit 4af0d7fd:
Fix not used constant in lru_test_mode.
1 file changed, 1 insertion(+), 1 deletion(-)
hwware in commit 6ef01878:
add missing file marco
1 file changed, 5 insertions(+)
ShooterIT in commit fe81d5c8:
Avoid compiler warnings
1 file changed, 1 insertion(+)
antirez in commit c2f01d7f:
RDB deletion: document it in example redis.conf.
1 file changed, 13 insertions(+)
antirez in commit 127e09bc:
Make sync RDB deletion configurable. Default to no.
3 files changed, 22 insertions(+), 4 deletions(-)
antirez in commit a20303c6:
Check that the file exists in removeRDBUsedToSyncReplicas().
1 file changed, 8 insertions(+), 4 deletions(-)
antirez in commit 7a23b945:
Log RDB deletion in persistence-less instances.
1 file changed, 15 insertions(+), 2 deletions(-)
antirez in commit baaf869f:
Introduce bg_unlink().
1 file changed, 31 insertions(+), 3 deletions(-)
antirez in commit be4bc1a5:
Remove RDB files used for replication in persistence-less instances.
3 files changed, 56 insertions(+), 1 deletion(-)
antirez in commit 07dc1b42:
Use a smaller getkeys global buffer.
1 file changed, 1 insertion(+), 1 deletion(-)
Oran Agra in commit 10e71b3d:
Optimize temporary memory allocations for getKeysFromCommand mechanism
1 file changed, 31 insertions(+), 10 deletions(-)
antirez in commit edc0ed14:
Modules: reformat RM_Scan() top comment a bit.
1 file changed, 21 insertions(+), 12 deletions(-)
antirez in commit c5319612:
Modules: more details in RM_Scan API top comment.
1 file changed, 22 insertions(+), 6 deletions(-)
Oran Agra in commit fff6b26a:
RM_Scan disable dict rehashing
2 files changed, 21 insertions(+), 6 deletions(-)
Guy Benoish in commit 65048460:
Add RM_CreateStringFromDouble
2 files changed, 14 insertions(+)
Oran Agra in commit 3144a278:
add no_auth to COMMAND INFO
1 file changed, 1 insertion(+)
Oran Agra in commit afe0b16c:
module api docs for aux_save and aux_load
2 files changed, 7 insertions(+), 1 deletion(-)
Guy Benoish in commit df152b0c:
streamReplyWithRangeFromConsumerPEL: Redundant streamDecodeID
1 file changed, 1 insertion(+), 3 deletions(-)
antirez in commit e3c1f439:
Show Redis version when not understanding a config directive.
1 file changed, 2 insertions(+), 1 deletion(-)
antirez in commit 141c0679:
Changelog: explain Redis 6 SPOP change.
1 file changed, 4 insertions(+), 1 deletion(-)
bodong.ybd in commit fe902461:
Fix spop return nil #4709
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit 9d4219eb:
Fix SDS misuse in enumConfigSet(). Related to #6778.
1 file changed, 3 insertions(+), 3 deletions(-)
antirez in commit 84243064:
Remove useless comment from enumConfigSet().
1 file changed, 1 deletion(-)
Ponnuvel Palaniyappan in commit dafb94db:
Fix a potential overflow with strncpy
1 file changed, 5 insertions(+), 5 deletions(-)
antirez in commit ea697b63:
Improve aeDeleteEventLoop() top comment grammar.
1 file changed, 2 insertions(+), 1 deletion(-)
wangyuan21 in commit dd479880:
free time event when delete eventloop
1 file changed, 7 insertions(+)
srzhao in commit ecf3b2ef:
fix impl of aof-child whitelist SIGUSR1 feature.
1 file changed, 5 insertions(+), 4 deletions(-)
meir@redislabs.com in commit 2966132c:
Changed log level for module fork api from 'notice' to 'verbos'.
1 file changed, 2 insertions(+), 2 deletions(-)
hwware in commit 7277e5d8:
format fix
1 file changed, 1 insertion(+), 1 deletion(-)
hwware in commit 1bb5ee9c:
fix potentical memory leaks
1 file changed, 4 insertions(+), 1 deletion(-)
Hengjian Tang in commit 97329733:
modify the read buf size according to the write buf size PROTO_IOBUF_LEN defined before
1 file changed, 1 insertion(+), 1 deletion(-)
Ariel in commit 15ea1324:
fix ThreadSafeContext lock/unlock function names
1 file changed, 2 insertions(+), 2 deletions(-)
Guy Benoish in commit 4d12c37c:
XREADGROUP should propagate XCALIM/SETID in MULTI/EXEC
1 file changed, 2 insertions(+), 2 deletions(-)
Oran Agra in commit 12626ce9:
fix race in module api test for fork
2 files changed, 2 insertions(+), 3 deletions(-)
Guy Benoish in commit 2ecab0b6:
Modules: Do not auto-unblock clients if not blocked on keys
1 file changed, 22 insertions(+), 7 deletions(-)
Oran Agra in commit 635321d4:
fix github actions failing latency test for active defrag - part 2
2 files changed, 5 insertions(+), 4 deletions(-)
Oran Agra in commit 0b988fa9:
fix github actions failing latency test for active defrag
2 files changed, 14 insertions(+), 13 deletions(-)
Oran Agra in commit 60096bc1:
Fix latency sensitivity of new defrag test
1 file changed, 32 insertions(+), 8 deletions(-)
antirez in commit b4395426:
Tracking: optin/out implemented.
3 files changed, 82 insertions(+), 16 deletions(-)
antirez in commit ef3551d1:
Test engine: experimental change to avoid busy port problems.
1 file changed, 84 insertions(+), 49 deletions(-)
antirez in commit 72c05351:
Test engine: detect timeout when checking for Redis startup.
1 file changed, 11 insertions(+), 1 deletion(-)
antirez in commit 294c9af4:
Test engine: better tracking of what workers are doing.
2 files changed, 12 insertions(+), 4 deletions(-)
hwware in commit ba027079:
add missing subcommand description for debug oom
1 file changed, 1 insertion(+)
Guy Benoish in commit 5d0890c0:
Fix memory leak in test_ld_conv
1 file changed, 4 insertions(+)
Madelyn Olson in commit d1f22eac:
Give an error message if you specify redirect twice
1 file changed, 7 insertions(+)
Madelyn Olson in commit 762fbcb6:
Minor CSC fixes and fixed documentation
2 files changed, 16 insertions(+), 17 deletions(-)
Oran Agra in commit 349aa245:
Defrag big lists in portions to avoid latency and freeze
4 files changed, 350 insertions(+), 34 deletions(-)
Guy Benoish in commit b4ddc7b7:
XGROUP DESTROY should unblock XREADGROUP with -NOGROUP
2 files changed, 11 insertions(+)
hayashier in commit 73806f74:
fix typo from fss to rss
1 file changed, 2 insertions(+), 2 deletions(-)
antirez in commit b6129f86:
Test is more complex now, increase default timeout.
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit f15fb727:
Tracking: fix max-keys configuration directive.
2 files changed, 2 insertions(+), 2 deletions(-)
Itamar Haber in commit e374573f:
Fixes segfault on calling trackingGetTotalKeys
1 file changed, 1 insertion(+)
antirez in commit 73d47d57:
Signal key as modified when expired on-access.
1 file changed, 4 insertions(+), 2 deletions(-)
antirez in commit b7cb28d5:
Tracking: first set of tests for the feature.
1 file changed, 66 insertions(+)
antirez in commit 1db72571:
Tracking: fix operators precedence error in bcast check.
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit fe96e29d:
Tracking: fix behavior when switchinig from normal to BCAST.
1 file changed, 11 insertions(+), 1 deletion(-)
antirez in commit f21be1ec:
Tracking: fix sending messages bug + tracking off bug.
2 files changed, 28 insertions(+), 20 deletions(-)
antirez in commit 6fb1aa23:
Tracking: BCAST: basic feature now works.
3 files changed, 55 insertions(+), 40 deletions(-)
antirez in commit d4fe79a1:
Tracking: BCAST: broadcasting of keys in prefixes implemented.
2 files changed, 94 insertions(+), 9 deletions(-)
antirez in commit abb81c63:
Tracking: BCAST: registration in the prefix table.
3 files changed, 67 insertions(+), 20 deletions(-)
antirez in commit 77da9608:
Tracking: BCAST: parsing of the options + skeleton.
4 files changed, 73 insertions(+), 19 deletions(-)
antirez in commit 3e8c69a9:
Tracking: always reply with an array of keys.
2 files changed, 10 insertions(+), 3 deletions(-)
antirez in commit a788c373:
Tracking: minor change of names and new INFO field.
4 files changed, 11 insertions(+), 4 deletions(-)
antirez in commit df838927:
Rax.c: populate data field after random walk.
1 file changed, 1 insertion(+)
antirez in commit 0517da36:
Tracking: rename INFO field with total items.
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit 3c16d6b3:
Tracking: first conversion from hashing to key names.
3 files changed, 84 insertions(+), 114 deletions(-)
Oran Agra in commit 3b4f1477:
add no-slowlog option to RM_CreateCommand
1 file changed, 3 insertions(+)
Khem Raj in commit 5e762d84:
Mark extern definition of SDS_NOINIT in sds.h
1 file changed, 1 insertion(+), 1 deletion(-)
lifubang in commit 54f5499a:
correct help info for --user and --pass
1 file changed, 2 insertions(+), 2 deletions(-)
Seunghoon Woo in commit 0c952b13:
[FIX] revisit CVE-2015-8080 vulnerability
1 file changed, 6 insertions(+), 4 deletions(-)
Guy Benoish in commit dd34f703:
Diskless-load emptyDb-related fixes
3 files changed, 44 insertions(+), 28 deletions(-)
lifubang in commit 5e042dbc:
fix ssl flag check for redis-cli
1 file changed, 10 insertions(+), 9 deletions(-)
Guy Benoish in commit dcbe8bfa:
Exclude "keymiss" notification from NOTIFY_ALL
5 files changed, 12 insertions(+), 7 deletions(-)
Oran Agra in commit 36caf2e4:
update RM_SignalModifiedKey doc comment
1 file changed, 2 insertions(+), 1 deletion(-)
Oran Agra in commit 3067352a:
Add handling of short read of module id in rdb
1 file changed, 4 insertions(+), 1 deletion(-)
Yossi Gottlieb in commit 9baaf858:
TLS: Update documentation.
2 files changed, 32 insertions(+), 31 deletions(-)
Oran Agra in commit 4440133e:
A few non-data commands that should be allowed while loading or stale
1 file changed, 8 insertions(+), 8 deletions(-)
Oran Agra in commit c9577941:
Memory leak when bind config is provided twice
1 file changed, 4 insertions(+)
Oran Agra in commit 1333a46b:
fix maxmemory config warning
1 file changed, 3 insertions(+), 2 deletions(-)
Oran Agra in commit 8e7282eb:
Fix client flags to be int64 in module.c
1 file changed, 3 insertions(+), 3 deletions(-)
Oran Agra in commit a678390e:
moduleRDBLoadError, add key name, and use panic rather than exit
1 file changed, 5 insertions(+), 4 deletions(-)
Oran Agra in commit 919fbf42:
reduce repeated calls to use_diskless_load
1 file changed, 3 insertions(+), 4 deletions(-)
Oran Agra in commit 22e45d46:
freeClientAsync don't lock mutex if there's just one thread
1 file changed, 6 insertions(+), 1 deletion(-)
Oran Agra in commit ba289244:
move restartAOFAfterSYNC from replicaofCommand to replicationUnsetMaster
1 file changed, 4 insertions(+), 3 deletions(-)
Oran Agra in commit f42ce57d:
stopAppendOnly resets aof_rewrite_scheduled
1 file changed, 1 insertion(+)
Oran Agra in commit df096bc9:
add SAVE subcommand to ACL HELP and top comment
1 file changed, 2 insertions(+)
Oran Agra in commit a55e5847:
DEBUG HELP - add PROTOCOL
1 file changed, 3 insertions(+), 2 deletions(-)
Guy Benoish in commit 5a6cfbf4:
Some refactroing using getClientType instead of CLIENT_SLAVE
2 files changed, 18 insertions(+), 26 deletions(-)
Guy Benoish in commit fae306b3:
Fix small bugs related to replica and monitor ambiguity
2 files changed, 8 insertions(+), 6 deletions(-)
Yossi Gottlieb in commit 73630966:
TLS: Some redis.conf clarifications.
1 file changed, 10 insertions(+), 11 deletions(-)
Oran Agra in commit 488e1947:
config.c verbose error replies for CONFIG SET, like config file parsing
1 file changed, 31 insertions(+), 97 deletions(-)
Oran Agra in commit c82ccf06:
memoryGetKeys helper function so that ACL can limit access to keys for MEMORY command
3 files changed, 18 insertions(+), 1 deletion(-)
antirez in commit 51c1a9f8:
ACL LOG: make max log entries configurable.
4 files changed, 19 insertions(+)
antirez in commit ea1e1b12:
ACL LOG: test for AUTH reason.
1 file changed, 9 insertions(+)
antirez in commit 7379c78a:
ACL LOG: log failed auth attempts.
5 files changed, 34 insertions(+), 12 deletions(-)
antirez in commit 9f6e84f6:
ACL LOG: implement a few basic tests.
1 file changed, 87 insertions(+)
antirez in commit 82790e51:
ACL LOG: also log ACL errors in the scripting/MULTI ctx.
2 files changed, 6 insertions(+), 2 deletions(-)
antirez in commit 943008eb:
ACL LOG: implement LOG RESET.
1 file changed, 6 insertions(+), 2 deletions(-)
antirez in commit e271a611:
ACL LOG: group similar entries in a given time delta.
1 file changed, 58 insertions(+), 3 deletions(-)
antirez in commit f1974d5d:
ACL LOG: actually emit entries.
3 files changed, 34 insertions(+), 5 deletions(-)
antirez in commit d9b153c9:
ACL LOG: implement ACL LOG subcommadn skeleton.
1 file changed, 37 insertions(+)
antirez in commit 577fc438:
ACL LOG: data structures and initial functions.
5 files changed, 54 insertions(+), 5 deletions(-)
Leo Murillo in commit f7a94526:
Set ZSKIPLIST_MAXLEVEL to optimal value given 2^64 elements and p=0.25
1 file changed, 1 insertion(+), 1 deletion(-)
WuYunlong in commit eecfa979:
Fix lua related memory leak.
1 file changed, 1 insertion(+)
WuYunlong in commit d2509811:
Add tcl regression test in scripting.tcl to reproduce memory leak.
1 file changed, 5 insertions(+)
Yossi Gottlieb in commit 29d4a150:
TLS: Fix missing initialization in redis-cli.
1 file changed, 9 insertions(+)
Oran Agra in commit ec0c61da:
fix uninitialized info_cb var in module.c
1 file changed, 1 insertion(+)
Guy Benoish in commit 6fe55c2f:
ld2string should fail if string contains \0 in the middle
5 files changed, 20 insertions(+), 11 deletions(-)
antirez in commit bbce3ba9:
Add more info in the unblockClientFromModule() function.
1 file changed, 7 insertions(+), 1 deletion(-)
Guy Benoish in commit 40295fb3:
Modules: Fix blocked-client-related memory leak
3 files changed, 51 insertions(+), 6 deletions(-)
antirez in commit 8e9d19bc:
Change error message for #6775.
1 file changed, 2 insertions(+), 2 deletions(-)
Vasyl Melnychuk in commit ba146d4c:
Make error when submitting command in incorrect context more explicit
1 file changed, 4 insertions(+), 1 deletion(-)
antirez in commit 721a39dd:
Document I/O threads in redis.conf.
1 file changed, 46 insertions(+)
antirez in commit 5be3a15a:
Setting N I/O threads should mean N-1 additional + 1 main thread.
1 file changed, 25 insertions(+), 22 deletions(-)
antirez in commit cbabf779:
Simplify #6379 changes.
2 files changed, 4 insertions(+), 9 deletions(-)
WuYunlong in commit 658749cc:
Free allocated sds in pfdebugCommand() to avoid memory leak.
1 file changed, 1 insertion(+)
WuYunlong in commit 47988c96:
Fix potential memory leak of clusterLoadConfig().
1 file changed, 20 insertions(+), 5 deletions(-)
WuYunlong in commit cc90f79b:
Fix potential memory leak of rioWriteBulkStreamID().
1 file changed, 4 insertions(+), 1 deletion(-)
antirez in commit ecd17e81:
Jump to right label on AOF parsing error.
1 file changed, 6 insertions(+), 4 deletions(-)
antirez in commit 1927932b:
Port PR #6110 to new connection object code.
1 file changed, 2 insertions(+), 2 deletions(-)
antirez in commit f2df5773:
A few comments about main thread serving I/O as well.
1 file changed, 7 insertions(+), 1 deletion(-)
zhaozhao.zz in commit b3ff8a4b:
Threaded IO: use main thread to handle read work
1 file changed, 8 insertions(+), 1 deletion(-)
zhaozhao.zz in commit b1f2c510:
Threaded IO: use main thread to handle write work
1 file changed, 10 insertions(+), 2 deletions(-)
ShooterIT in commit 7bbafc56:
Rename rdb asynchronously
1 file changed, 7 insertions(+)
Leo Murillo in commit c7f75266:
Fix bug on KEYS command where pattern starts with * followed by \x00 (null char).
1 file changed, 1 insertion(+), 1 deletion(-)
Jamie Scott in commit ed7ea13a:
Update to directive in redis.conf (missing s)
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit 3be77623:
Free fakeclient argv on AOF error.
1 file changed, 11 insertions(+), 3 deletions(-)
antirez in commit 15f6b748:
Git ignore: ignore more files.
1 file changed, 2 insertions(+)
Guy Benoish in commit 1b5bf40c:
Blocking XREAD[GROUP] should always reply with valid data (or timeout)
3 files changed, 44 insertions(+), 10 deletions(-)
John Sully in commit 954c20ed:
Add support for incremental build with header files
2 files changed, 6 insertions(+), 1 deletion(-)
WuYunlong in commit 11c3afd7:
Fix petential cluster link error.
1 file changed, 4 insertions(+)
Yossi Gottlieb in commit b752e83d:
Add REDISMODULE_CTX_FLAGS_MULTI_DIRTY.
2 files changed, 8 insertions(+)
hwware in commit e16eb874:
typo fix in acl.c
1 file changed, 2 insertions(+), 2 deletions(-)
Itamar Haber in commit 35ea9d23:
Adjusts 'io_threads_num' max to 128
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit 38729126:
XCLAIM: Create the consumer only on successful claims.
1 file changed, 4 insertions(+), 2 deletions(-)
yz1509 in commit b9a15303:
avoid sentinel changes promoted_slave to be its own replica.
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit 5e7e5e6b:
Fix active expire division by zero.
1 file changed, 7 insertions(+), 4 deletions(-)
antirez in commit e61dde88:
Fix duplicated CLIENT SETNAME reply.
1 file changed, 1 deletion(-)
Guy Benoish in commit cddf1da2:
Stream: Handle streamID-related edge cases
4 files changed, 54 insertions(+), 4 deletions(-)
Oran Agra in commit 52ea44e5:
config.c adjust config limits and mutable
2 files changed, 7 insertions(+), 7 deletions(-)
antirez in commit 0f28ea16:
Inline protocol: handle empty strings well.
1 file changed, 2 insertions(+), 6 deletions(-)
antirez in commit 00e5fefe:
Fix ip and missing mode in RM_GetClusterNodeInfo().
1 file changed, 5 insertions(+), 2 deletions(-)
================================================================================ ================================================================================
Redis 6.0 RC1 Released Thu Dec 19 09:58:24 CEST 2019 Redis 6.0 RC1 Released Thu Dec 19 09:58:24 CEST 2019
================================================================================ ================================================================================
@ -114,7 +1034,10 @@ Redis 6.0 is mostly a strict superset of 5.0, you should not have any problem
upgrading your application from 5.0 to 6.0. However this is a list of small upgrading your application from 5.0 to 6.0. However this is a list of small
non-backward compatible changes introduced in the 6.0 release: non-backward compatible changes introduced in the 6.0 release:
* Nothing found yet. * The SPOP <count> command no longer returns null when the set key does not
exist. Now it returns the empty set as it should and as happens when it is
called with a 0 argument. This is technically a fix, however it changes the
old behavior.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@ -88,6 +88,11 @@ Compiling is as simple as:
% make % make
To build with TLS support, you'll need OpenSSL development libraries (e.g.
libssl-dev on Debian/Ubuntu) and run:
% make BUILD_TLS=yes
You can enable flash support with: You can enable flash support with:
% make MALLOC=memkind % make MALLOC=memkind
@ -95,6 +100,13 @@ You can enable flash support with:
***Note that the following dependencies may be needed: ***Note that the following dependencies may be needed:
% sudo apt-get install autoconf autotools-dev libnuma-dev libtool % sudo apt-get install autoconf autotools-dev libnuma-dev libtool
If TLS is built, running the tests with TLS enabled (you will need `tcl-tls`
installed):
% ./utils/gen-test-certs.sh
% ./runtest --tls
Fixing build problems with dependencies or cached build options Fixing build problems with dependencies or cached build options
--------- ---------
@ -177,6 +189,14 @@ as options using the command line. Examples:
All the options in keydb.conf are also supported as options using the command All the options in keydb.conf are also supported as options using the command
line, with exactly the same name. line, with exactly the same name.
Running Redis with TLS:
------------------
Please consult the [TLS.md](TLS.md) file for more information on
how to use Redis with TLS.
Playing with KeyDB Playing with KeyDB
------------------ ------------------
@ -253,49 +273,8 @@ $ docker run -it --rm -v /path-to-dump-binaries:/keydb_bin eqalpha/keydb-build-b
``` ```
Please note that you will need libcurl4-openssl-dev in order to run keydb. With flash version you may need libnuma-dev and libtool installed in order to run the binaries. Keep this in mind especially when running in a container. For a copy of all our Dockerfiles, please see them on [docs]( https://docs.keydb.dev/docs/dockerfiles/). Please note that you will need libcurl4-openssl-dev in order to run keydb. With flash version you may need libnuma-dev and libtool installed in order to run the binaries. Keep this in mind especially when running in a container. For a copy of all our Dockerfiles, please see them on [docs]( https://docs.keydb.dev/docs/dockerfiles/).
<<<<<<< HEAD
Code contributions Code contributions
----------------- -----------------
=======
One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing replica instances connected
to our master, so that the replicas can get the writes performed by the clients:
this way their data set will remain synchronized with the one in the master.
This file also implements both the `SYNC` and `PSYNC` commands that are
used in order to perform the first synchronization between masters and
replicas, or to continue the replication after a disconnection.
Other C files
---
* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c`, `t_zset.c` and `t_stream.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.
* `ae.c` implements the Redis event loop, it's a self contained library which is simple to read and understand.
* `sds.c` is the Redis string library, check http://github.com/antirez/sds for more information.
* `anet.c` is a library to use POSIX networking in a simpler way compared to the raw interface exposed by the kernel.
* `dict.c` is an implementation of a non-blocking hash table which rehashes incrementally.
* `scripting.c` implements Lua scripting. It is completely self contained from the rest of the Redis implementation and is simple enough to understand if you are familar with the Lua API.
* `cluster.c` implements the Redis Cluster. Probably a good read only after being very familiar with the rest of the Redis code base. If you want to read `cluster.c` make sure to read the [Redis Cluster specification][3].
[3]: http://redis.io/topics/cluster-spec
Anatomy of a Redis command
---
All the Redis commands are defined in the following way:
void foobarCommand(client *c) {
printf("%s",c->argv[1]->ptr); /* Do something with the argument. */
addReply(c,shared.ok); /* Reply something to the client. */
}
The command is then referenced inside `server.c` in the command table:
{"foobar",foobarCommand,2,"rtF",0,NULL,0,0,0,0,0},
In the above example `2` is the number of arguments the command takes,
while `"rtF"` are the command flags, as documented in the command table
top comment inside `server.c`.
>>>>>>> redis/6.0
Note: by contributing code to the KeyDB project in any form, including sending Note: by contributing code to the KeyDB project in any form, including sending
a pull request via Github, a code fragment or patch via private email or a pull request via Github, a code fragment or patch via private email or

45
TLS.md
View File

@ -1,8 +1,5 @@
TLS Support -- Work In Progress TLS Support
=============================== ===========
This is a brief note to capture current thoughts/ideas and track pending action
items.
Getting Started Getting Started
--------------- ---------------
@ -69,37 +66,23 @@ probably not be so hard. For cluster keys migration it might be more difficult,
but there are probably other good reasons to improve that part anyway. but there are probably other good reasons to improve that part anyway.
To-Do List To-Do List
========== ----------
Additional TLS Features - [ ] Add session caching support. Check if/how it's handled by clients to
----------------------- assess how useful/important it is.
- [ ] redis-benchmark support. The current implementation is a mix of using
hiredis for parsing and basic networking (establishing connections), but
directly manipulating sockets for most actions. This will need to be cleaned
up for proper TLS support. The best approach is probably to migrate to hiredis
async mode.
- [ ] redis-cli `--slave` and `--rdb` support.
1. Add metrics to INFO? Multi-port
2. Add session caching support. Check if/how it's handled by clients to assess ----------
how useful/important it is.
redis-benchmark
---------------
The current implementation is a mix of using hiredis for parsing and basic
networking (establishing connections), but directly manipulating sockets for
most actions.
This will need to be cleaned up for proper TLS support. The best approach is
probably to migrate to hiredis async mode.
redis-cli
---------
1. Add support for TLS in --slave and --rdb modes.
Others
------
Consider the implications of allowing TLS to be configured on a separate port, Consider the implications of allowing TLS to be configured on a separate port,
making Redis listening on multiple ports. making Redis listening on multiple ports:
This impacts many things, like
1. Startup banner port notification 1. Startup banner port notification
2. Proctitle 2. Proctitle
3. How slaves announce themselves 3. How slaves announce themselves

View File

@ -21,7 +21,7 @@ So what usually happens is either:
The result is a pollution of binaries without line editing support. The result is a pollution of binaries without line editing support.
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not. So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
## Terminals, in 2010. ## Terminals, in 2010.
@ -126,6 +126,24 @@ Linenoise has direct support for persisting the history into an history
file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do
just that. Both functions return -1 on error and 0 on success. just that. Both functions return -1 on error and 0 on success.
## Mask mode
Sometimes it is useful to allow the user to type passwords or other
secrets that should not be displayed. For such situations linenoise supports
a "mask mode" that will just replace the characters the user is typing
with `*` characters, like in the following example:
$ ./linenoise_example
hello> get mykey
echo: 'get mykey'
hello> /mask
hello> *********
You can enable and disable mask mode using the following two functions:
void linenoiseMaskModeEnable(void);
void linenoiseMaskModeDisable(void);
## Completion ## Completion
Linenoise supports completion, which is the ability to complete the user Linenoise supports completion, which is the ability to complete the user
@ -222,3 +240,8 @@ Sometimes you may want to clear the screen as a result of something the
user typed. You can do this by calling the following function: user typed. You can do this by calling the following function:
void linenoiseClearScreen(void); void linenoiseClearScreen(void);
## Related projects
* [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like UTF-8 support, Windows support and other features. Uses C++ instead of C as development language.
* [Linenoise-swift](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift.

View File

@ -55,6 +55,7 @@ int main(int argc, char **argv) {
* *
* The typed string is returned as a malloc() allocated string by * The typed string is returned as a malloc() allocated string by
* linenoise, so the user needs to free() it. */ * linenoise, so the user needs to free() it. */
while((line = linenoise("hello> ")) != NULL) { while((line = linenoise("hello> ")) != NULL) {
/* Do something with the string. */ /* Do something with the string. */
if (line[0] != '\0' && line[0] != '/') { if (line[0] != '\0' && line[0] != '/') {
@ -65,6 +66,10 @@ int main(int argc, char **argv) {
/* The "/historylen" command will change the history len. */ /* The "/historylen" command will change the history len. */
int len = atoi(line+11); int len = atoi(line+11);
linenoiseHistorySetMaxLen(len); linenoiseHistorySetMaxLen(len);
} else if (!strncmp(line, "/mask", 5)) {
linenoiseMaskModeEnable();
} else if (!strncmp(line, "/unmask", 7)) {
linenoiseMaskModeDisable();
} else if (line[0] == '/') { } else if (line[0] == '/') {
printf("Unreconized command: %s\n", line); printf("Unreconized command: %s\n", line);
} }

View File

@ -125,6 +125,7 @@ static linenoiseHintsCallback *hintsCallback = NULL;
static linenoiseFreeHintsCallback *freeHintsCallback = NULL; static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
static struct termios orig_termios; /* In order to restore at exit.*/ static struct termios orig_termios; /* In order to restore at exit.*/
static int maskmode = 0; /* Show "***" instead of input. For passwords. */
static int rawmode = 0; /* For atexit() function to check if restore is needed*/ static int rawmode = 0; /* For atexit() function to check if restore is needed*/
static int mlmode = 0; /* Multi line mode. Default is single line. */ static int mlmode = 0; /* Multi line mode. Default is single line. */
static int atexit_registered = 0; /* Register atexit just 1 time. */ static int atexit_registered = 0; /* Register atexit just 1 time. */
@ -197,6 +198,19 @@ FILE *lndebug_fp = NULL;
/* ======================= Low level terminal handling ====================== */ /* ======================= Low level terminal handling ====================== */
/* Enable "mask mode". When it is enabled, instead of the input that
* the user is typing, the terminal will just display a corresponding
* number of asterisks, like "****". This is useful for passwords and other
* secrets that should not be displayed. */
void linenoiseMaskModeEnable(void) {
maskmode = 1;
}
/* Disable mask mode. */
void linenoiseMaskModeDisable(void) {
maskmode = 0;
}
/* Set if to use or not the multi line mode. */ /* Set if to use or not the multi line mode. */
void linenoiseSetMultiLine(int ml) { void linenoiseSetMultiLine(int ml) {
mlmode = ml; mlmode = ml;
@ -485,6 +499,8 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
if (bold == 1 && color == -1) color = 37; if (bold == 1 && color == -1) color = 37;
if (color != -1 || bold != 0) if (color != -1 || bold != 0)
snprintf(seq,64,"\033[%d;%d;49m",bold,color); snprintf(seq,64,"\033[%d;%d;49m",bold,color);
else
seq[0] = '\0';
abAppend(ab,seq,strlen(seq)); abAppend(ab,seq,strlen(seq));
abAppend(ab,hint,hintlen); abAppend(ab,hint,hintlen);
if (color != -1 || bold != 0) if (color != -1 || bold != 0)
@ -523,7 +539,11 @@ static void refreshSingleLine(struct linenoiseState *l) {
abAppend(&ab,seq,strlen(seq)); abAppend(&ab,seq,strlen(seq));
/* Write the prompt and the current buffer content */ /* Write the prompt and the current buffer content */
abAppend(&ab,l->prompt,strlen(l->prompt)); abAppend(&ab,l->prompt,strlen(l->prompt));
abAppend(&ab,buf,len); if (maskmode == 1) {
while (len--) abAppend(&ab,"*",1);
} else {
abAppend(&ab,buf,len);
}
/* Show hits if any. */ /* Show hits if any. */
refreshShowHints(&ab,l,plen); refreshShowHints(&ab,l,plen);
/* Erase to right */ /* Erase to right */
@ -577,7 +597,12 @@ static void refreshMultiLine(struct linenoiseState *l) {
/* Write the prompt and the current buffer content */ /* Write the prompt and the current buffer content */
abAppend(&ab,l->prompt,strlen(l->prompt)); abAppend(&ab,l->prompt,strlen(l->prompt));
abAppend(&ab,l->buf,l->len); if (maskmode == 1) {
unsigned int i;
for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
} else {
abAppend(&ab,l->buf,l->len);
}
/* Show hits if any. */ /* Show hits if any. */
refreshShowHints(&ab,l,plen); refreshShowHints(&ab,l,plen);
@ -645,7 +670,8 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) {
if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
/* Avoid a full update of the line in the /* Avoid a full update of the line in the
* trivial case. */ * trivial case. */
if (write(l->ofd,&c,1) == -1) return -1; char d = (maskmode==1) ? '*' : c;
if (write(l->ofd,&d,1) == -1) return -1;
} else { } else {
refreshLine(l); refreshLine(l);
} }

View File

@ -65,6 +65,8 @@ int linenoiseHistoryLoad(const char *filename);
void linenoiseClearScreen(void); void linenoiseClearScreen(void);
void linenoiseSetMultiLine(int ml); void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void); void linenoisePrintKeyCodes(void);
void linenoiseMaskModeEnable(void);
void linenoiseMaskModeDisable(void);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -89,12 +89,14 @@ typedef struct Header {
} Header; } Header;
static int getnum (const char **fmt, int df) { static int getnum (lua_State *L, const char **fmt, int df) {
if (!isdigit(**fmt)) /* no number? */ if (!isdigit(**fmt)) /* no number? */
return df; /* return default value */ return df; /* return default value */
else { else {
int a = 0; int a = 0;
do { do {
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
luaL_error(L, "integral size overflow");
a = a*10 + *((*fmt)++) - '0'; a = a*10 + *((*fmt)++) - '0';
} while (isdigit(**fmt)); } while (isdigit(**fmt));
return a; return a;
@ -115,9 +117,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
case 'f': return sizeof(float); case 'f': return sizeof(float);
case 'd': return sizeof(double); case 'd': return sizeof(double);
case 'x': return 1; case 'x': return 1;
case 'c': return getnum(fmt, 1); case 'c': return getnum(L, fmt, 1);
case 'i': case 'I': { case 'i': case 'I': {
int sz = getnum(fmt, sizeof(int)); int sz = getnum(L, fmt, sizeof(int));
if (sz > MAXINTSIZE) if (sz > MAXINTSIZE)
luaL_error(L, "integral size %d is larger than limit of %d", luaL_error(L, "integral size %d is larger than limit of %d",
sz, MAXINTSIZE); sz, MAXINTSIZE);
@ -150,7 +152,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
case '>': h->endian = BIG; return; case '>': h->endian = BIG; return;
case '<': h->endian = LITTLE; return; case '<': h->endian = LITTLE; return;
case '!': { case '!': {
int a = getnum(fmt, MAXALIGN); int a = getnum(L, fmt, MAXALIGN);
if (!isp2(a)) if (!isp2(a))
luaL_error(L, "alignment %d is not a power of 2", a); luaL_error(L, "alignment %d is not a power of 2", a);
h->align = a; h->align = a;

View File

@ -142,7 +142,8 @@ tcp-keepalive 300
# server to connected clients, masters or cluster peers. These files should be # server to connected clients, masters or cluster peers. These files should be
# PEM formatted. # PEM formatted.
# #
# tls-cert-file redis.crt tls-key-file redis.key # tls-cert-file redis.crt
# tls-key-file redis.key
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange: # Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
# #
@ -155,29 +156,27 @@ tcp-keepalive 300
# tls-ca-cert-file ca.crt # tls-ca-cert-file ca.crt
# tls-ca-cert-dir /etc/ssl/certs # tls-ca-cert-dir /etc/ssl/certs
# If TLS/SSL clients are required to authenticate using a client side # By default, clients (including replica servers) on a TLS port are required
# certificate, use this directive. # to authenticate using valid client side certificates.
# #
# Note: this applies to all incoming clients, including replicas. # It is possible to disable authentication using this directive.
# #
# tls-auth-clients yes # tls-auth-clients no
# If TLS/SSL should be used when connecting as a replica to a master, enable # By default, a Redis replica does not attempt to establish a TLS connection
# this configuration directive: # with its master.
#
# Use the following directive to enable TLS on replication links.
# #
# tls-replication yes # tls-replication yes
# If TLS/SSL should be used for the Redis Cluster bus, enable this configuration # By default, the Redis Cluster bus uses a plain TCP connection. To enable
# directive. # TLS for the bus protocol, use the following directive:
#
# NOTE: If TLS/SSL is enabled for Cluster Bus, mutual authentication is always
# enforced.
# #
# tls-cluster yes # tls-cluster yes
# Explicitly specify TLS versions to support. Allowed values are case insensitive # Explicitly specify TLS versions to support. Allowed values are case insensitive
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or # and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1)
# "default" which is currently >= TLSv1.1.
# #
# tls-protocols TLSv1.2 # tls-protocols TLSv1.2
@ -322,6 +321,19 @@ rdbchecksum yes
# The filename where to dump the DB # The filename where to dump the DB
dbfilename dump.rdb dbfilename dump.rdb
# Remove RDB files used by replication in instances without persistence
# enabled. By default this option is disabled, however there are environments
# where for regulations or other security concerns, RDB files persisted on
# disk by masters in order to feed replicas, or stored on disk by replicas
# in order to load them for the initial synchronization, should be deleted
# ASAP. Note that this option ONLY WORKS in instances that have both AOF
# and RDB persistence disabled, otherwise is completely ignored.
#
# An alternative (and sometimes better) way to obtain the same effect is
# to use diskless replication on both master and replicas instances. However
# in the case of replicas, diskless is not always an option.
rdb-del-sync-files no
# The working directory. # The working directory.
# #
# The DB will be written inside this directory, with the filename specified # The DB will be written inside this directory, with the filename specified
@ -938,6 +950,52 @@ lazyfree-lazy-expire no
lazyfree-lazy-server-del no lazyfree-lazy-server-del no
replica-lazy-flush no replica-lazy-flush no
################################ THREADED I/O #################################
# Redis is mostly single threaded, however there are certain threaded
# operations such as UNLINK, slow I/O accesses and other things that are
# performed on side threads.
#
# Now it is also possible to handle Redis clients socket reads and writes
# in different I/O threads. Since especially writing is so slow, normally
# Redis users use pipelining in order to speedup the Redis performances per
# core, and spawn multiple instances in order to scale more. Using I/O
# threads it is possible to easily speedup two times Redis without resorting
# to pipelining nor sharding of the instance.
#
# By default threading is disabled, we suggest enabling it only in machines
# that have at least 4 or more cores, leaving at least one spare core.
# Using more than 8 threads is unlikely to help much. We also recommend using
# threaded I/O only if you actually have performance problems, with Redis
# instances being able to use a quite big percentage of CPU time, otherwise
# there is no point in using this feature.
#
# So for instance if you have a four cores boxes, try to use 2 or 3 I/O
# threads, if you have a 8 cores, try to use 6 threads. In order to
# enable I/O threads use the following configuration directive:
#
# io-threads 4
#
# Setting io-threads to 1 will just use the main thread as usually.
# When I/O threads are enabled, we only use threads for writes, that is
# to thread the write(2) syscall and transfer the client buffers to the
# socket. However it is also possible to enable threading of reads and
# protocol parsing using the following configuration directive, by setting
# it to yes:
#
# io-threads-do-reads no
#
# Usually threading reads doesn't help much.
#
# NOTE 1: This configuration directive cannot be changed at runtime via
# CONFIG SET. Aso this feature currently does not work when SSL is
# enabled.
#
# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make
# sure you also run the benchmark itself in threaded mode, using the
# --threads option to match the number of Redis theads, otherwise you'll not
# be able to notice the improvements.
############################## APPEND ONLY MODE ############################### ############################## APPEND ONLY MODE ###############################
# By default KeyDB asynchronously dumps the dataset on disk. This mode is # By default KeyDB asynchronously dumps the dataset on disk. This mode is
@ -1316,7 +1374,11 @@ latency-monitor-threshold 0
# z Sorted set commands # z Sorted set commands
# 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)
# A Alias for g$lshzxe, so that the "AKE" string means all the events. # t Stream commands
# 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
# (Except key-miss events which are excluded from 'A' due to their
# unique nature).
# #
# The "notify-keyspace-events" takes as argument a string that is composed # The "notify-keyspace-events" takes as argument a string that is composed
# of zero or multiple characters. The empty string means that notifications # of zero or multiple characters. The empty string means that notifications
@ -1566,7 +1628,7 @@ hz 10
# offers, and enables by default, the ability to use an adaptive HZ value # offers, and enables by default, the ability to use an adaptive HZ value
# which will temporary raise when there are many connected clients. # which will temporary raise when there are many connected clients.
# #
# When dynamic HZ is enabled, the actual configured HZ will be used as # When dynamic HZ is enabled, the actual configured HZ will be used
# as a baseline, but multiples of the configured HZ value will be actually # as a baseline, but multiples of the configured HZ value will be actually
# used as needed once more clients are connected. In this way an idle # used as needed once more clients are connected. In this way an idle
# instance will use very little CPU time while a busy instance will be # instance will use very little CPU time while a busy instance will be

View File

@ -102,6 +102,18 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# #
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# sentinel auth-user <master-name> <username>
#
# This is useful in order to authenticate to instances having ACL capabilities,
# that is, running Redis 6.0 or greater. When just auth-pass is provided the
# Sentinel instance will authenticate to Redis using the old "AUTH <pass>"
# method. When also an username is provided, it will use "AUTH <user> <pass>".
# In the Redis servers side, the ACL to provide just minimal access to
# Sentinel instances, should be configured along the following lines:
#
# user sentinel-user >somepassword +client +subscribe +publish \
# +ping +info +multi +slaveof +config +client +exec on
# sentinel down-after-milliseconds <master-name> <milliseconds> # sentinel down-after-milliseconds <master-name> <milliseconds>
# #
# Number of milliseconds the master (or any attached replica or sentinel) should # Number of milliseconds the master (or any attached replica or sentinel) should
@ -112,6 +124,14 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# Default is 30 seconds. # Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000 sentinel down-after-milliseconds mymaster 30000
# requirepass <password>
#
# You can configure Sentinel itself to require a password, however when doing
# so Sentinel will try to authenticate with the same password to all the
# other Sentinels. So you need to configure all your Sentinels in a given
# group with the same "requirepass" password. Check the following documentation
# for more info: https://redis.io/topics/sentinel
# sentinel parallel-syncs <master-name> <numreplicas> # sentinel parallel-syncs <master-name> <numreplicas>
# #
# How many replicas we can reconfigure to point to the new replica simultaneously # How many replicas we can reconfigure to point to the new replica simultaneously

View File

@ -274,7 +274,7 @@ endif
REDIS_SERVER_NAME=keydb-pro-server REDIS_SERVER_NAME=keydb-pro-server
REDIS_SENTINEL_NAME=keydb-sentinel REDIS_SENTINEL_NAME=keydb-sentinel
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o AsyncWorkQueue.o snapshot.o storage/rocksdb.o storage/rocksdbfactory.o storage/teststorageprovider.o keydbutils.o motd.o $(ASM_OBJ) REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o AsyncWorkQueue.o snapshot.o storage/rocksdb.o storage/rocksdbfactory.o storage/teststorageprovider.o keydbutils.o $(ASM_OBJ)
REDIS_CLI_NAME=keydb-cli REDIS_CLI_NAME=keydb-cli
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ) REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ)
REDIS_BENCHMARK_NAME=keydb-benchmark REDIS_BENCHMARK_NAME=keydb-benchmark

View File

@ -51,6 +51,8 @@ list *UsersToLoad; /* This is a list of users found in the configuration file
array of SDS pointers: the first is the user name, array of SDS pointers: the first is the user name,
all the remaining pointers are ACL rules in the same all the remaining pointers are ACL rules in the same
format as ACLSetUser(). */ format as ACLSetUser(). */
list *ACLLog; /* Our security log, the user is able to inspect that
using the ACL LOG command .*/
struct ACLCategoryItem { struct ACLCategoryItem {
const char *name; const char *name;
@ -95,6 +97,7 @@ struct ACLUserFlag {
void ACLResetSubcommandsForCommand(user *u, unsigned long id); void ACLResetSubcommandsForCommand(user *u, unsigned long id);
void ACLResetSubcommands(user *u); void ACLResetSubcommands(user *u);
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub); void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
void ACLFreeLogEntry(struct ACLLogEntry *le);
/* The length of the string representation of a hashed password. */ /* The length of the string representation of a hashed password. */
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2 #define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
@ -898,16 +901,6 @@ const char *ACLSetUserStringError(void) {
return errmsg; return errmsg;
} }
/* Return the first password of the default user or NULL.
* This function is needed for backward compatibility with the old
* directive "requirepass" when Redis supported a single global
* password. */
sds ACLDefaultUserFirstPassword(void) {
if (listLength(DefaultUser->passwords) == 0) return NULL;
listNode *first = listFirst(DefaultUser->passwords);
return (sds)listNodeValue(first);
}
/* Initialize the default user, that will always exist for all the process /* Initialize the default user, that will always exist for all the process
* lifetime. */ * lifetime. */
void ACLInitDefaultUser(void) { void ACLInitDefaultUser(void) {
@ -922,7 +915,9 @@ void ACLInitDefaultUser(void) {
void ACLInit(void) { void ACLInit(void) {
Users = raxNew(); Users = raxNew();
UsersToLoad = listCreate(); UsersToLoad = listCreate();
ACLLog = listCreate();
ACLInitDefaultUser(); ACLInitDefaultUser();
g_pserver->requirepass = NULL; /* Only used for backward compatibility. */
} }
/* Check the username and password pair and return C_OK if they are valid, /* Check the username and password pair and return C_OK if they are valid,
@ -980,6 +975,7 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
moduleNotifyUserChanged(c); moduleNotifyUserChanged(c);
return C_OK; return C_OK;
} else { } else {
addACLLogEntry(c,ACL_DENIED_AUTH,0,szFromObj(username));
return C_ERR; return C_ERR;
} }
} }
@ -1029,14 +1025,14 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
/* Check if the command is ready to be executed in the client 'c', already /* Check if the command is ready to be executed in the client 'c', already
* referenced by c->cmd, and can be executed by this client according to the * referenced by c->cmd, and can be executed by this client according to the
* ACLs associated to the client user c->user. * ACLs associated to the client user c->puser.
* *
* If the user can execute the command ACL_OK is returned, otherwise * If the user can execute the command ACL_OK is returned, otherwise
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the * ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
* command cannot be executed because the user is not allowed to run such * command cannot be executed because the user is not allowed to run such
* command, the second if the command is denied because the user is trying * command, the second if the command is denied because the user is trying
* to access keys that are not among the specified patterns. */ * to access keys that are not among the specified patterns. */
int ACLCheckCommandPerm(client *c) { int ACLCheckCommandPerm(client *c, int *keyidxptr) {
user *u = c->puser; user *u = c->puser;
uint64_t id = c->cmd->id; uint64_t id = c->cmd->id;
@ -1096,6 +1092,7 @@ int ACLCheckCommandPerm(client *c) {
} }
} }
if (!match) { if (!match) {
if (keyidxptr) *keyidxptr = keyidx[j];
getKeysFreeResult(keyidx); getKeysFreeResult(keyidx);
return ACL_DENIED_KEY; return ACL_DENIED_KEY;
} }
@ -1456,6 +1453,131 @@ void ACLLoadUsersAtStartup(void) {
} }
} }
/* =============================================================================
* ACL log
* ==========================================================================*/
#define ACL_LOG_CTX_TOPLEVEL 0
#define ACL_LOG_CTX_LUA 1
#define ACL_LOG_CTX_MULTI 2
#define ACL_LOG_GROUPING_MAX_TIME_DELTA 60000
/* This structure defines an entry inside the ACL log. */
typedef struct ACLLogEntry {
uint64_t count; /* Number of times this happened recently. */
int reason; /* Reason for denying the command. ACL_DENIED_*. */
int context; /* Toplevel, Lua or MULTI/EXEC? ACL_LOG_CTX_*. */
sds object; /* The key name or command name. */
sds username; /* User the client is authenticated with. */
mstime_t ctime; /* Milliseconds time of last update to this entry. */
sds cinfo; /* Client info (last client if updated). */
} ACLLogEntry;
/* This function will check if ACL entries 'a' and 'b' are similar enough
* that we should actually update the existing entry in our ACL log instead
* of creating a new one. */
int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
if (a->reason != b->reason) return 0;
if (a->context != b->context) return 0;
mstime_t delta = a->ctime - b->ctime;
if (delta < 0) delta = -delta;
if (delta > ACL_LOG_GROUPING_MAX_TIME_DELTA) return 0;
if (sdscmp(a->object,b->object) != 0) return 0;
if (sdscmp(a->username,b->username) != 0) return 0;
return 1;
}
/* Release an ACL log entry. */
void ACLFreeLogEntry(ACLLogEntry *leptr) {
ACLLogEntry *le = leptr;
sdsfree(le->object);
sdsfree(le->username);
sdsfree(le->cinfo);
zfree(le);
}
/* Adds a new entry in the ACL log, making sure to delete the old entry
* if we reach the maximum length allowed for the log. This function attempts
* to find similar entries in the current log in order to bump the counter of
* the log entry instead of creating many entries for very similar ACL
* rules issues.
*
* The keypos argument is only used when the reason is ACL_DENIED_KEY, since
* it allows the function to log the key name that caused the problem.
* Similarly the username is only passed when we failed to authenticate the
* user with AUTH or HELLO, for the ACL_DENIED_AUTH reason. Otherwise
* it will just be NULL.
*/
void addACLLogEntry(client *c, int reason, int keypos, sds username) {
/* Create a new entry. */
struct ACLLogEntry *le = (ACLLogEntry*)zmalloc(sizeof(*le));
le->count = 1;
le->reason = reason;
le->username = sdsdup(reason == ACL_DENIED_AUTH ? username : c->puser->name);
le->ctime = mstime();
switch(reason) {
case ACL_DENIED_CMD: le->object = sdsnew(c->cmd->name); break;
case ACL_DENIED_KEY: le->object = sdsnew(szFromObj(c->argv[keypos])); break;
case ACL_DENIED_AUTH: le->object = sdsnew(szFromObj(c->argv[0])); break;
default: le->object = sdsempty();
}
client *realclient = c;
if (realclient->flags & CLIENT_LUA) realclient = g_pserver->lua_caller;
le->cinfo = catClientInfoString(sdsempty(),realclient);
if (c->flags & CLIENT_MULTI) {
le->context = ACL_LOG_CTX_MULTI;
} else if (c->flags & CLIENT_LUA) {
le->context = ACL_LOG_CTX_LUA;
} else {
le->context = ACL_LOG_CTX_TOPLEVEL;
}
/* Try to match this entry with past ones, to see if we can just
* update an existing entry instead of creating a new one. */
long toscan = 10; /* Do a limited work trying to find duplicated. */
listIter li;
listNode *ln;
listRewind(ACLLog,&li);
ACLLogEntry *match = NULL;
while (toscan-- && (ln = listNext(&li)) != NULL) {
ACLLogEntry *current = (ACLLogEntry*)listNodeValue(ln);
if (ACLLogMatchEntry(current,le)) {
match = current;
listDelNode(ACLLog,ln);
listAddNodeHead(ACLLog,current);
break;
}
}
/* If there is a match update the entry, otherwise add it as a
* new one. */
if (match) {
/* We update a few fields of the existing entry and bump the
* counter of events for this entry. */
sdsfree(match->cinfo);
match->cinfo = le->cinfo;
match->ctime = le->ctime;
match->count++;
/* Release the old entry. */
le->cinfo = NULL;
ACLFreeLogEntry(le);
} else {
/* Add it to our list of entires. We'll have to trim the list
* to its maximum size. */
listAddNodeHead(ACLLog, le);
while(listLength(ACLLog) > g_pserver->acllog_max_len) {
listNode *ln = listLast(ACLLog);
ACLLogEntry *le = (ACLLogEntry*)listNodeValue(ln);
ACLFreeLogEntry(le);
listDelNode(ACLLog,ln);
}
}
}
/* ============================================================================= /* =============================================================================
* ACL related commands * ACL related commands
* ==========================================================================*/ * ==========================================================================*/
@ -1463,6 +1585,7 @@ void ACLLoadUsersAtStartup(void) {
/* ACL -- show and modify the configuration of ACL users. /* ACL -- show and modify the configuration of ACL users.
* ACL HELP * ACL HELP
* ACL LOAD * ACL LOAD
* ACL SAVE
* ACL LIST * ACL LIST
* ACL USERS * ACL USERS
* ACL CAT [<category>] * ACL CAT [<category>]
@ -1471,6 +1594,7 @@ void ACLLoadUsersAtStartup(void) {
* ACL GETUSER <username> * ACL GETUSER <username>
* ACL GENPASS * ACL GENPASS
* ACL WHOAMI * ACL WHOAMI
* ACL LOG [<count> | RESET]
*/ */
void aclCommand(client *c) { void aclCommand(client *c) {
char *sub = szFromObj(c->argv[1]); char *sub = szFromObj(c->argv[1]);
@ -1657,9 +1781,76 @@ void aclCommand(client *c) {
char pass[32]; /* 128 bits of actual pseudo random data. */ char pass[32]; /* 128 bits of actual pseudo random data. */
getRandomHexChars(pass,sizeof(pass)); getRandomHexChars(pass,sizeof(pass));
addReplyBulkCBuffer(c,pass,sizeof(pass)); addReplyBulkCBuffer(c,pass,sizeof(pass));
} else if (!strcasecmp(sub,"log") && (c->argc == 2 || c->argc ==3)) {
long count = 10; /* Number of entries to emit by default. */
/* Parse the only argument that LOG may have: it could be either
* the number of entires the user wants to display, or alternatively
* the "RESET" command in order to flush the old entires. */
if (c->argc == 3) {
if (!strcasecmp(szFromObj(c->argv[2]),"reset")) {
listSetFreeMethod(ACLLog,(void(*)(const void*))ACLFreeLogEntry);
listEmpty(ACLLog);
listSetFreeMethod(ACLLog,NULL);
addReply(c,shared.ok);
return;
} else if (getLongFromObjectOrReply(c,c->argv[2],&count,NULL)
!= C_OK)
{
return;
}
if (count < 0) count = 0;
}
/* Fix the count according to the number of entries we got. */
if ((size_t)count > listLength(ACLLog))
count = listLength(ACLLog);
addReplyArrayLen(c,count);
listIter li;
listNode *ln;
listRewind(ACLLog,&li);
mstime_t now = mstime();
while (count-- && (ln = listNext(&li)) != NULL) {
ACLLogEntry *le = (ACLLogEntry*)listNodeValue(ln);
addReplyMapLen(c,7);
addReplyBulkCString(c,"count");
addReplyLongLong(c,le->count);
addReplyBulkCString(c,"reason");
const char *reasonstr = "INVALID_REASON";
switch(le->reason) {
case ACL_DENIED_CMD: reasonstr="command"; break;
case ACL_DENIED_KEY: reasonstr="key"; break;
case ACL_DENIED_AUTH: reasonstr="auth"; break;
default: reasonstr="unknown";
}
addReplyBulkCString(c,reasonstr);
addReplyBulkCString(c,"context");
const char *ctxstr;
switch(le->context) {
case ACL_LOG_CTX_TOPLEVEL: ctxstr="toplevel"; break;
case ACL_LOG_CTX_MULTI: ctxstr="multi"; break;
case ACL_LOG_CTX_LUA: ctxstr="lua"; break;
default: ctxstr="unknown";
}
addReplyBulkCString(c,ctxstr);
addReplyBulkCString(c,"object");
addReplyBulkCBuffer(c,le->object,sdslen(le->object));
addReplyBulkCString(c,"username");
addReplyBulkCBuffer(c,le->username,sdslen(le->username));
addReplyBulkCString(c,"age-seconds");
double age = (double)(now - le->ctime)/1000;
addReplyDouble(c,age);
addReplyBulkCString(c,"client-info");
addReplyBulkCBuffer(c,le->cinfo,sdslen(le->cinfo));
}
} else if (!strcasecmp(sub,"help")) { } else if (!strcasecmp(sub,"help")) {
const char *help[] = { const char *help[] = {
"LOAD -- Reload users from the ACL file.", "LOAD -- Reload users from the ACL file.",
"SAVE -- Save the current config to the ACL file."
"LIST -- Show user details in config file format.", "LIST -- Show user details in config file format.",
"USERS -- List all the registered usernames.", "USERS -- List all the registered usernames.",
"SETUSER <username> [attribs ...] -- Create or modify a user.", "SETUSER <username> [attribs ...] -- Create or modify a user.",
@ -1669,6 +1860,7 @@ void aclCommand(client *c) {
"CAT <category> -- List commands inside category.", "CAT <category> -- List commands inside category.",
"GENPASS -- Generate a secure user password.", "GENPASS -- Generate a secure user password.",
"WHOAMI -- Return the current connection username.", "WHOAMI -- Return the current connection username.",
"LOG [<count> | RESET] -- Show the ACL log entries.",
NULL NULL
}; };
addReplyHelp(c,help); addReplyHelp(c,help);

View File

@ -407,6 +407,7 @@ extern "C" void aeDeleteEventLoop(aeEventLoop *eventLoop) {
close(eventLoop->fdCmdRead); close(eventLoop->fdCmdRead);
close(eventLoop->fdCmdWrite); close(eventLoop->fdCmdWrite);
/* Free the time events list. */
auto *te = eventLoop->timeEventHead; auto *te = eventLoop->timeEventHead;
while (te) while (te)
{ {
@ -684,6 +685,7 @@ extern "C" void ProcessEventCore(aeEventLoop *eventLoop, aeFileEvent *fe, int ma
LOCK_IF_NECESSARY(fe, AE_READ_THREADSAFE); LOCK_IF_NECESSARY(fe, AE_READ_THREADSAFE);
fe->rfileProc(eventLoop,fd,fe->clientData,mask | (fe->mask & AE_READ_THREADSAFE)); fe->rfileProc(eventLoop,fd,fe->clientData,mask | (fe->mask & AE_READ_THREADSAFE));
fired++; fired++;
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
} }
/* Fire the writable event. */ /* Fire the writable event. */

View File

@ -257,6 +257,7 @@ void stopAppendOnly(void) {
g_pserver->aof_fd = -1; g_pserver->aof_fd = -1;
g_pserver->aof_selected_db = -1; g_pserver->aof_selected_db = -1;
g_pserver->aof_state = AOF_OFF; g_pserver->aof_state = AOF_OFF;
g_pserver->aof_rewrite_scheduled = 0;
killAppendOnlyChild(); killAppendOnlyChild();
} }
@ -598,21 +599,59 @@ sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, r
return buf; return buf;
} }
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) { sds catAppendOnlyExpireMemberAtCommand(sds buf, struct redisCommand *cmd, robj **argv, const size_t argc) {
sds buf = sdsempty(); long long when = 0;
robj *tmpargv[3]; int unit = UNIT_SECONDS;
bool fAbsolute = false;
/* The DB this command was targeting is not the same as the last command if (cmd->proc == expireMemberCommand) {
* we appended. To issue a SELECT command is needed. */ if (getLongLongFromObject(argv[3], &when) != C_OK)
if (dictid != g_pserver->aof_selected_db) { serverPanic("propogating invalid EXPIREMEMBER command");
char seldb[64];
snprintf(seldb,sizeof(seldb),"%d",dictid); if (argc == 5) {
buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n", unit = parseUnitString(szFromObj(argv[4]));
(unsigned long)strlen(seldb),seldb); }
g_pserver->aof_selected_db = dictid; } else if (cmd->proc == expireMemberAtCommand) {
if (getLongLongFromObject(argv[3], &when) != C_OK)
serverPanic("propogating invalid EXPIREMEMBERAT command");
fAbsolute = true;
} else if (cmd->proc == pexpireMemberAtCommand) {
if (getLongLongFromObject(argv[3], &when) != C_OK)
serverPanic("propogating invalid PEXPIREMEMBERAT command");
fAbsolute = true;
unit = UNIT_MILLISECONDS;
} else {
serverPanic("Unknown expiremember command");
} }
switch (unit)
{
case UNIT_SECONDS:
when *= 1000;
break;
case UNIT_MILLISECONDS:
break;
}
if (!fAbsolute)
when += mstime();
robj *argvNew[4];
argvNew[0] = createStringObject("PEXPIREMEMBERAT",15);
argvNew[1] = argv[1];
argvNew[2] = argv[2];
argvNew[3] = createStringObjectFromLongLong(when);
buf = catAppendOnlyGenericCommand(buf, 4, argvNew);
decrRefCount(argvNew[0]);
decrRefCount(argvNew[3]);
return buf;
}
sds catCommandForAofAndActiveReplication(sds buf, struct redisCommand *cmd, robj **argv, int argc)
{
robj *tmpargv[3];
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand || if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
cmd->proc == expireatCommand) { cmd->proc == expireatCommand) {
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */ /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
@ -641,6 +680,10 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
if (pxarg) if (pxarg)
buf = catAppendOnlyExpireAtCommand(buf,cserver.pexpireCommand,argv[1], buf = catAppendOnlyExpireAtCommand(buf,cserver.pexpireCommand,argv[1],
pxarg); pxarg);
} else if (cmd->proc == expireMemberCommand || cmd->proc == expireMemberAtCommand ||
cmd->proc == pexpireMemberAtCommand) {
/* Translate subkey expire commands to PEXPIREMEMBERAT */
buf = catAppendOnlyExpireMemberAtCommand(buf, cmd, argv, argc);
} else { } else {
/* All the other commands don't need translation or need the /* All the other commands don't need translation or need the
* same translation already operated in the command vector * same translation already operated in the command vector
@ -648,6 +691,25 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
buf = catAppendOnlyGenericCommand(buf,argc,argv); buf = catAppendOnlyGenericCommand(buf,argc,argv);
} }
return buf;
}
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
sds buf = sdsempty();
/* The DB this command was targeting is not the same as the last command
* we appended. To issue a SELECT command is needed. */
if (dictid != g_pserver->aof_selected_db) {
char seldb[64];
snprintf(seldb,sizeof(seldb),"%d",dictid);
buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
(unsigned long)strlen(seldb),seldb);
g_pserver->aof_selected_db = dictid;
}
buf = catCommandForAofAndActiveReplication(buf, cmd, argv, argc);
/* Append to the AOF buffer. This will be flushed on disk just before /* Append to the AOF buffer. This will be flushed on disk just before
* of re-entering the event loop, so before the client will get a * of re-entering the event loop, so before the client will get a
* positive reply about the operation performed. */ * positive reply about the operation performed. */
@ -821,12 +883,14 @@ int loadAppendOnlyFile(char *filename) {
for (j = 0; j < argc; j++) { for (j = 0; j < argc; j++) {
/* Parse the argument len. */ /* Parse the argument len. */
if (fgets(buf,sizeof(buf),fp) == NULL || char *readres = fgets(buf,sizeof(buf),fp);
buf[0] != '$') if (readres == NULL || buf[0] != '$') {
{
fakeClient->argc = j; /* Free up to j-1. */ fakeClient->argc = j; /* Free up to j-1. */
freeFakeClientArgv(fakeClient); freeFakeClientArgv(fakeClient);
goto readerr; if (readres == NULL)
goto readerr;
else
goto fmterr;
} }
len = strtol(buf+1,NULL,10); len = strtol(buf+1,NULL,10);
@ -860,7 +924,7 @@ int loadAppendOnlyFile(char *filename) {
if (cmd == cserver.multiCommand) valid_before_multi = valid_up_to; if (cmd == cserver.multiCommand) valid_before_multi = valid_up_to;
/* Run the command in the context of a fake client */ /* Run the command in the context of a fake client */
fakeClient->cmd = cmd; fakeClient->cmd = fakeClient->lastcmd = cmd;
if (fakeClient->flags & CLIENT_MULTI && if (fakeClient->flags & CLIENT_MULTI &&
fakeClient->cmd->proc != execCommand) fakeClient->cmd->proc != execCommand)
{ {
@ -1184,7 +1248,7 @@ int rioWriteBulkStreamID(rio *r,streamID *id) {
int retval; int retval;
sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq); sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq);
if ((retval = rioWriteBulkString(r,replyid,sdslen(replyid))) == 0) return 0; retval = rioWriteBulkString(r,replyid,sdslen(replyid));
sdsfree(replyid); sdsfree(replyid);
return retval; return retval;
} }
@ -1246,12 +1310,13 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
/* Use the XADD MAXLEN 0 trick to generate an empty stream if /* Use the XADD MAXLEN 0 trick to generate an empty stream if
* the key we are serializing is an empty string, which is possible * the key we are serializing is an empty string, which is possible
* for the Stream type. */ * for the Stream type. */
id.ms = 0; id.seq = 1;
if (rioWriteBulkCount(r,'*',7) == 0) return 0; if (rioWriteBulkCount(r,'*',7) == 0) return 0;
if (rioWriteBulkString(r,"XADD",4) == 0) return 0; if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkObject(r,key) == 0) return 0;
if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0; if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0;
if (rioWriteBulkString(r,"0",1) == 0) return 0; if (rioWriteBulkString(r,"0",1) == 0) return 0;
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0; if (rioWriteBulkStreamID(r,&id) == 0) return 0;
if (rioWriteBulkString(r,"x",1) == 0) return 0; if (rioWriteBulkString(r,"x",1) == 0) return 0;
if (rioWriteBulkString(r,"y",1) == 0) return 0; if (rioWriteBulkString(r,"y",1) == 0) return 0;
} }
@ -1344,7 +1409,6 @@ ssize_t aofReadDiffFromParent(void) {
} }
int rewriteAppendOnlyFileRio(rio *aof) { int rewriteAppendOnlyFileRio(rio *aof) {
dictIterator *di = NULL;
size_t processed = 0; size_t processed = 0;
int j; int j;
@ -1399,7 +1463,7 @@ int rewriteAppendOnlyFileRio(rio *aof) {
} }
else else
{ {
char cmd[]="*4\r\n$12\r\nEXPIREMEMBER\r\n"; char cmd[]="*4\r\n$12\r\nPEXPIREMEMBERAT\r\n";
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) return false; if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) return false;
if (rioWriteBulkObject(aof,&key) == 0) return false; if (rioWriteBulkObject(aof,&key) == 0) return false;
if (rioWrite(aof,subExpire.subkey(),sdslen(subExpire.subkey())) == 0) return false; if (rioWrite(aof,subExpire.subkey(),sdslen(subExpire.subkey())) == 0) return false;
@ -1420,7 +1484,6 @@ int rewriteAppendOnlyFileRio(rio *aof) {
return C_OK; return C_OK;
werr: werr:
if (di) dictReleaseIterator(di);
return C_ERR; return C_ERR;
} }
@ -1856,14 +1919,15 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
serverLog(LL_VERBOSE, serverLog(LL_VERBOSE,
"Background AOF rewrite signal handler took %lldus", ustime()-now); "Background AOF rewrite signal handler took %lldus", ustime()-now);
} else if (!bysignal && exitcode != 0) { } else if (!bysignal && exitcode != 0) {
g_pserver->aof_lastbgrewrite_status = C_ERR;
serverLog(LL_WARNING,
"Background AOF rewrite terminated with error");
} else {
/* SIGUSR1 is whitelisted, so we have a way to kill a child without /* SIGUSR1 is whitelisted, so we have a way to kill a child without
* tirggering an error condition. */ * tirggering an error condition. */
if (bysignal != SIGUSR1) if (bysignal != SIGUSR1)
g_pserver->aof_lastbgrewrite_status = C_ERR; g_pserver->aof_lastbgrewrite_status = C_ERR;
serverLog(LL_WARNING,
"Background AOF rewrite terminated with error");
} else {
g_pserver->aof_lastbgrewrite_status = C_ERR;
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Background AOF rewrite terminated by signal %d", bysignal); "Background AOF rewrite terminated by signal %d", bysignal);

View File

@ -976,6 +976,9 @@ void bitposCommand(client *c) {
* OVERFLOW [WRAP|SAT|FAIL] * OVERFLOW [WRAP|SAT|FAIL]
*/ */
#define BITFIELD_FLAG_NONE 0
#define BITFIELD_FLAG_READONLY (1<<0)
struct bitfieldOp { struct bitfieldOp {
uint64_t offset; /* Bitfield offset. */ uint64_t offset; /* Bitfield offset. */
int64_t i64; /* Increment amount (INCRBY) or SET value */ int64_t i64; /* Increment amount (INCRBY) or SET value */
@ -985,7 +988,10 @@ struct bitfieldOp {
int sign; /* True if signed, otherwise unsigned op. */ int sign; /* True if signed, otherwise unsigned op. */
}; };
void bitfieldCommand(client *c) { /* This implements both the BITFIELD command and the BITFIELD_RO command
* when flags is set to BITFIELD_FLAG_READONLY: in this case only the
* GET subcommand is allowed, other subcommands will return an error. */
void bitfieldGeneric(client *c, int flags) {
robj_roptr o; robj_roptr o;
size_t bitoffset; size_t bitoffset;
int j, numops = 0, changes = 0; int j, numops = 0, changes = 0;
@ -1073,6 +1079,12 @@ void bitfieldCommand(client *c) {
return; return;
} }
} else { } else {
if (flags & BITFIELD_FLAG_READONLY) {
zfree(ops);
addReplyError(c, "BITFIELD_RO only supports the GET subcommand");
return;
}
/* Lookup by making room up to the farest bit reached by /* Lookup by making room up to the farest bit reached by
* this operation. */ * this operation. */
if ((o = lookupStringForBitCommand(c, if ((o = lookupStringForBitCommand(c,
@ -1203,3 +1215,11 @@ void bitfieldCommand(client *c) {
} }
zfree(ops); zfree(ops);
} }
void bitfieldCommand(client *c) {
bitfieldGeneric(c, BITFIELD_FLAG_NONE);
}
void bitfieldroCommand(client *c) {
bitfieldGeneric(c, BITFIELD_FLAG_READONLY);
}

View File

@ -31,9 +31,6 @@
* *
* API: * API:
* *
* getTimeoutFromObjectOrReply() is just an utility function to parse a
* timeout argument since blocking operations usually require a timeout.
*
* blockClient() set the CLIENT_BLOCKED flag in the client, and set the * blockClient() set the CLIENT_BLOCKED flag in the client, and set the
* specified block type 'btype' filed to one of BLOCKED_* macros. * specified block type 'btype' filed to one of BLOCKED_* macros.
* *
@ -68,42 +65,6 @@
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where); int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
/* Get a timeout value from an object and store it into 'timeout'.
* The final timeout is always stored as milliseconds as a time where the
* timeout will expire, however the parsing is performed according to
* the 'unit' that can be seconds or milliseconds.
*
* Note that if the timeout is zero (usually from the point of view of
* commands API this means no timeout) the value stored into 'timeout'
* is zero. */
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit) {
long long tval;
long double ftval;
if (unit == UNIT_SECONDS) {
if (getLongDoubleFromObjectOrReply(c,object,&ftval,
"timeout is not an float or out of range") != C_OK)
return C_ERR;
tval = (long long) (ftval * 1000.0);
} else {
if (getLongLongFromObjectOrReply(c,object,&tval,
"timeout is not an integer or out of range") != C_OK)
return C_ERR;
}
if (tval < 0) {
addReplyErrorAsync(c,"timeout is negative");
return C_ERR;
}
if (tval > 0) {
tval += mstime();
}
*timeout = tval;
return C_OK;
}
/* Block a client for the specific operation type. Once the CLIENT_BLOCKED /* Block a client for the specific operation type. Once the CLIENT_BLOCKED
* flag is set client query buffer is not longer processed, but accumulated, * flag is set client query buffer is not longer processed, but accumulated,
* and will be processed when the client is unblocked. */ * and will be processed when the client is unblocked. */
@ -115,6 +76,7 @@ void blockClient(client *c, int btype) {
c->casyncOpsPending++; c->casyncOpsPending++;
g_pserver->blocked_clients++; g_pserver->blocked_clients++;
g_pserver->blocked_clients_by_type[btype]++; g_pserver->blocked_clients_by_type[btype]++;
addClientToTimeoutTable(c);
} }
/* This function is called in the beforeSleep() function of the event loop /* This function is called in the beforeSleep() function of the event loop
@ -205,6 +167,7 @@ void unblockClient(client *c) {
g_pserver->blocked_clients_by_type[c->btype]--; g_pserver->blocked_clients_by_type[c->btype]--;
c->flags &= ~CLIENT_BLOCKED; c->flags &= ~CLIENT_BLOCKED;
c->btype = BLOCKED_NONE; c->btype = BLOCKED_NONE;
removeClientFromTimeoutTable(c);
queueClientForReprocessing(c); queueClientForReprocessing(c);
} }

View File

@ -165,7 +165,10 @@ int clusterLoadConfig(char *filename) {
} }
/* Regular config lines have at least eight fields */ /* Regular config lines have at least eight fields */
if (argc < 8) goto fmterr; if (argc < 8) {
sdsfreesplitres(argv,argc);
goto fmterr;
}
/* Create this node if it does not exist */ /* Create this node if it does not exist */
n = clusterLookupNode(argv[0]); n = clusterLookupNode(argv[0]);
@ -174,7 +177,10 @@ int clusterLoadConfig(char *filename) {
clusterAddNode(n); clusterAddNode(n);
} }
/* Address and port */ /* Address and port */
if ((p = strrchr(argv[1],':')) == NULL) goto fmterr; if ((p = strrchr(argv[1],':')) == NULL) {
sdsfreesplitres(argv,argc);
goto fmterr;
}
*p = '\0'; *p = '\0';
memcpy(n->ip,argv[1],strlen(argv[1])+1); memcpy(n->ip,argv[1],strlen(argv[1])+1);
char *port = p+1; char *port = p+1;
@ -255,7 +261,10 @@ int clusterLoadConfig(char *filename) {
*p = '\0'; *p = '\0';
direction = p[1]; /* Either '>' or '<' */ direction = p[1]; /* Either '>' or '<' */
slot = atoi(argv[j]+1); slot = atoi(argv[j]+1);
if (slot < 0 || slot >= CLUSTER_SLOTS) goto fmterr; if (slot < 0 || slot >= CLUSTER_SLOTS) {
sdsfreesplitres(argv,argc);
goto fmterr;
}
p += 3; p += 3;
cn = clusterLookupNode(p); cn = clusterLookupNode(p);
if (!cn) { if (!cn) {
@ -275,8 +284,12 @@ int clusterLoadConfig(char *filename) {
} else { } else {
start = stop = atoi(argv[j]); start = stop = atoi(argv[j]);
} }
if (start < 0 || start >= CLUSTER_SLOTS) goto fmterr; if (start < 0 || start >= CLUSTER_SLOTS ||
if (stop < 0 || stop >= CLUSTER_SLOTS) goto fmterr; stop < 0 || stop >= CLUSTER_SLOTS)
{
sdsfreesplitres(argv,argc);
goto fmterr;
}
while(start <= stop) clusterAddSlot(n, start++); while(start <= stop) clusterAddSlot(n, start++);
} }
@ -706,9 +719,10 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
* or schedule it for later depending on connection implementation. * or schedule it for later depending on connection implementation.
*/ */
if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) { if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
serverLog(LL_VERBOSE, if (connGetState(conn) == CONN_STATE_ERROR)
"Error accepting cluster node connection: %s", serverLog(LL_VERBOSE,
connGetLastError(conn)); "Error accepting cluster node connection: %s",
connGetLastError(conn));
connClose(conn); connClose(conn);
return; return;
} }
@ -4315,7 +4329,7 @@ void clusterCommand(client *c) {
"FORGET <node-id> -- Remove a node from the cluster.", "FORGET <node-id> -- Remove a node from the cluster.",
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.", "GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
"FLUSHSLOTS -- Delete current node own slots information.", "FLUSHSLOTS -- Delete current node own slots information.",
"INFO - Return onformation about the cluster.", "INFO - Return information about the cluster.",
"KEYSLOT <key> -- Return the hash slot for <key>.", "KEYSLOT <key> -- Return the hash slot for <key>.",
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.", "MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
"MYID -- Return the node id.", "MYID -- Return the node id.",
@ -4326,6 +4340,7 @@ void clusterCommand(client *c) {
"SET-config-epoch <epoch> - Set config epoch of current node.", "SET-config-epoch <epoch> - Set config epoch of current node.",
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.", "SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
"REPLICAS <node-id> -- Return <node-id> replicas.", "REPLICAS <node-id> -- Return <node-id> replicas.",
"SAVECONFIG - Force saving cluster configuration on disk.",
"SLOTS -- Return information about slots range mappings. Each range is made of:", "SLOTS -- Return information about slots range mappings. Each range is made of:",
" start, end, master and replicas IP addresses, ports and ids", " start, end, master and replicas IP addresses, ports and ids",
NULL NULL
@ -5054,6 +5069,7 @@ eoferr:
} }
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000); objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
signalModifiedKey(c->db,c->argv[1]); signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",c->argv[1],c->db->id);
addReply(c,shared.ok); addReply(c,shared.ok);
g_pserver->dirty++; g_pserver->dirty++;
} }
@ -5402,6 +5418,7 @@ try_again:
/* No COPY option: remove the local key, signal the change. */ /* No COPY option: remove the local key, signal the change. */
dbDelete(c->db,kv[j]); dbDelete(c->db,kv[j]);
signalModifiedKey(c->db,kv[j]); signalModifiedKey(c->db,kv[j]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",kv[j],c->db->id);
g_pserver->dirty++; g_pserver->dirty++;
/* Populate the argument vector to replace the old one. */ /* Populate the argument vector to replace the old one. */

View File

@ -115,12 +115,12 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
/* Generic config infrastructure function pointers /* Generic config infrastructure function pointers
* int is_valid_fn(val, err) * int is_valid_fn(val, err)
* Return 1 when val is valid, and 0 when invalid. * Return 1 when val is valid, and 0 when invalid.
* Optionslly set err to a static error string. * Optionally set err to a static error string.
* int update_fn(val, prev, err) * int update_fn(val, prev, err)
* This function is called only for CONFIG SET command (not at config file parsing) * This function is called only for CONFIG SET command (not at config file parsing)
* It is called after the actual config is applied, * It is called after the actual config is applied,
* Return 1 for success, and 0 for failure. * Return 1 for success, and 0 for failure.
* Optionslly set err to a static error string. * Optionally set err to a static error string.
* On failure the config change will be reverted. * On failure the config change will be reverted.
*/ */
@ -197,8 +197,9 @@ typedef struct typeInterface {
void (*init)(typeData data); void (*init)(typeData data);
/* Called on server start, should return 1 on success, 0 on error and should set err */ /* 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); int (*load)(typeData data, sds *argc, int argv, const char **err);
/* Called on CONFIG SET, returns 1 on success, 0 on error */ /* Called on server startup and CONFIG SET, returns 1 on success, 0 on error
int (*set)(typeData data, sds value, const char **err); * 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);
/* Called on CONFIG GET, required to add output to the client */ /* Called on CONFIG GET, required to add output to the client */
void (*get)(client *c, typeData data); void (*get)(client *c, typeData data);
/* Called on CONFIG REWRITE, required to rewrite the config state */ /* Called on CONFIG REWRITE, required to rewrite the config state */
@ -392,7 +393,11 @@ void loadServerConfigFromString(char *config) {
if ((!strcasecmp(argv[0],config->name) || if ((!strcasecmp(argv[0],config->name) ||
(config->alias && !strcasecmp(argv[0],config->alias)))) (config->alias && !strcasecmp(argv[0],config->alias))))
{ {
if (!config->interface.load(config->data, argv, argc, &err)) { if (argc != 2) {
err = "wrong number of arguments";
goto loaderr;
}
if (!config->interface.set(config->data, argv[1], 0, &err)) {
goto loaderr; goto loaderr;
} }
@ -413,6 +418,10 @@ void loadServerConfigFromString(char *config) {
if (addresses > CONFIG_BINDADDR_MAX) { if (addresses > CONFIG_BINDADDR_MAX) {
err = "Too many bind addresses specified"; goto loaderr; err = "Too many bind addresses specified"; goto loaderr;
} }
/* Free old bind addresses */
for (j = 0; j < g_pserver->bindaddr_count; j++) {
zfree(g_pserver->bindaddr[j]);
}
for (j = 0; j < addresses; j++) for (j = 0; j < addresses; j++)
g_pserver->bindaddr[j] = zstrdup(argv[j+1]); g_pserver->bindaddr[j] = zstrdup(argv[j+1]);
g_pserver->bindaddr_count = addresses; g_pserver->bindaddr_count = addresses;
@ -469,11 +478,15 @@ void loadServerConfigFromString(char *config) {
goto loaderr; goto loaderr;
} }
/* The old "requirepass" directive just translates to setting /* The old "requirepass" directive just translates to setting
* a password to the default user. */ * a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this
* case, for backward compatibility with Redis <= 5. */
ACLSetUser(DefaultUser,"resetpass",-1); ACLSetUser(DefaultUser,"resetpass",-1);
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]); sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
ACLSetUser(DefaultUser,aclop,sdslen(aclop)); ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop); sdsfree(aclop);
sdsfree(g_pserver->requirepass);
g_pserver->requirepass = sdsnew(argv[1]);
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){ } else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
/* DEAD OPTION */ /* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) { } else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
@ -577,8 +590,14 @@ void loadServerConfigFromString(char *config) {
} else if (strcasecmp(argv[1], "false") == 0) { } else if (strcasecmp(argv[1], "false") == 0) {
cserver.fThreadAffinity = FALSE; cserver.fThreadAffinity = FALSE;
} else { } else {
err = "Unknown argument: server-thread-affinity expects either true or false"; int offset = atoi(argv[1]);
goto loaderr; if (offset > 0) {
cserver.fThreadAffinity = TRUE;
cserver.threadAffinityOffset = offset-1;
} else {
err = "Unknown argument: server-thread-affinity expects either true or false";
goto loaderr;
}
} }
} else if (!strcasecmp(argv[0], "active-replica") && argc == 2) { } else if (!strcasecmp(argv[0], "active-replica") && argc == 2) {
g_pserver->fActiveReplica = yesnotoi(argv[1]); g_pserver->fActiveReplica = yesnotoi(argv[1]);
@ -628,7 +647,8 @@ void loadServerConfigFromString(char *config) {
return; return;
loaderr: loaderr:
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n"); fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (Redis %s) ***\n",
KEYDB_REAL_VERSION);
fprintf(stderr, "Reading the configuration file, at line %d\n", linenum); fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
fprintf(stderr, ">>> '%s'\n", lines[i]); fprintf(stderr, ">>> '%s'\n", lines[i]);
fprintf(stderr, "%s\n", err); fprintf(stderr, "%s\n", err);
@ -718,7 +738,7 @@ void configSetCommand(client *c) {
if(config->modifiable && (!strcasecmp(szFromObj(c->argv[2]),config->name) || if(config->modifiable && (!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->interface.set(config->data,szFromObj(o), &errstr)) { if (!config->interface.set(config->data,szFromObj(o),1,&errstr)) {
goto badfmt; goto badfmt;
} }
addReply(c,shared.ok); addReply(c,shared.ok);
@ -732,11 +752,15 @@ void configSetCommand(client *c) {
config_set_special_field("requirepass") { config_set_special_field("requirepass") {
if (sdslen(szFromObj(o)) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt; if (sdslen(szFromObj(o)) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
/* The old "requirepass" directive just translates to setting /* The old "requirepass" directive just translates to setting
* a password to the default user. */ * a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this
* case, for backward compatibility with Redis <= 5. */
ACLSetUser(DefaultUser,"resetpass",-1); ACLSetUser(DefaultUser,"resetpass",-1);
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)ptrFromObj(o)); sds aclop = sdscatprintf(sdsempty(),">%s",(char*)ptrFromObj(o));
ACLSetUser(DefaultUser,aclop,sdslen(aclop)); ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop); sdsfree(aclop);
sdsfree(g_pserver->requirepass);
g_pserver->requirepass = sdsnew(szFromObj(o));
} config_set_special_field("save") { } config_set_special_field("save") {
int vlen, j; int vlen, j;
sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen); sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen);
@ -838,7 +862,7 @@ void configSetCommand(client *c) {
* config_set_memory_field(name,var) */ * config_set_memory_field(name,var) */
} config_set_memory_field( } config_set_memory_field(
"client-query-buffer-limit",cserver.client_max_querybuf_len) { "client-query-buffer-limit",cserver.client_max_querybuf_len) {
/* Everyhing else is an error... */ /* Everything else is an error... */
} config_set_else { } config_set_else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
(char*)ptrFromObj(c->argv[2])); (char*)ptrFromObj(c->argv[2]));
@ -1025,7 +1049,7 @@ void configGetCommand(client *c) {
} }
if (stringmatch(pattern,"requirepass",1)) { if (stringmatch(pattern,"requirepass",1)) {
addReplyBulkCString(c,"requirepass"); addReplyBulkCString(c,"requirepass");
sds password = ACLDefaultUserFirstPassword(); sds password = g_pserver->requirepass;
if (password) { if (password) {
addReplyBulkCBuffer(c,password,sdslen(password)); addReplyBulkCBuffer(c,password,sdslen(password));
} else { } else {
@ -1476,7 +1500,7 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, const char *option) { void rewriteConfigRequirepassOption(struct rewriteConfigState *state, const char *option) {
int force = 1; int force = 1;
sds line; sds line;
sds password = ACLDefaultUserFirstPassword(); sds password = g_pserver->requirepass;
/* If there is no password set, we don't want the requirepass option /* If there is no password set, we don't want the requirepass option
* to be present in the configuration at all. */ * to be present in the configuration at all. */
@ -1682,8 +1706,8 @@ static char loadbuf[LOADBUF_SIZE];
#define embedCommonConfig(config_name, config_alias, is_modifiable) \ #define embedCommonConfig(config_name, config_alias, is_modifiable) \
config_name, config_alias, is_modifiable, config_name, config_alias, is_modifiable,
#define embedConfigInterface(initfn, loadfn, setfn, getfn, rewritefn) { \ #define embedConfigInterface(initfn, setfn, getfn, rewritefn) { \
initfn, loadfn, setfn, getfn, rewritefn, \ initfn, nullptr, 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
@ -1703,31 +1727,19 @@ static void boolConfigInit(typeData data) {
*data.yesno.config = data.yesno.default_value; *data.yesno.config = data.yesno.default_value;
} }
static int boolConfigLoad(typeData data, sds *argv, int argc, const char **err) { static int boolConfigSet(typeData data, sds value, int update, const char **err) {
int yn;
if (argc != 2) {
*err = "wrong number of arguments";
return 0;
}
if ((yn = yesnotoi(argv[1])) == -1) {
if ((yn = truefalsetoi(argv[1])) == -1)
*err = "argument must be 'yes' or 'no'";
return 0;
}
if (data.yesno.is_valid_fn && !data.yesno.is_valid_fn(yn, err))
return 0;
*data.yesno.config = yn;
return 1;
}
static int boolConfigSet(typeData data, sds value, const char **err) {
int yn = yesnotoi(value); int yn = yesnotoi(value);
if (yn == -1) return 0; if (yn == -1) {
if ((yn = truefalsetoi(value)) == -1) {
*err = "argument must be 'yes' or 'no'";
return 0;
}
}
if (data.yesno.is_valid_fn && !data.yesno.is_valid_fn(yn, err)) if (data.yesno.is_valid_fn && !data.yesno.is_valid_fn(yn, err))
return 0; return 0;
int prev = *(data.yesno.config); int prev = *(data.yesno.config);
*(data.yesno.config) = yn; *(data.yesno.config) = yn;
if (data.yesno.update_fn && !data.yesno.update_fn(yn, prev, err)) { if (update && data.yesno.update_fn && !data.yesno.update_fn(yn, prev, err)) {
*(data.yesno.config) = prev; *(data.yesno.config) = prev;
return 0; return 0;
} }
@ -1746,7 +1758,7 @@ constexpr standardConfig createBoolConfig(const char *name, const char *alias, i
{ {
standardConfig conf = { standardConfig conf = {
embedCommonConfig(name, alias, modifiable) embedCommonConfig(name, alias, modifiable)
{ boolConfigInit, boolConfigLoad, boolConfigSet, boolConfigGet, boolConfigRewrite } { boolConfigInit, nullptr, 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;
@ -1764,23 +1776,7 @@ static void stringConfigInit(typeData data) {
} }
} }
static int stringConfigLoad(typeData data, sds *argv, int argc, const char **err) { static int stringConfigSet(typeData data, sds value, int update, const char **err) {
if (argc != 2) {
*err = "wrong number of arguments";
return 0;
}
if (data.string.is_valid_fn && !data.string.is_valid_fn(argv[1], err))
return 0;
zfree(*data.string.config);
if (data.string.convert_empty_to_null) {
*data.string.config = argv[1][0] ? zstrdup(argv[1]) : NULL;
} else {
*data.string.config = zstrdup(argv[1]);
}
return 1;
}
static int stringConfigSet(typeData data, sds value, const char **err) {
if (data.string.is_valid_fn && !data.string.is_valid_fn(value, err)) if (data.string.is_valid_fn && !data.string.is_valid_fn(value, err))
return 0; return 0;
char *prev = *data.string.config; char *prev = *data.string.config;
@ -1789,7 +1785,7 @@ static int stringConfigSet(typeData data, sds value, const char **err) {
} else { } else {
*data.string.config = zstrdup(value); *data.string.config = zstrdup(value);
} }
if (data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) { if (update && data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) {
zfree(*data.string.config); zfree(*data.string.config);
*data.string.config = prev; *data.string.config = prev;
return 0; return 0;
@ -1812,7 +1808,7 @@ static void stringConfigRewrite(typeData data, const char *name, struct rewriteC
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, int modifiable, 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, modifiable)
embedConfigInterface(stringConfigInit, stringConfigLoad, stringConfigSet, stringConfigGet, stringConfigRewrite) embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite)
}; };
conf.data.string = { conf.data.string = {
&(config_addr), &(config_addr),
@ -1825,68 +1821,53 @@ constexpr standardConfig createStringConfig(const char *name, const char *alias,
} }
/* Enum configs */ /* Enum configs */
static void configEnumInit(typeData data) { static void enumConfigInit(typeData data) {
*data.enumd.config = data.enumd.default_value; *data.enumd.config = data.enumd.default_value;
} }
static int configEnumLoad(typeData data, sds *argv, int argc, const char **err) { static int enumConfigSet(typeData data, sds value, int update, const char **err) {
if (argc != 2) { int enumval = configEnumGetValue(data.enumd.enum_value, value);
*err = "wrong number of arguments";
return 0;
}
int enumval = configEnumGetValue(data.enumd.enum_value, argv[1]);
if (enumval == INT_MIN) { if (enumval == INT_MIN) {
sds enumerr = sdsnew("argument must be one of the following: "); sds enumerr = sdsnew("argument must be one of the following: ");
configEnum *enumNode = data.enumd.enum_value; configEnum *enumNode = data.enumd.enum_value;
while(enumNode->name != NULL) { while(enumNode->name != NULL) {
enumerr = sdscatlen(enumerr, enumNode->name, strlen(enumNode->name)); enumerr = sdscatlen(enumerr, enumNode->name,
strlen(enumNode->name));
enumerr = sdscatlen(enumerr, ", ", 2); enumerr = sdscatlen(enumerr, ", ", 2);
enumNode++; enumNode++;
} }
sdsrange(enumerr,0,-3); /* Remove final ", ". */
enumerr[sdslen(enumerr) - 2] = '\0';
/* Make sure we don't overrun the fixed buffer */
enumerr[LOADBUF_SIZE - 1] = '\0';
strncpy(loadbuf, enumerr, LOADBUF_SIZE); strncpy(loadbuf, enumerr, LOADBUF_SIZE);
loadbuf[LOADBUF_SIZE - 1] = '\0';
sdsfree(enumerr); sdsfree(enumerr);
*err = loadbuf; *err = loadbuf;
return 0; return 0;
} }
if (data.enumd.is_valid_fn && !data.enumd.is_valid_fn(enumval, err))
return 0;
*(data.enumd.config) = enumval;
return 1;
}
static int configEnumSet(typeData data, sds value, const char **err) {
int enumval = configEnumGetValue(data.enumd.enum_value, value);
if (enumval == INT_MIN) return 0;
if (data.enumd.is_valid_fn && !data.enumd.is_valid_fn(enumval, err)) if (data.enumd.is_valid_fn && !data.enumd.is_valid_fn(enumval, err))
return 0; return 0;
int prev = *(data.enumd.config); int prev = *(data.enumd.config);
*(data.enumd.config) = enumval; *(data.enumd.config) = enumval;
if (data.enumd.update_fn && !data.enumd.update_fn(enumval, prev, err)) { if (update && data.enumd.update_fn && !data.enumd.update_fn(enumval, prev, err)) {
*(data.enumd.config) = prev; *(data.enumd.config) = prev;
return 0; return 0;
} }
return 1; return 1;
} }
static void configEnumGet(client *c, typeData data) { static void enumConfigGet(client *c, typeData data) {
addReplyBulkCString(c, configEnumGetNameOrUnknown(data.enumd.enum_value,*data.enumd.config)); addReplyBulkCString(c, configEnumGetNameOrUnknown(data.enumd.enum_value,*data.enumd.config));
} }
static void configEnumRewrite(typeData data, const char *name, struct rewriteConfigState *state) { static void enumConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
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, int modifiable, 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, modifiable)
embedConfigInterface(configEnumInit, configEnumLoad, configEnumSet, configEnumGet, configEnumRewrite) embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite)
}; };
c.data.enumd = { c.data.enumd = {
&(config_addr), &(config_addr),
@ -1984,49 +1965,22 @@ static int numericBoundaryCheck(typeData data, long long ll, const char **err) {
return 1; return 1;
} }
static int numericConfigLoad(typeData data, sds *argv, int argc, const char **err) { static int numericConfigSet(typeData data, sds value, int update, const char **err) {
long long ll; long long ll, prev = 0;
if (argc != 2) {
*err = "wrong number of arguments";
return 0;
}
if (data.numeric.is_memory) { if (data.numeric.is_memory) {
int memerr; int memerr;
ll = memtoll(argv[1], &memerr); ll = memtoll(value, &memerr);
if (memerr || ll < 0) { if (memerr || ll < 0) {
*err = "argument must be a memory value"; *err = "argument must be a memory value";
return 0; return 0;
} }
} else { } else {
if (!string2ll(argv[1], sdslen(argv[1]),&ll)) { if (!string2ll(value, sdslen(value),&ll)) {
*err = "argument couldn't be parsed into an integer" ; *err = "argument couldn't be parsed into an integer" ;
return 0; return 0;
} }
} }
if (!numericBoundaryCheck(data, ll, err))
return 0;
if (data.numeric.is_valid_fn && !data.numeric.is_valid_fn(ll, err))
return 0;
SET_NUMERIC_TYPE(ll)
return 1;
}
static int numericConfigSet(typeData data, sds value, const char **err) {
long long ll, prev = 0;
if (data.numeric.is_memory) {
int memerr;
ll = memtoll(value, &memerr);
if (memerr || ll < 0) return 0;
} else {
if (!string2ll(value, sdslen(value),&ll)) return 0;
}
if (!numericBoundaryCheck(data, ll, err)) if (!numericBoundaryCheck(data, ll, err))
return 0; return 0;
@ -2036,7 +1990,7 @@ static int numericConfigSet(typeData data, sds value, const char **err) {
GET_NUMERIC_TYPE(prev) GET_NUMERIC_TYPE(prev)
SET_NUMERIC_TYPE(ll) SET_NUMERIC_TYPE(ll)
if (data.numeric.update_fn && !data.numeric.update_fn(ll, prev, err)) { if (update && data.numeric.update_fn && !data.numeric.update_fn(ll, prev, err)) {
SET_NUMERIC_TYPE(prev) SET_NUMERIC_TYPE(prev)
return 0; return 0;
} }
@ -2071,7 +2025,7 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite
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, 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**)) {
standardConfig conf = { standardConfig conf = {
embedCommonConfig(name, alias, modifiable) embedCommonConfig(name, alias, modifiable)
embedConfigInterface(numericConfigInit, numericConfigLoad, numericConfigSet, numericConfigGet, numericConfigRewrite) embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite)
}; };
conf.data.numeric.is_memory = (memory); conf.data.numeric.is_memory = (memory);
conf.data.numeric.lower_bound = (lower); conf.data.numeric.lower_bound = (lower);
@ -2218,8 +2172,9 @@ static int updateMaxmemory(long long val, long long prev, const char **err) {
UNUSED(prev); UNUSED(prev);
UNUSED(err); UNUSED(err);
if (val) { if (val) {
if ((unsigned long long)val < zmalloc_used_memory()) { size_t used = zmalloc_used_memory()-freeMemoryGetNotCountedMemory();
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy."); if ((unsigned long long)val < used) {
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET (%llu) is smaller than the current memory usage (%zu). This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.", g_pserver->maxmemory, used);
} }
freeMemoryIfNeededAndSafe(); freeMemoryIfNeededAndSafe();
} }
@ -2309,6 +2264,7 @@ standardConfig configs[] = {
createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, g_pserver->always_show_logo, 0, NULL, NULL), createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, g_pserver->always_show_logo, 0, NULL, NULL),
createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, g_pserver->protected_mode, 1, NULL, NULL), createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, g_pserver->protected_mode, 1, NULL, NULL),
createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, g_pserver->rdb_compression, 1, NULL, NULL), createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, g_pserver->rdb_compression, 1, NULL, NULL),
createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, g_pserver->rdb_del_sync_files, 0, NULL, NULL),
createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, g_pserver->activerehashing, 1, NULL, NULL), createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, g_pserver->activerehashing, 1, NULL, NULL),
createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, g_pserver->stop_writes_on_bgsave_err, 1, NULL, NULL), createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, g_pserver->stop_writes_on_bgsave_err, 1, NULL, NULL),
createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, g_pserver->dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/ createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, g_pserver->dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/
@ -2384,7 +2340,6 @@ standardConfig configs[] = {
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),
createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->key_load_delay, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->key_load_delay, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("tracking-table-max-fill", NULL, MODIFIABLE_CONFIG, 0, 100, g_pserver->tracking_table_max_fill, 10, INTEGER_CONFIG, NULL, NULL), /* Default: 10% tracking table max fill. */
createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, cserver.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */ createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, cserver.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */
createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ), createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ),
createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves), createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves),
@ -2396,6 +2351,7 @@ standardConfig configs[] = {
/* Unsigned Long configs */ /* Unsigned Long configs */
createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, cserver.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */ createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, cserver.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */
createULongConfig("slowlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL), createULongConfig("slowlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
/* Long Long configs */ /* Long Long configs */
createLongLongConfig("lua-time-limit", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->lua_time_limit, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */ createLongLongConfig("lua-time-limit", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->lua_time_limit, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */
@ -2418,6 +2374,7 @@ standardConfig configs[] = {
createSizeTConfig("stream-node-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->stream_node_max_bytes, 4096, MEMORY_CONFIG, NULL, NULL), createSizeTConfig("stream-node-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->stream_node_max_bytes, 4096, MEMORY_CONFIG, NULL, NULL),
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL), createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL), createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */
/* Other configs */ /* Other configs */
createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */ createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */

View File

@ -153,7 +153,7 @@ static void connSocketClose(connection *conn) {
/* If called from within a handler, schedule the close but /* If called from within a handler, schedule the close but
* keep the connection until the handler returns. * keep the connection until the handler returns.
*/ */
if (conn->flags & CONN_FLAG_IN_HANDLER) { if (connHasRefs(conn)) {
conn->flags |= CONN_FLAG_CLOSE_SCHEDULED; conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
return; return;
} }
@ -184,10 +184,16 @@ static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
} }
static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) { static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
int ret = C_OK;
if (conn->state != CONN_STATE_ACCEPTING) return C_ERR; if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
conn->state = CONN_STATE_CONNECTED; conn->state = CONN_STATE_CONNECTED;
if (!callHandler(conn, accept_handler)) return C_ERR;
return C_OK; connIncrRefs(conn);
if (!callHandler(conn, accept_handler)) ret = C_ERR;
connDecrRefs(conn);
return ret;
} }
/* Register a write handler, to be called when the connection is writable. /* Register a write handler, to be called when the connection is writable.
@ -441,15 +447,3 @@ const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
snprintf(buf, buf_len-1, "fd=%i", conn->fd); snprintf(buf, buf_len-1, "fd=%i", conn->fd);
return buf; return buf;
} }
int callHandler(connection *conn, ConnectionCallbackFunc handler) {
conn->flags |= CONN_FLAG_IN_HANDLER;
if (handler) handler(conn);
conn->flags &= ~CONN_FLAG_IN_HANDLER;
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
connClose(conn);
return 0;
}
return 1;
}

View File

@ -45,11 +45,10 @@ typedef enum {
CONN_STATE_ERROR CONN_STATE_ERROR
} ConnectionState; } ConnectionState;
#define CONN_FLAG_IN_HANDLER (1<<0) /* A handler execution is in progress */ #define CONN_FLAG_CLOSE_SCHEDULED (1<<0) /* Closed scheduled by a handler */
#define CONN_FLAG_CLOSE_SCHEDULED (1<<1) /* Closed scheduled by a handler */ #define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */
#define CONN_FLAG_WRITE_BARRIER (1<<2) /* Write barrier requested */ #define CONN_FLAG_READ_THREADSAFE (1<<2)
#define CONN_FLAG_READ_THREADSAFE (1<<3) #define CONN_FLAG_WRITE_THREADSAFE (1<<3)
#define CONN_FLAG_WRITE_THREADSAFE (1<<4)
typedef void (*ConnectionCallbackFunc)(struct connection *conn); typedef void (*ConnectionCallbackFunc)(struct connection *conn);
@ -72,7 +71,8 @@ typedef struct ConnectionType {
struct connection { struct connection {
ConnectionType *type; ConnectionType *type;
ConnectionState state; ConnectionState state;
int flags; short int flags;
short int refs;
int last_errno; int last_errno;
void *private_data; void *private_data;
ConnectionCallbackFunc conn_handler; ConnectionCallbackFunc conn_handler;
@ -90,6 +90,13 @@ struct connection {
* connAccept() may directly call accept_handler(), or return and call it * connAccept() may directly call accept_handler(), or return and call it
* at a later time. This behavior is a bit awkward but aims to reduce the need * at a later time. This behavior is a bit awkward but aims to reduce the need
* to wait for the next event loop, if no additional handshake is required. * to wait for the next event loop, if no additional handshake is required.
*
* IMPORTANT: accept_handler may decide to close the connection, calling connClose().
* To make this safe, the connection is only marked with CONN_FLAG_CLOSE_SCHEDULED
* in this case, and connAccept() returns with an error.
*
* connAccept() callers must always check the return value and on error (C_ERR)
* a connClose() must be called.
*/ */
static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) { static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {

View File

@ -37,40 +37,52 @@
* implementations (currently sockets in connection.c and TLS in tls.c). * implementations (currently sockets in connection.c and TLS in tls.c).
* *
* Currently helpers implement the mechanisms for invoking connection * Currently helpers implement the mechanisms for invoking connection
* handlers, tracking in-handler states and dealing with deferred * handlers and tracking connection references, to allow safe destruction
* destruction (if invoked by a handler). * of connections from within a handler.
*/ */
/* Called whenever a handler is invoked on a connection and sets the /* Incremenet connection references.
* CONN_FLAG_IN_HANDLER flag to indicate we're in a handler context.
* *
* An attempt to close a connection while CONN_FLAG_IN_HANDLER is * Inside a connection handler, we guarantee refs >= 1 so it is always
* set will result with deferred close, i.e. setting the CONN_FLAG_CLOSE_SCHEDULED * safe to connClose().
* instead of destructing it. *
* In other cases where we don't want to prematurely lose the connection,
* it can go beyond 1 as well; currently it is only done by connAccept().
*/ */
static inline void enterHandler(connection *conn) { static inline void connIncrRefs(connection *conn) {
conn->flags |= CONN_FLAG_IN_HANDLER; conn->refs++;
} }
/* Called whenever a handler returns. This unsets the CONN_FLAG_IN_HANDLER /* Decrement connection references.
* flag and performs actual close/destruction if a deferred close was *
* scheduled by the handler. * Note that this is not intended to provide any automatic free logic!
* callHandler() takes care of that for the common flows, and anywhere an
* explicit connIncrRefs() is used, the caller is expected to take care of
* that.
*/ */
static inline int exitHandler(connection *conn) {
conn->flags &= ~CONN_FLAG_IN_HANDLER; static inline void connDecrRefs(connection *conn) {
conn->refs--;
}
static inline int connHasRefs(connection *conn) {
return conn->refs;
}
/* Helper for connection implementations to call handlers:
* 1. Increment refs to protect the connection.
* 2. Execute the handler (if set).
* 3. Decrement refs and perform deferred close, if refs==0.
*/
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
connIncrRefs(conn);
if (handler) handler(conn);
connDecrRefs(conn);
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) { if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
connClose(conn); if (!connHasRefs(conn)) connClose(conn);
return 0; return 0;
} }
return 1; return 1;
} }
/* Helper for connection implementations to call handlers:
* 1. Mark the handler in use.
* 2. Execute the handler (if set).
* 3. Mark the handler as NOT in use and perform deferred close if was
* requested by the handler at any time.
*/
int callHandler(connection *conn, ConnectionCallbackFunc handler);
#endif /* __REDIS_CONNHELPERS_H */ #endif /* __REDIS_CONNHELPERS_H */

View File

@ -70,6 +70,7 @@ void cronCommand(client *c)
decrRefCount(o); decrRefCount(o);
// use an expire to trigger execution. Note: We use a subkey expire here so legacy clients don't delete it. // use an expire to trigger execution. Note: We use a subkey expire here so legacy clients don't delete it.
setExpire(c, c->db, c->argv[ARG_NAME], c->argv[ARG_NAME], base + interval); setExpire(c, c->db, c->argv[ARG_NAME], c->argv[ARG_NAME], base + interval);
++g_pserver->dirty;
addReply(c, shared.ok); addReply(c, shared.ok);
} }
@ -86,6 +87,7 @@ void executeCronJobExpireHook(const char *key, robj *o)
serverAssert(cFake->argc == 0); serverAssert(cFake->argc == 0);
// Setup the args for the EVAL command // Setup the args for the EVAL command
cFake->cmd = lookupCommandByCString("EVAL");
cFake->argc = 3 + job->veckeys.size() + job->vecargs.size(); cFake->argc = 3 + job->veckeys.size() + job->vecargs.size();
cFake->argv = (robj**)zmalloc(sizeof(robj*) * cFake->argc, MALLOC_LOCAL); cFake->argv = (robj**)zmalloc(sizeof(robj*) * cFake->argc, MALLOC_LOCAL);
cFake->argv[0] = createStringObject("EVAL", 4); cFake->argv[0] = createStringObject("EVAL", 4);
@ -96,7 +98,17 @@ void executeCronJobExpireHook(const char *key, robj *o)
for (size_t i = 0; i < job->vecargs.size(); ++i) for (size_t i = 0; i < job->vecargs.size(); ++i)
cFake->argv[3+job->veckeys.size()+i] = createStringObject(job->vecargs[i].get(), job->vecargs[i].size()); cFake->argv[3+job->veckeys.size()+i] = createStringObject(job->vecargs[i].get(), job->vecargs[i].size());
int lua_replicate_backup = g_pserver->lua_always_replicate_commands;
g_pserver->lua_always_replicate_commands = 0;
evalCommand(cFake); evalCommand(cFake);
g_pserver->lua_always_replicate_commands = lua_replicate_backup;
if (g_pserver->aof_state != AOF_OFF)
feedAppendOnlyFile(cFake->cmd,cFake->db->id,cFake->argv,cFake->argc);
// Active replicas do their own expiries, do not propogate
if (!g_pserver->fActiveReplica)
replicationFeedSlaves(g_pserver->slaves,cFake->db->id,cFake->argv,cFake->argc);
resetClient(cFake); resetClient(cFake);
robj *keyobj = createStringObject(key,sdslen(key)); robj *keyobj = createStringObject(key,sdslen(key));

View File

@ -220,8 +220,9 @@ bool dbAddCore(redisDb *db, robj *key, robj *val) {
if (fInserted) if (fInserted)
{ {
if (val->type == OBJ_LIST || if (val->type == OBJ_LIST ||
val->type == OBJ_ZSET) val->type == OBJ_ZSET ||
signalKeyAsReady(db, key); val->type == OBJ_STREAM)
signalKeyAsReady(db, key);
if (g_pserver->cluster_enabled) slotToKeyAdd(key); if (g_pserver->cluster_enabled) slotToKeyAdd(key);
} }
else else
@ -481,7 +482,10 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
* DB number if we want to flush only a single Redis database number. * DB number if we want to flush only a single Redis database number.
* *
* Flags are be EMPTYDB_NO_FLAGS if no special flags are specified or * Flags are be EMPTYDB_NO_FLAGS if no special flags are specified or
* EMPTYDB_ASYNC if we want the memory to be freed in a different thread * 1. EMPTYDB_ASYNC if we want the memory to be freed in a different thread.
* 2. EMPTYDB_BACKUP if we want to empty the backup dictionaries created by
* disklessLoadMakeBackups. In that case we only free memory and avoid
* firing module events.
* and the function to return ASAP. * and the function to return ASAP.
* *
* On success the fuction returns the number of keys removed from the * On success the fuction returns the number of keys removed from the
@ -489,6 +493,8 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
* DB number is out of range, and errno is set to EINVAL. */ * DB number is out of range, and errno is set to EINVAL. */
long long emptyDbGeneric(redisDb **dbarray, int dbnum, int flags, void(callback)(void*)) { long long emptyDbGeneric(redisDb **dbarray, int dbnum, int flags, void(callback)(void*)) {
int async = (flags & EMPTYDB_ASYNC); int async = (flags & EMPTYDB_ASYNC);
int backup = (flags & EMPTYDB_BACKUP); /* Just free the memory, nothing else */
RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum};
long long removed = 0; long long removed = 0;
if (dbnum < -1 || dbnum >= cserver.dbnum) { if (dbnum < -1 || dbnum >= cserver.dbnum) {
@ -496,16 +502,18 @@ long long emptyDbGeneric(redisDb **dbarray, int dbnum, int flags, void(callback)
return -1; return -1;
} }
/* Fire the flushdb modules event. */ /* Pre-flush actions */
RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum}; if (!backup) {
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB, /* Fire the flushdb modules event. */
REDISMODULE_SUBEVENT_FLUSHDB_START, moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
&fi); REDISMODULE_SUBEVENT_FLUSHDB_START,
&fi);
/* Make sure the WATCHed keys are affected by the FLUSH* commands. /* Make sure the WATCHed keys are affected by the FLUSH* commands.
* Note that we need to call the function while the keys are still * Note that we need to call the function while the keys are still
* there. */ * there. */
signalFlushedDb(dbnum); signalFlushedDb(dbnum);
}
int startdb, enddb; int startdb, enddb;
if (dbnum == -1) { if (dbnum == -1) {
@ -518,20 +526,24 @@ long long emptyDbGeneric(redisDb **dbarray, int dbnum, int flags, void(callback)
for (int j = startdb; j <= enddb; j++) { for (int j = startdb; j <= enddb; j++) {
removed += dbarray[j]->clear(!!async, callback); removed += dbarray[j]->clear(!!async, callback);
} }
if (g_pserver->cluster_enabled) {
if (async) {
slotToKeyFlushAsync();
} else {
slotToKeyFlush();
}
}
if (dbnum == -1) flushSlaveKeysWithExpireList();
/* Also fire the end event. Note that this event will fire almost /* Post-flush actions */
* immediately after the start event if the flush is asynchronous. */ if (!backup) {
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB, if (g_pserver->cluster_enabled) {
REDISMODULE_SUBEVENT_FLUSHDB_END, if (async) {
&fi); slotToKeyFlushAsync();
} else {
slotToKeyFlush();
}
}
if (dbnum == -1) flushSlaveKeysWithExpireList();
/* Also fire the end event. Note that this event will fire almost
* immediately after the start event if the flush is asynchronous. */
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
REDISMODULE_SUBEVENT_FLUSHDB_END,
&fi);
}
return removed; return removed;
} }
@ -1568,6 +1580,7 @@ const expireEntry *redisDbPersistentDataSnapshot::getExpire(const char *key) con
* keys. */ * keys. */
void propagateExpire(redisDb *db, robj *key, int lazy) { void propagateExpire(redisDb *db, robj *key, int lazy) {
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
robj *argv[2]; robj *argv[2];
argv[0] = lazy ? shared.unlink : shared.del; argv[0] = lazy ? shared.unlink : shared.del;
@ -1585,6 +1598,48 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
decrRefCount(argv[1]); decrRefCount(argv[1]);
} }
void propagateSubkeyExpire(redisDb *db, int type, robj *key, robj *subkey)
{
robj *argv[3];
robj objT;
redisCommand *cmd = nullptr;
switch (type)
{
case OBJ_SET:
argv[0] = shared.srem;
argv[1] = key;
argv[2] = subkey;
cmd = cserver.sremCommand;
break;
case OBJ_HASH:
argv[0] = shared.hdel;
argv[1] = key;
argv[2] = subkey;
cmd = cserver.hdelCommand;
break;
case OBJ_ZSET:
argv[0] = shared.zrem;
argv[1] = key;
argv[2] = subkey;
cmd = cserver.zremCommand;
break;
case OBJ_CRON:
return; // CRON jobs replicate in their own handler
default:
serverPanic("Unknown subkey type");
}
if (g_pserver->aof_state != AOF_OFF)
feedAppendOnlyFile(cmd,db->id,argv,3);
// Active replicas do their own expiries, do not propogate
if (!g_pserver->fActiveReplica)
replicationFeedSlaves(g_pserver->slaves,db->id,argv,3);
}
/* Check if the key is expired. Note, this does not check subexpires */ /* Check if the key is expired. Note, this does not check subexpires */
int keyIsExpired(redisDb *db, robj *key) { int keyIsExpired(redisDb *db, robj *key) {
expireEntry *pexpire = db->getExpire(key); expireEntry *pexpire = db->getExpire(key);
@ -1673,13 +1728,17 @@ int expireIfNeeded(redisDb *db, robj *key) {
propagateExpire(db,key,g_pserver->lazyfree_lazy_expire); propagateExpire(db,key,g_pserver->lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED, notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",key,db->id); "expired",key,db->id);
return g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(db,key) : int retval = g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key); dbSyncDelete(db,key);
if (retval) signalModifiedKey(db,key);
return retval;
} }
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* API to get key arguments from commands * API to get key arguments from commands
* ---------------------------------------------------------------------------*/ * ---------------------------------------------------------------------------*/
#define MAX_KEYS_BUFFER 256
thread_local static int getKeysTempBuffer[MAX_KEYS_BUFFER];
/* The base case is to use the keys position as given in the command table /* The base case is to use the keys position as given in the command table
* (firstkey, lastkey, step). */ * (firstkey, lastkey, step). */
@ -1694,7 +1753,12 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
last = cmd->lastkey; last = cmd->lastkey;
if (last < 0) last = argc+last; if (last < 0) last = argc+last;
keys = (int*)zmalloc(sizeof(int)*((last - cmd->firstkey)+1), MALLOC_SHARED);
int count = ((last - cmd->firstkey)+1);
keys = getKeysTempBuffer;
if (count > MAX_KEYS_BUFFER)
keys = (int*)zmalloc(sizeof(int)*count);
for (j = cmd->firstkey; j <= last; j += cmd->keystep) { for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
if (j >= argc) { if (j >= argc) {
/* Modules commands, and standard commands with a not fixed number /* Modules commands, and standard commands with a not fixed number
@ -1704,7 +1768,7 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
* return no keys and expect the command implementation to report * return no keys and expect the command implementation to report
* an arity or syntax error. */ * an arity or syntax error. */
if (cmd->flags & CMD_MODULE || cmd->arity < 0) { if (cmd->flags & CMD_MODULE || cmd->arity < 0) {
zfree(keys); getKeysFreeResult(keys);
*numkeys = 0; *numkeys = 0;
return NULL; return NULL;
} else { } else {
@ -1740,7 +1804,8 @@ int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *nu
/* Free the result of getKeysFromCommand. */ /* Free the result of getKeysFromCommand. */
void getKeysFreeResult(int *result) { void getKeysFreeResult(int *result) {
zfree(result); if (result != getKeysTempBuffer)
zfree(result);
} }
/* Helper function to extract keys from following commands: /* Helper function to extract keys from following commands:
@ -1761,7 +1826,9 @@ int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *nu
/* Keys in z{union,inter}store come from two places: /* Keys in z{union,inter}store come from two places:
* argv[1] = storage key, * argv[1] = storage key,
* argv[3...n] = keys to intersect */ * argv[3...n] = keys to intersect */
keys = (int*)zmalloc(sizeof(int)*(num+1), MALLOC_SHARED); keys = getKeysTempBuffer;
if (num+1>MAX_KEYS_BUFFER)
keys = (int*)zmalloc(sizeof(int)*(num+1));
/* Add all key positions for argv[3...n] to keys[] */ /* Add all key positions for argv[3...n] to keys[] */
for (i = 0; i < num; i++) keys[i] = 3+i; for (i = 0; i < num; i++) keys[i] = 3+i;
@ -1787,7 +1854,10 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
return NULL; return NULL;
} }
keys = (int*)zmalloc(sizeof(int)*num, MALLOC_SHARED); keys = getKeysTempBuffer;
if (num>MAX_KEYS_BUFFER)
keys = (int*)zmalloc(sizeof(int)*num);
*numkeys = num; *numkeys = num;
/* Add all key positions for argv[3...n] to keys[] */ /* Add all key positions for argv[3...n] to keys[] */
@ -1808,7 +1878,7 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
UNUSED(cmd); UNUSED(cmd);
num = 0; num = 0;
keys = (int*)zmalloc(sizeof(int)*2, MALLOC_SHARED); /* Alloc 2 places for the worst case. */ keys = getKeysTempBuffer; /* Alloc 2 places for the worst case. */
keys[num++] = 1; /* <sort-key> is always present. */ keys[num++] = 1; /* <sort-key> is always present. */
@ -1866,7 +1936,10 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
} }
} }
keys = (int*)zmalloc(sizeof(int)*num, MALLOC_SHARED); keys = getKeysTempBuffer;
if (num>MAX_KEYS_BUFFER)
keys = (int*)zmalloc(sizeof(int)*num);
for (i = 0; i < num; i++) keys[i] = first+i; for (i = 0; i < num; i++) keys[i] = first+i;
*numkeys = num; *numkeys = num;
return keys; return keys;
@ -1899,7 +1972,9 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
* argv[1] = key, * argv[1] = key,
* argv[5...n] = stored key if present * argv[5...n] = stored key if present
*/ */
keys = (int*)zmalloc(sizeof(int) * num, MALLOC_SHARED); keys = getKeysTempBuffer;
if (num>MAX_KEYS_BUFFER)
keys = (int*)zmalloc(sizeof(int) * num);
/* Add all key positions to keys[] */ /* Add all key positions to keys[] */
keys[0] = 1; keys[0] = 1;
@ -1910,6 +1985,22 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
return keys; return keys;
} }
/* Helper function to extract keys from memory command.
* MEMORY USAGE <key> */
int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
int *keys;
UNUSED(cmd);
if (argc >= 3 && !strcasecmp(szFromObj(argv[1]),"usage")) {
keys = getKeysTempBuffer;
keys[0] = 2;
*numkeys = 1;
return keys;
}
*numkeys = 0;
return NULL;
}
/* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>] /* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>]
* STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N */ * STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N */
int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) { int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
@ -1948,7 +2039,10 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
num /= 2; /* We have half the keys as there are arguments because num /= 2; /* We have half the keys as there are arguments because
there are also the IDs, one per key. */ there are also the IDs, one per key. */
keys = (int*)zmalloc(sizeof(int) * num, MALLOC_SHARED); keys = getKeysTempBuffer;
if (num>MAX_KEYS_BUFFER)
keys = (int*)zmalloc(sizeof(int) * num);
for (i = streams_pos+1; i < argc-num; i++) keys[i-streams_pos-1] = i; for (i = streams_pos+1; i < argc-num; i++) keys[i-streams_pos-1] = i;
*numkeys = num; *numkeys = num;
return keys; return keys;
@ -1959,6 +2053,8 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
* while rehashing the cluster and in other conditions when we need to * while rehashing the cluster and in other conditions when we need to
* understand if we have keys for a given hash slot. */ * understand if we have keys for a given hash slot. */
void slotToKeyUpdateKey(robj *key, int add) { void slotToKeyUpdateKey(robj *key, int add) {
serverAssert(GlobalLocksAcquired());
size_t keylen = sdslen(szFromObj(key)); size_t keylen = sdslen(szFromObj(key));
unsigned int hashslot = keyHashSlot(szFromObj(key),keylen); unsigned int hashslot = keyHashSlot(szFromObj(key),keylen);
unsigned char buf[64]; unsigned char buf[64];
@ -1969,11 +2065,13 @@ void slotToKeyUpdateKey(robj *key, int add) {
indexed[0] = (hashslot >> 8) & 0xff; indexed[0] = (hashslot >> 8) & 0xff;
indexed[1] = hashslot & 0xff; indexed[1] = hashslot & 0xff;
memcpy(indexed+2,ptrFromObj(key),keylen); memcpy(indexed+2,ptrFromObj(key),keylen);
int fModified = false;
if (add) { if (add) {
raxInsert(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL,NULL); fModified = raxInsert(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
} else { } else {
raxRemove(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL); fModified = raxRemove(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL);
} }
serverAssert(fModified);
if (indexed != buf) zfree(indexed); if (indexed != buf) zfree(indexed);
} }
@ -1986,6 +2084,8 @@ void slotToKeyDel(robj *key) {
} }
void slotToKeyFlush(void) { void slotToKeyFlush(void) {
serverAssert(GlobalLocksAcquired());
raxFree(g_pserver->cluster->slots_to_keys); raxFree(g_pserver->cluster->slots_to_keys);
g_pserver->cluster->slots_to_keys = raxNew(); g_pserver->cluster->slots_to_keys = raxNew();
memset(g_pserver->cluster->slots_keys_count,0, memset(g_pserver->cluster->slots_keys_count,0,
@ -2015,6 +2115,8 @@ unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int coun
/* Remove all the keys in the specified hash slot. /* Remove all the keys in the specified hash slot.
* The number of removed items is returned. */ * The number of removed items is returned. */
unsigned int delKeysInSlot(unsigned int hashslot) { unsigned int delKeysInSlot(unsigned int hashslot) {
serverAssert(GlobalLocksAcquired());
raxIterator iter; raxIterator iter;
int j = 0; int j = 0;
unsigned char indexed[2]; unsigned char indexed[2];
@ -2026,8 +2128,10 @@ unsigned int delKeysInSlot(unsigned int hashslot) {
raxSeek(&iter,">=",indexed,2); raxSeek(&iter,">=",indexed,2);
raxNext(&iter); raxNext(&iter);
auto count = g_pserver->cluster->slots_keys_count[hashslot];
robj *key = createStringObject((char*)iter.key+2,iter.key_len-2); robj *key = createStringObject((char*)iter.key+2,iter.key_len-2);
dbDelete(g_pserver->db[0],key); dbDelete(g_pserver->db[0],key);
serverAssert(count > g_pserver->cluster->slots_keys_count[hashslot]); // we should have deleted something or we will be in an infinite loop
decrRefCount(key); decrRefCount(key);
j++; j++;
} }
@ -2313,7 +2417,6 @@ redisDbPersistentData::~redisDbPersistentData()
if (m_pdbSnapshotASYNC) if (m_pdbSnapshotASYNC)
endSnapshot(m_pdbSnapshotASYNC); endSnapshot(m_pdbSnapshotASYNC);
serverAssert(m_spdbSnapshotHOLDER == nullptr);
//serverAssert(m_pdbSnapshot == nullptr); //serverAssert(m_pdbSnapshot == nullptr);
serverAssert(m_refCount == 0); serverAssert(m_refCount == 0);
//serverAssert(m_pdict->iterators == 0); //serverAssert(m_pdict->iterators == 0);

View File

@ -31,6 +31,7 @@
#include "server.h" #include "server.h"
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */ #include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
#include "crc64.h" #include "crc64.h"
#include "cron.h"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <signal.h> #include <signal.h>
@ -251,6 +252,10 @@ void xorObjectDigest(redisDb *db, robj_roptr keyobj, unsigned char *digest, robj
mt->digest(&md,mv->value); mt->digest(&md,mv->value);
xorDigest(digest,md.x,sizeof(md.x)); xorDigest(digest,md.x,sizeof(md.x));
} }
} else if (o->type == OBJ_CRON) {
cronjob *job = (cronjob*)ptrFromObj(o);
mixDigest(digest, &job->interval, sizeof(job->interval));
mixDigest(digest, job->script.get(), job->script.size());
} else { } else {
serverPanic("Unknown object type"); serverPanic("Unknown object type");
} }
@ -358,6 +363,7 @@ void debugCommand(client *c) {
"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.", "CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
"DIGEST -- Output a hex signature representing the current DB content.", "DIGEST -- Output a hex signature representing the current DB content.",
"DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.", "DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.",
"DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false]",
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.", "ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
"LOG <message> -- write message to the server log.", "LOG <message> -- write message to the server log.",
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.", "HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
@ -365,6 +371,7 @@ void debugCommand(client *c) {
"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.", "LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.",
"LUA-ALWAYS-REPLICATE-COMMANDS <0|1> -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.", "LUA-ALWAYS-REPLICATE-COMMANDS <0|1> -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.",
"OBJECT <key> -- Show low level info about key and associated value.", "OBJECT <key> -- Show low level info about key and associated value.",
"OOM -- Crash the server simulating an out-of-memory error.",
"PANIC -- Crash the server simulating a panic.", "PANIC -- Crash the server simulating a panic.",
"POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.", "POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.",
"RELOAD -- Save the RDB on disk and reload it back in memory.", "RELOAD -- Save the RDB on disk and reload it back in memory.",
@ -586,8 +593,8 @@ NULL
} }
} else if (!strcasecmp(szFromObj(c->argv[1]),"protocol") && c->argc == 3) { } else if (!strcasecmp(szFromObj(c->argv[1]),"protocol") && c->argc == 3) {
/* DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map| /* DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|
* attrib|push|verbatim|true|false|state|err|bloberr] */ * attrib|push|verbatim|true|false] */
char *name = szFromObj(c->argv[2]); const char *name = szFromObj(c->argv[2]);
if (!strcasecmp(name,"string")) { if (!strcasecmp(name,"string")) {
addReplyBulkCString(c,"Hello World"); addReplyBulkCString(c,"Hello World");
} else if (!strcasecmp(name,"integer")) { } else if (!strcasecmp(name,"integer")) {
@ -634,7 +641,7 @@ NULL
} else if (!strcasecmp(name,"verbatim")) { } else if (!strcasecmp(name,"verbatim")) {
addReplyVerbatim(c,"This is a verbatim\nstring",25,"txt"); addReplyVerbatim(c,"This is a verbatim\nstring",25,"txt");
} else { } else {
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false|state|err|bloberr"); addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false");
} }
} else if (!strcasecmp(szFromObj(c->argv[1]),"sleep") && c->argc == 3) { } else if (!strcasecmp(szFromObj(c->argv[1]),"sleep") && c->argc == 3) {
double dtime = strtod(szFromObj(c->argv[2]),NULL); double dtime = strtod(szFromObj(c->argv[2]),NULL);
@ -683,9 +690,12 @@ NULL
sds stats = sdsempty(); sds stats = sdsempty();
char buf[4096]; char buf[4096];
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK) if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK) {
sdsfree(stats);
return; return;
}
if (dbid < 0 || dbid >= cserver.dbnum) { if (dbid < 0 || dbid >= cserver.dbnum) {
sdsfree(stats);
addReplyError(c,"Out of range database"); addReplyError(c,"Out of range database");
return; return;
} }
@ -832,6 +842,8 @@ void serverLogObjectDebugInfo(robj_roptr o) {
serverLog(LL_WARNING,"Sorted set size: %d", (int) zsetLength(o)); serverLog(LL_WARNING,"Sorted set size: %d", (int) zsetLength(o));
if (o->encoding == OBJ_ENCODING_SKIPLIST) if (o->encoding == OBJ_ENCODING_SKIPLIST)
serverLog(LL_WARNING,"Skiplist level: %d", (int) ((const zset*)ptrFromObj(o))->zsl->level); serverLog(LL_WARNING,"Skiplist level: %d", (int) ((const zset*)ptrFromObj(o))->zsl->level);
} else if (o->type == OBJ_STREAM) {
serverLog(LL_WARNING,"Stream size: %d", (int) streamLength(o));
} }
} }

View File

@ -5,8 +5,8 @@
* We do that by scanning the keyspace and for each pointer we have, we can try to * We do that by scanning the keyspace and for each pointer we have, we can try to
* ask the allocator if moving it to a new address will help reduce fragmentation. * ask the allocator if moving it to a new address will help reduce fragmentation.
* *
* Copyright (c) 2017, Oran Agra * Copyright (c) 2020, Oran Agra
* Copyright (c) 2017, Redis Labs, Inc * Copyright (c) 2020, Redis Labs, Inc
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -421,25 +421,32 @@ bool replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
return false; return false;
} }
long activeDefragQuickListNodes(quicklist *ql) { long activeDefragQuickListNode(quicklist *ql, quicklistNode **node_ref) {
quicklistNode *node = ql->head, *newnode; quicklistNode *newnode, *node = *node_ref;
long defragged = 0; long defragged = 0;
unsigned char *newzl; unsigned char *newzl;
if ((newnode = (quicklistNode*)activeDefragAlloc(node))) {
if (newnode->prev)
newnode->prev->next = newnode;
else
ql->head = newnode;
if (newnode->next)
newnode->next->prev = newnode;
else
ql->tail = newnode;
*node_ref = node = newnode;
defragged++;
}
if ((newzl = (unsigned char*)activeDefragAlloc(node->zl)))
defragged++, node->zl = newzl;
return defragged;
}
long activeDefragQuickListNodes(quicklist *ql) {
quicklistNode *node = ql->head;
long defragged = 0;
while (node) { while (node) {
if ((newnode = (quicklistNode*)activeDefragAlloc(node))) { defragged += activeDefragQuickListNode(ql, &node);
if (newnode->prev)
newnode->prev->next = newnode;
else
ql->head = newnode;
if (newnode->next)
newnode->next->prev = newnode;
else
ql->tail = newnode;
node = newnode;
defragged++;
}
if ((newzl = (unsigned char*)activeDefragAlloc(node->zl)))
defragged++, node->zl = newzl;
node = node->next; node = node->next;
} }
return defragged; return defragged;
@ -453,12 +460,48 @@ void defragLater(redisDb *db, dictEntry *kde) {
listAddNodeTail(db->defrag_later, key); listAddNodeTail(db->defrag_later, key);
} }
long scanLaterList(robj *ob) { /* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
long scanLaterList(robj *ob, unsigned long *cursor, long long endtime, long long *defragged) {
quicklist *ql = (quicklist*)ptrFromObj(ob); quicklist *ql = (quicklist*)ptrFromObj(ob);
quicklistNode *node;
long iterations = 0;
int bookmark_failed = 0;
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST) if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST)
return 0; return 0;
g_pserver->stat_active_defrag_scanned+=ql->len;
return activeDefragQuickListNodes(ql); if (*cursor == 0) {
/* if cursor is 0, we start new iteration */
node = ql->head;
} else {
node = quicklistBookmarkFind(ql, "_AD");
if (!node) {
/* if the bookmark was deleted, it means we reached the end. */
*cursor = 0;
return 0;
}
node = node->next;
}
(*cursor)++;
while (node) {
(*defragged) += activeDefragQuickListNode(ql, &node);
g_pserver->stat_active_defrag_scanned++;
if (++iterations > 128 && !bookmark_failed) {
if (ustime() > endtime) {
if (!quicklistBookmarkCreate(&ql, "_AD", node)) {
bookmark_failed = 1;
} else {
ob->m_ptr = ql; /* bookmark creation may have re-allocated the quicklist */
return 1;
}
}
iterations = 0;
}
node = node->next;
}
quicklistBookmarkDelete(ql, "_AD");
*cursor = 0;
return bookmark_failed? 1: 0;
} }
typedef struct { typedef struct {
@ -651,7 +694,8 @@ int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime,
void *newdata = activeDefragAlloc(ri.data); void *newdata = activeDefragAlloc(ri.data);
if (newdata) if (newdata)
raxSetData(ri.node, ri.data=newdata), (*defragged)++; raxSetData(ri.node, ri.data=newdata), (*defragged)++;
if (++iterations > 16) { g_pserver->stat_active_defrag_scanned++;
if (++iterations > 128) {
if (ustime() > endtime) { if (ustime() > endtime) {
serverAssert(ri.key_len==sizeof(last)); serverAssert(ri.key_len==sizeof(last));
memcpy(last,ri.key,ri.key_len); memcpy(last,ri.key,ri.key_len);
@ -914,8 +958,7 @@ long defragOtherGlobals() {
int defragLaterItem(robj *ob, unsigned long *cursor, long long endtime) { int defragLaterItem(robj *ob, unsigned long *cursor, long long endtime) {
if (ob) { if (ob) {
if (ob->type == OBJ_LIST) { if (ob->type == OBJ_LIST) {
g_pserver->stat_active_defrag_hits += scanLaterList(ob); return scanLaterList(ob, cursor, endtime, &g_pserver->stat_active_defrag_hits);
*cursor = 0; /* list has no scan, we must finish it in one go */
} else if (ob->type == OBJ_SET) { } else if (ob->type == OBJ_SET) {
g_pserver->stat_active_defrag_hits += scanLaterSet(ob, cursor); g_pserver->stat_active_defrag_hits += scanLaterSet(ob, cursor);
} else if (ob->type == OBJ_ZSET) { } else if (ob->type == OBJ_ZSET) {
@ -975,11 +1018,6 @@ int defragLaterStep(redisDb *db, long long endtime) {
if (defragLaterItem(o, &defrag_later_cursor, endtime)) if (defragLaterItem(o, &defrag_later_cursor, endtime))
quit = 1; /* time is up, we didn't finish all the work */ quit = 1; /* time is up, we didn't finish all the work */
/* Don't start a new BIG key in this loop, this is because the
* next key can be a list, and scanLaterList must be done in once cycle */
if (!defrag_later_cursor)
quit = 1;
/* Once in 16 scan iterations, 512 pointer reallocations, or 64 fields /* Once in 16 scan iterations, 512 pointer reallocations, or 64 fields
* (if we have a lot of pointers in one hash bucket, or rehashing), * (if we have a lot of pointers in one hash bucket, or rehashing),
* check if we reached the time limit. */ * check if we reached the time limit. */

View File

@ -135,7 +135,7 @@ int _dictInit(dict *d, dictType *type,
* but with the invariant of a USED/BUCKETS ratio near to <= 1 */ * but with the invariant of a USED/BUCKETS ratio near to <= 1 */
int dictResize(dict *d) int dictResize(dict *d)
{ {
int minimal; unsigned long minimal;
if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR; if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
minimal = d->ht[0].used; minimal = d->ht[0].used;
@ -925,6 +925,10 @@ unsigned long dictScan(dict *d,
if (dictSize(d) == 0) return 0; if (dictSize(d) == 0) return 0;
/* Having a safe iterator means no rehashing can happen, see _dictRehashStep.
* This is needed in case the scan callback tries to do dictFind or alike. */
d->iterators++;
if (!dictIsRehashing(d)) { if (!dictIsRehashing(d)) {
t0 = &(d->ht[0]); t0 = &(d->ht[0]);
m0 = t0->sizemask; m0 = t0->sizemask;
@ -991,6 +995,9 @@ unsigned long dictScan(dict *d,
} while (v & (m0 ^ m1)); } while (v & (m0 ^ m1));
} }
/* undo the ++ at the top */
d->iterators--;
return v; return v;
} }

View File

@ -480,7 +480,7 @@ int freeMemoryIfNeeded(void) {
/* By default replicas should ignore maxmemory /* By default replicas should ignore maxmemory
* and just be masters exact copies. */ * and just be masters exact copies. */
if (listLength(g_pserver->masters) && g_pserver->repl_slave_ignore_maxmemory) return C_OK; if (listLength(g_pserver->masters) && g_pserver->repl_slave_ignore_maxmemory && !g_pserver->fActiveReplica) return C_OK;
size_t mem_reported, mem_tofree, mem_freed; size_t mem_reported, mem_tofree, mem_freed;
mstime_t latency, eviction_latency; mstime_t latency, eviction_latency;

View File

@ -77,6 +77,10 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
expireEntryFat *pfat = e.pfatentry(); expireEntryFat *pfat = e.pfatentry();
robj *val = db->find(e.key()); robj *val = db->find(e.key());
int deleted = 0; int deleted = 0;
robj objKey;
initStaticStringObject(objKey, (char*)e.key());
while (!pfat->FEmpty()) while (!pfat->FEmpty())
{ {
if (pfat->nextExpireEntry().when > now) if (pfat->nextExpireEntry().when > now)
@ -129,14 +133,14 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
default: default:
serverAssert(false); serverAssert(false);
} }
robj objSubkey;
initStaticStringObject(objSubkey, (char*)pfat->nextExpireEntry().spsubkey.get());
propagateSubkeyExpire(db, val->type, &objKey, &objSubkey);
pfat->popfrontExpireEntry(); pfat->popfrontExpireEntry();
} }
robj *keyobj = nullptr;
if (deleted || pfat->FEmpty())
keyobj = createStringObject(e.key(),sdslen(e.key()));
if (deleted) if (deleted)
{ {
if (!pfat->FEmpty()) if (!pfat->FEmpty())
@ -145,24 +149,19 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
db->resortExpire(e); db->resortExpire(e);
} }
robj objT;
switch (val->type) switch (val->type)
{ {
case OBJ_SET: case OBJ_SET:
initStaticStringObject(objT, (char*)e.key()); signalModifiedKey(db,&objKey);
signalModifiedKey(db,&objT); notifyKeyspaceEvent(NOTIFY_SET,"srem",&objKey,db->id);
notifyKeyspaceEvent(NOTIFY_SET,"srem",&objT,db->id);
break; break;
} }
} }
if (pfat->FEmpty()) if (pfat->FEmpty())
{ {
removeExpire(db, keyobj); removeExpire(db, &objKey);
} }
if (keyobj)
decrRefCount(keyobj);
} }
int parseUnitString(const char *sz) int parseUnitString(const char *sz)
@ -226,7 +225,8 @@ void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, lo
} }
setExpire(c, c->db, key, subkey, when); setExpire(c, c->db, key, subkey, when);
signalModifiedKey(c->db, key);
g_pserver->dirty++;
addReply(c, shared.cone); addReply(c, shared.cone);
} }
@ -258,6 +258,14 @@ void expireMemberAtCommand(client *c)
expireMemberCore(c, c->argv[1], c->argv[2], 0, when, UNIT_SECONDS); expireMemberCore(c, c->argv[1], c->argv[2], 0, when, UNIT_SECONDS);
} }
void pexpireMemberAtCommand(client *c)
{
long long when;
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
return;
expireMemberCore(c, c->argv[1], c->argv[2], 0, when, UNIT_MILLISECONDS);
}
/* Try to expire a few timed out keys. The algorithm used is adaptive and /* Try to expire a few timed out keys. The algorithm used is adaptive and
* will use few CPU cycles if there are few expiring keys, otherwise * will use few CPU cycles if there are few expiring keys, otherwise

View File

@ -27,6 +27,7 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "fmacros.h"
#include "fastlock.h" #include "fastlock.h"
#include <unistd.h> #include <unistd.h>
#include <sys/syscall.h> #include <sys/syscall.h>
@ -222,6 +223,7 @@ public:
auto itr = m_mapwait.find(pidCheck); auto itr = m_mapwait.find(pidCheck);
if (itr == m_mapwait.end()) if (itr == m_mapwait.end())
break; break;
__atomic_load(&itr->second->m_pidOwner, &pidCheck, __ATOMIC_RELAXED); __atomic_load(&itr->second->m_pidOwner, &pidCheck, __ATOMIC_RELAXED);
if (pidCheck == thispid) if (pidCheck == thispid)
{ {
@ -233,7 +235,7 @@ public:
{ {
auto itr = m_mapwait.find(pidCheck); auto itr = m_mapwait.find(pidCheck);
serverLog(3 /* LL_WARNING */, "\t%d: (%p) %s", pidCheck, itr->second, itr->second->szName); serverLog(3 /* LL_WARNING */, "\t%d: (%p) %s", pidCheck, itr->second, itr->second->szName);
pidCheck = itr->second->m_pidOwner; __atomic_load(&itr->second->m_pidOwner, &pidCheck, __ATOMIC_RELAXED);
if (pidCheck == thispid) if (pidCheck == thispid)
break; break;
} }

View File

@ -1548,6 +1548,7 @@ void pfdebugCommand(client *c) {
sds decoded = sdsempty(); sds decoded = sdsempty();
if (hdr->encoding != HLL_SPARSE) { if (hdr->encoding != HLL_SPARSE) {
sdsfree(decoded);
addReplyError(c,"HLL encoding is not sparse"); addReplyError(c,"HLL encoding is not sparse");
return; return;
} }

View File

@ -85,7 +85,7 @@ int THPGetAnonHugePagesSize(void) {
/* ---------------------------- Latency API --------------------------------- */ /* ---------------------------- Latency API --------------------------------- */
/* Latency monitor initialization. We just need to create the dictionary /* Latency monitor initialization. We just need to create the dictionary
* of time series, each time serie is craeted on demand in order to avoid * of time series, each time serie is created on demand in order to avoid
* having a fixed list to maintain. */ * having a fixed list to maintain. */
void latencyMonitorInit(void) { void latencyMonitorInit(void) {
g_pserver->latency_events = dictCreate(&latencyTimeSeriesDictType,NULL); g_pserver->latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);

View File

@ -728,9 +728,9 @@ void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) {
* flags into the command flags used by the Redis core. * flags into the command flags used by the Redis core.
* *
* It returns the set of flags, or -1 if unknown flags are found. */ * It returns the set of flags, or -1 if unknown flags are found. */
int commandFlagsFromString(char *s) { int64_t commandFlagsFromString(char *s) {
int count, j; int count, j;
int flags = 0; int64_t flags = 0;
sds *tokens = sdssplitlen(s,strlen(s)," ",1,&count); sds *tokens = sdssplitlen(s,strlen(s)," ",1,&count);
for (j = 0; j < count; j++) { for (j = 0; j < count; j++) {
char *t = tokens[j]; char *t = tokens[j];
@ -744,6 +744,7 @@ int commandFlagsFromString(char *s) {
else if (!strcasecmp(t,"random")) flags |= CMD_RANDOM; else if (!strcasecmp(t,"random")) flags |= CMD_RANDOM;
else if (!strcasecmp(t,"allow-stale")) flags |= CMD_STALE; else if (!strcasecmp(t,"allow-stale")) flags |= CMD_STALE;
else if (!strcasecmp(t,"no-monitor")) flags |= CMD_SKIP_MONITOR; else if (!strcasecmp(t,"no-monitor")) flags |= CMD_SKIP_MONITOR;
else if (!strcasecmp(t,"no-slowlog")) flags |= CMD_SKIP_SLOWLOG;
else if (!strcasecmp(t,"fast")) flags |= CMD_FAST; else if (!strcasecmp(t,"fast")) flags |= CMD_FAST;
else if (!strcasecmp(t,"no-auth")) flags |= CMD_NO_AUTH; else if (!strcasecmp(t,"no-auth")) flags |= CMD_NO_AUTH;
else if (!strcasecmp(t,"getkeys-api")) flags |= CMD_MODULE_GETKEYS; else if (!strcasecmp(t,"getkeys-api")) flags |= CMD_MODULE_GETKEYS;
@ -795,6 +796,8 @@ int commandFlagsFromString(char *s) {
* this means. * this means.
* * **"no-monitor"**: Don't propagate the command on monitor. Use this if * * **"no-monitor"**: Don't propagate the command on monitor. Use this if
* the command has sensible data among the arguments. * the command has sensible data among the arguments.
* * **"no-slowlog"**: Don't log this command in the slowlog. Use this if
* the command has sensible data among the arguments.
* * **"fast"**: The command time complexity is not greater * * **"fast"**: The command time complexity is not greater
* than O(log(N)) where N is the size of the collection or * than O(log(N)) where N is the size of the collection or
* anything else representing the normal scalability * anything else representing the normal scalability
@ -812,7 +815,7 @@ int commandFlagsFromString(char *s) {
* to authenticate a client. * to authenticate a client.
*/ */
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) { int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
int flags = strflags ? commandFlagsFromString((char*)strflags) : 0; int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
if (flags == -1) return REDISMODULE_ERR; if (flags == -1) return REDISMODULE_ERR;
if ((flags & CMD_MODULE_NO_CLUSTER) && g_pserver->cluster_enabled) if ((flags & CMD_MODULE_NO_CLUSTER) && g_pserver->cluster_enabled)
return REDISMODULE_ERR; return REDISMODULE_ERR;
@ -873,6 +876,7 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
module->in_call = 0; module->in_call = 0;
module->in_hook = 0; module->in_hook = 0;
module->options = 0; module->options = 0;
module->info_cb = 0;
ctx->module = module; ctx->module = module;
} }
@ -903,7 +907,8 @@ 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). */
int RM_SignalModifiedKey(RedisModuleCtx *ctx, RedisModuleString *keyname) { int RM_SignalModifiedKey(RedisModuleCtx *ctx, RedisModuleString *keyname) {
signalModifiedKey(ctx->client->db,keyname); signalModifiedKey(ctx->client->db,keyname);
return REDISMODULE_OK; return REDISMODULE_OK;
@ -1051,6 +1056,17 @@ RedisModuleString *RM_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll
return RM_CreateString(ctx,buf,len); return RM_CreateString(ctx,buf,len);
} }
/* Like RedisModule_CreatString(), but creates a string starting from a double
* integer instead of taking a buffer and its length.
*
* The returned string must be released with RedisModule_FreeString() or by
* enabling automatic memory management. */
RedisModuleString *RM_CreateStringFromDouble(RedisModuleCtx *ctx, double d) {
char buf[128];
size_t len = d2string(buf,sizeof(buf),d);
return RM_CreateString(ctx,buf,len);
}
/* Like RedisModule_CreatString(), but creates a string starting from a long /* Like RedisModule_CreatString(), but creates a string starting from a long
* double. * double.
* *
@ -1848,7 +1864,12 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
* current request context (whether the client is a Lua script or in a MULTI), * current request context (whether the client is a Lua script or in a MULTI),
* and about the Redis instance in general, i.e replication and persistence. * and about the Redis instance in general, i.e replication and persistence.
* *
* The available flags are: * It is possible to call this function even with a NULL context, however
* in this case the following flags will not be reported:
*
* * LUA, MULTI, REPLICATED, DIRTY (see below for more info).
*
* Available flags and their meaning:
* *
* * REDISMODULE_CTX_FLAGS_LUA: The command is running in a Lua script * * REDISMODULE_CTX_FLAGS_LUA: The command is running in a Lua script
* *
@ -1901,20 +1922,22 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
int flags = 0; int flags = 0;
/* Client specific flags */ /* Client specific flags */
if (ctx->client) { if (ctx) {
if (ctx->client->flags & CLIENT_LUA) if (ctx->client) {
flags |= REDISMODULE_CTX_FLAGS_LUA; if (ctx->client->flags & CLIENT_LUA)
if (ctx->client->flags & CLIENT_MULTI) flags |= REDISMODULE_CTX_FLAGS_LUA;
flags |= REDISMODULE_CTX_FLAGS_MULTI; if (ctx->client->flags & CLIENT_MULTI)
/* Module command recieved from MASTER, is replicated. */ flags |= REDISMODULE_CTX_FLAGS_MULTI;
if (ctx->client->flags & CLIENT_MASTER) /* Module command recieved from MASTER, is replicated. */
flags |= REDISMODULE_CTX_FLAGS_REPLICATED; if (ctx->client->flags & CLIENT_MASTER)
} flags |= REDISMODULE_CTX_FLAGS_REPLICATED;
}
/* For DIRTY flags, we need the blocked client if used */ /* For DIRTY flags, we need the blocked client if used */
client *c = ctx->blocked_client ? ctx->blocked_client->client : ctx->client; client *c = ctx->blocked_client ? ctx->blocked_client->client : ctx->client;
if (c && (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC))) { if (c && (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC))) {
flags |= REDISMODULE_CTX_FLAGS_MULTI_DIRTY; flags |= REDISMODULE_CTX_FLAGS_MULTI_DIRTY;
}
} }
if (g_pserver->cluster_enabled) if (g_pserver->cluster_enabled)
@ -3379,13 +3402,8 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
* a Lua script in the context of AOF and slaves. */ * a Lua script in the context of AOF and slaves. */
if (replicate) moduleReplicateMultiIfNeeded(ctx); if (replicate) moduleReplicateMultiIfNeeded(ctx);
if (ctx->client->flags & CLIENT_MULTI ||
ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
c->flags |= CLIENT_MULTI;
}
/* Run the command */ /* Run the command */
call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS; call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_NOWRAP;
if (replicate) { if (replicate) {
if (!(flags & REDISMODULE_ARGV_NO_AOF)) if (!(flags & REDISMODULE_ARGV_NO_AOF))
call_flags |= CMD_CALL_PROPAGATE_AOF; call_flags |= CMD_CALL_PROPAGATE_AOF;
@ -3394,9 +3412,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
} }
call(c,call_flags); call(c,call_flags);
/* Convert the result of the Redis command into a suitable Lua type. /* Convert the result of the Redis command into a module reply. */
* The first thing we need is to create a single string from the client
* output buffers. */
proto = sdsnewlen(c->buf,c->bufpos); proto = sdsnewlen(c->buf,c->bufpos);
c->bufpos = 0; c->bufpos = 0;
while(listLength(c->reply)) { while(listLength(c->reply)) {
@ -3600,6 +3616,8 @@ void moduleTypeNameByID(char *name, uint64_t moduleid) {
* // Optional fields * // Optional fields
* .digest = myType_DigestCallBack, * .digest = myType_DigestCallBack,
* .mem_usage = myType_MemUsageCallBack, * .mem_usage = myType_MemUsageCallBack,
* .aux_load = myType_AuxRDBLoadCallBack,
* .aux_save = myType_AuxRDBSaveCallBack,
* } * }
* *
* * **rdb_load**: A callback function pointer that loads data from RDB files. * * **rdb_load**: A callback function pointer that loads data from RDB files.
@ -3607,6 +3625,10 @@ void moduleTypeNameByID(char *name, uint64_t moduleid) {
* * **aof_rewrite**: A callback function pointer that rewrites data as commands. * * **aof_rewrite**: A callback function pointer that rewrites data as commands.
* * **digest**: A callback function pointer that is used for `DEBUG DIGEST`. * * **digest**: A callback function pointer that is used for `DEBUG DIGEST`.
* * **free**: A callback function pointer that can free a type value. * * **free**: A callback function pointer that can free a type value.
* * **aux_save**: A callback function pointer that saves out of keyspace data to RDB files.
* 'when' argument is either REDISMODULE_AUX_BEFORE_RDB or REDISMODULE_AUX_AFTER_RDB.
* * **aux_load**: A callback function pointer that loads out of keyspace data from RDB files.
* Similar to aux_save, returns REDISMODULE_OK on success, and ERR otherwise.
* *
* The **digest* and **mem_usage** methods should currently be omitted since * The **digest* and **mem_usage** methods should currently be omitted since
* they are not yet implemented inside the Redis modules core. * they are not yet implemented inside the Redis modules core.
@ -3724,14 +3746,15 @@ void moduleRDBLoadError(RedisModuleIO *io) {
io->error = 1; io->error = 1;
return; return;
} }
serverLog(LL_WARNING, serverPanic(
"Error loading data from RDB (short read or EOF). " "Error loading data from RDB (short read or EOF). "
"Read performed by module '%s' about type '%s' " "Read performed by module '%s' about type '%s' "
"after reading '%llu' bytes of a value.", "after reading '%llu' bytes of a value "
"for key named: '%s'.",
io->type->module->name, io->type->module->name,
io->type->name, io->type->name,
(unsigned long long)io->bytes); (unsigned long long)io->bytes,
exit(1); io->key? szFromObj(io->key): "(null)");
} }
/* Returns 0 if there's at least one registered data type that did not declare /* Returns 0 if there's at least one registered data type that did not declare
@ -3981,7 +4004,7 @@ void RM_SaveLongDouble(RedisModuleIO *io, long double value) {
/* Long double has different number of bits in different platforms, so we /* Long double has different number of bits in different platforms, so we
* save it as a string type. */ * save it as a string type. */
size_t len = ld2string(buf,sizeof(buf),value,LD_STR_HEX); size_t len = ld2string(buf,sizeof(buf),value,LD_STR_HEX);
RM_SaveStringBuffer(io,buf,len+1); /* len+1 for '\0' */ RM_SaveStringBuffer(io,buf,len);
} }
/* In the context of the rdb_save method of a module data type, loads back the /* In the context of the rdb_save method of a module data type, loads back the
@ -4357,6 +4380,24 @@ void unblockClientFromModule(client *c) {
moduleFreeContext(&ctx); moduleFreeContext(&ctx);
} }
/* If we made it here and client is still blocked it means that the command
* timed-out, client was killed or disconnected and disconnect_callback was
* not implemented (or it was, but RM_UnblockClient was not called from
* within it, as it should).
* We must call moduleUnblockClient in order to free privdata and
* RedisModuleBlockedClient.
*
* Note that we only do that for clients that are blocked on keys, for which
* the contract is that the module should not call RM_UnblockClient under
* normal circumstances.
* Clients implementing threads and working with private data should be
* aware that calling RM_UnblockClient for every blocked client is their
* responsibility, and if they fail to do so memory may leak. Ideally they
* should implement the disconnect and timeout callbacks and call
* RM_UnblockClient, but any other way is also acceptable. */
if (bc->blocked_on_keys && !bc->unblocked)
moduleUnblockClient(c);
bc->client = NULL; bc->client = NULL;
/* Reset the client for a new query since, for blocking commands implemented /* Reset the client for a new query since, for blocking commands implemented
* into modules, we do not it immediately after the command returns (and * into modules, we do not it immediately after the command returns (and
@ -4468,6 +4509,10 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
* *
* free_privdata: called in order to free the private data that is passed * free_privdata: called in order to free the private data that is passed
* by RedisModule_UnblockClient() call. * by RedisModule_UnblockClient() call.
*
* Note: RedisModule_UnblockClient should be called for every blocked client,
* even if client was killed, timed-out or disconnected. Failing to do so
* will result in memory leaks.
*/ */
RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) { RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) {
return moduleBlockClient(ctx,reply_callback,timeout_callback,free_privdata,timeout_ms, NULL,0,NULL); return moduleBlockClient(ctx,reply_callback,timeout_callback,free_privdata,timeout_ms, NULL,0,NULL);
@ -4522,7 +4567,15 @@ RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc
* freed using the free_privdata callback provided by the user. * freed using the free_privdata callback provided by the user.
* *
* However the reply callback will be able to access the argument vector of * However the reply callback will be able to access the argument vector of
* the command, so the private data is often not needed. */ * the command, so the private data is often not needed.
*
* Note: Under normal circumstances RedisModule_UnblockClient should not be
* called for clients that are blocked on keys (Either the key will
* become ready or a timeout will occur). If for some reason you do want
* to call RedisModule_UnblockClient it is possible: Client will be
* handled as if it were timed-out (You must implement the timeout
* callback in that case).
*/
RedisModuleBlockedClient *RM_BlockClientOnKeys(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) { RedisModuleBlockedClient *RM_BlockClientOnKeys(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) {
return moduleBlockClient(ctx,reply_callback,timeout_callback,free_privdata,timeout_ms, keys,numkeys,privdata); return moduleBlockClient(ctx,reply_callback,timeout_callback,free_privdata,timeout_ms, keys,numkeys,privdata);
} }
@ -4800,9 +4853,9 @@ int RM_BlockedClientDisconnected(RedisModuleCtx *ctx) {
* *
* To call non-reply APIs, the thread safe context must be prepared with: * To call non-reply APIs, the thread safe context must be prepared with:
* *
* RedisModule_ThreadSafeCallStart(ctx); * RedisModule_ThreadSafeContextLock(ctx);
* ... make your call here ... * ... make your call here ...
* RedisModule_ThreadSafeCallStop(ctx); * RedisModule_ThreadSafeContextUnlock(ctx);
* *
* This is not needed when using `RedisModule_Reply*` functions, assuming * This is not needed when using `RedisModule_Reply*` functions, assuming
* that a blocked client was used when the context was created, otherwise * that a blocked client was used when the context was created, otherwise
@ -4918,7 +4971,8 @@ 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_ALL: All events * - REDISMODULE_NOTIFY_KEYMISS: Key-miss events
* - REDISMODULE_NOTIFY_ALL: All events (Excluding REDISMODULE_NOTIFY_KEYMISS)
* *
* We do not distinguish between key events and keyspace events, and it is up * We do not distinguish between key events and keyspace events, and it is up
* to the module to filter the actions taken based on the key. * to the module to filter the actions taken based on the key.
@ -6309,7 +6363,7 @@ int moduleUnregisterUsedAPI(RedisModule *module) {
RedisModule *used = (RedisModule*)ln->value; RedisModule *used = (RedisModule*)ln->value;
listNode *ln = listSearchKey(used->usedby,module); listNode *ln = listSearchKey(used->usedby,module);
if (ln) { if (ln) {
listDelNode(module->usingMods,ln); listDelNode(used->usedby,ln);
count++; count++;
} }
} }
@ -6604,24 +6658,32 @@ void RM_ScanCursorDestroy(RedisModuleScanCursor *cursor) {
zfree(cursor); zfree(cursor);
} }
/* Scan api that allows a module to scan all the keys and value in the selected db. /* Scan API that allows a module to scan all the keys and value in
* the selected db.
* *
* Callback for scan implementation. * Callback for scan implementation.
* void scan_callback(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata); * void scan_callback(RedisModuleCtx *ctx, RedisModuleString *keyname,
* - ctx - the redis module context provided to for the scan. * RedisModuleKey *key, void *privdata);
* - keyname - owned by the caller and need to be retained if used after this function. * ctx - the redis module context provided to for the scan.
* - key - holds info on the key and value, it is provided as best effort, in some cases it might * keyname - owned by the caller and need to be retained if used after this
* be NULL, in which case the user should (can) use RedisModule_OpenKey (and CloseKey too). * function.
* when it is provided, it is owned by the caller and will be free when the callback returns. *
* - privdata - the user data provided to RedisModule_Scan. * key - holds info on the key and value, it is provided as best effort, in
* some cases it might be NULL, in which case the user should (can) use
* RedisModule_OpenKey (and CloseKey too).
* when it is provided, it is owned by the caller and will be free when the
* callback returns.
*
* privdata - the user data provided to RedisModule_Scan.
* *
* The way it should be used: * The way it should be used:
* RedisModuleCursor *c = RedisModule_ScanCursorCreate(); * RedisModuleCursor *c = RedisModule_ScanCursorCreate();
* while(RedisModule_Scan(ctx, c, callback, privateData)); * while(RedisModule_Scan(ctx, c, callback, privateData));
* RedisModule_ScanCursorDestroy(c); * RedisModule_ScanCursorDestroy(c);
* *
* It is also possible to use this API from another thread while the lock is acquired durring * It is also possible to use this API from another thread while the lock
* the actuall call to RM_Scan: * is acquired durring the actuall call to RM_Scan:
*
* RedisModuleCursor *c = RedisModule_ScanCursorCreate(); * RedisModuleCursor *c = RedisModule_ScanCursorCreate();
* RedisModule_ThreadSafeContextLock(ctx); * RedisModule_ThreadSafeContextLock(ctx);
* while(RedisModule_Scan(ctx, c, callback, privateData)){ * while(RedisModule_Scan(ctx, c, callback, privateData)){
@ -6631,9 +6693,26 @@ void RM_ScanCursorDestroy(RedisModuleScanCursor *cursor) {
* } * }
* RedisModule_ScanCursorDestroy(c); * RedisModule_ScanCursorDestroy(c);
* *
* The function will return 1 if there are more elements to scan and 0 otherwise, * The function will return 1 if there are more elements to scan and
* possibly setting errno if the call failed. * 0 otherwise, possibly setting errno if the call failed.
* It is also possible to restart and existing cursor using RM_CursorRestart. */ *
* It is also possible to restart and existing cursor using RM_CursorRestart.
*
* IMPORTANT: This API is very similar to the Redis SCAN command from the
* point of view of the guarantees it provides. This means that the API
* may report duplicated keys, but guarantees to report at least one time
* every key that was there from the start to the end of the scanning process.
*
* NOTE: If you do database changes within the callback, you should be aware
* that the internal state of the database may change. For instance it is safe
* to delete or modify the current key, but may not be safe to delete any
* other key.
* Moreover playing with the Redis keyspace while iterating may have the
* effect of returning more duplicates. A safe pattern is to store the keys
* names you want to modify elsewhere, and perform the actions on the keys
* later when the iteration is complete. Howerver this can cost a lot of
* memory, so it may make sense to just operate on the current key when
* possible during the iteration, given that this is safe. */
int RM_Scan(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata) { int RM_Scan(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata) {
if (cursor->done) { if (cursor->done) {
errno = ENOENT; errno = ENOENT;
@ -6711,9 +6790,17 @@ static void moduleScanKeyCallback(void *privdata, const dictEntry *de) {
* RedisModule_CloseKey(key); * RedisModule_CloseKey(key);
* RedisModule_ScanCursorDestroy(c); * RedisModule_ScanCursorDestroy(c);
* *
* The function will return 1 if there are more elements to scan and 0 otherwise, * The function will return 1 if there are more elements to scan and 0 otherwise,
* possibly setting errno if the call failed. * possibly setting errno if the call failed.
* It is also possible to restart and existing cursor using RM_CursorRestart. */ * It is also possible to restart and existing cursor using RM_CursorRestart.
*
* NOTE: Certain operations are unsafe while iterating the object. For instance
* while the API guarantees to return at least one time all the elements that
* are present in the data structure consistently from the start to the end
* of the iteration (see HSCAN and similar commands documentation), the more
* you play with the elements, the more duplicates you may get. In general
* deleting the current element of the data structure is safe, while removing
* the key you are iterating is not safe. */
int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata) { int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata) {
if (key == NULL || key->value == NULL) { if (key == NULL || key->value == NULL) {
errno = EINVAL; errno = EINVAL;
@ -6819,7 +6906,7 @@ int RM_Fork(RedisModuleForkDoneHandler cb, void *user_data) {
g_pserver->module_child_pid = childpid; g_pserver->module_child_pid = childpid;
moduleForkInfo.done_handler = cb; moduleForkInfo.done_handler = cb;
moduleForkInfo.done_handler_user_data = user_data; moduleForkInfo.done_handler_user_data = user_data;
serverLog(LL_NOTICE, "Module fork started pid: %d ", childpid); serverLog(LL_VERBOSE, "Module fork started pid: %d ", childpid);
} }
return childpid; return childpid;
} }
@ -6842,7 +6929,7 @@ int TerminateModuleForkChild(int child_pid, int wait) {
g_pserver->module_child_pid != child_pid) return C_ERR; g_pserver->module_child_pid != child_pid) return C_ERR;
int statloc; int statloc;
serverLog(LL_NOTICE,"Killing running module fork child: %ld", serverLog(LL_VERBOSE,"Killing running module fork child: %ld",
(long) g_pserver->module_child_pid); (long) g_pserver->module_child_pid);
if (kill(g_pserver->module_child_pid,SIGUSR1) != -1 && wait) { if (kill(g_pserver->module_child_pid,SIGUSR1) != -1 && wait) {
while(wait4(g_pserver->module_child_pid,&statloc,0,NULL) != while(wait4(g_pserver->module_child_pid,&statloc,0,NULL) !=
@ -7769,6 +7856,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(CreateStringFromCallReply); REGISTER_API(CreateStringFromCallReply);
REGISTER_API(CreateString); REGISTER_API(CreateString);
REGISTER_API(CreateStringFromLongLong); REGISTER_API(CreateStringFromLongLong);
REGISTER_API(CreateStringFromDouble);
REGISTER_API(CreateStringFromLongDouble); REGISTER_API(CreateStringFromLongDouble);
REGISTER_API(CreateStringFromString); REGISTER_API(CreateStringFromString);
REGISTER_API(CreateStringPrintf); REGISTER_API(CreateStringPrintf);

View File

@ -180,8 +180,10 @@ void execCommand(client *c) {
must_propagate = 1; must_propagate = 1;
} }
int acl_retval = ACLCheckCommandPerm(c); int acl_keypos;
int acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
if (acl_retval != ACL_OK) { if (acl_retval != ACL_OK) {
addACLLogEntry(c,acl_retval,acl_keypos,NULL);
addReplyErrorFormat(c, addReplyErrorFormat(c,
"-NOPERM ACLs rules changed between the moment the " "-NOPERM ACLs rules changed between the moment the "
"transaction was accumulated and the EXEC call. " "transaction was accumulated and the EXEC call. "

View File

@ -137,7 +137,8 @@ client *createClient(connection *conn, int iel) {
c->ctime = c->lastinteraction = g_pserver->unixtime; c->ctime = c->lastinteraction = g_pserver->unixtime;
/* If the default user does not require authentication, the user is /* If the default user does not require authentication, the user is
* directly authenticated. */ * directly authenticated. */
c->authenticated = (c->puser->flags & USER_FLAG_NOPASS) != 0; c->authenticated = (c->puser->flags & USER_FLAG_NOPASS) &&
!(c->puser->flags & USER_FLAG_DISABLED);
c->replstate = REPL_STATE_NONE; c->replstate = REPL_STATE_NONE;
c->repl_put_online_on_ack = 0; c->repl_put_online_on_ack = 0;
c->reploff = 0; c->reploff = 0;
@ -177,6 +178,7 @@ client *createClient(connection *conn, int iel) {
c->master_error = 0; c->master_error = 0;
memset(c->uuid, 0, UUID_BINARY_LEN); memset(c->uuid, 0, UUID_BINARY_LEN);
c->client_tracking_prefixes = NULL;
c->auth_callback = NULL; c->auth_callback = NULL;
c->auth_callback_privdata = NULL; c->auth_callback_privdata = NULL;
c->auth_module = NULL; c->auth_module = NULL;
@ -486,10 +488,22 @@ void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync)
* Where the master must propagate the first change even if the second * Where the master must propagate the first change even if the second
* will produce an error. However it is useful to log such events since * will produce an error. However it is useful to log such events since
* they are rare and may hint at errors in a script or a bug in Redis. */ * they are rare and may hint at errors in a script or a bug in Redis. */
if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) { int ctype = getClientType(c);
const char* to = reinterpret_cast<const char*>(c->flags & CLIENT_MASTER? "master": "replica"); if (ctype == CLIENT_TYPE_MASTER || ctype == CLIENT_TYPE_SLAVE || c->id == CLIENT_ID_AOF) {
const char* from = reinterpret_cast<const char*>(c->flags & CLIENT_MASTER? "replica": "master"); const char *to, *from;
const char *cmdname = reinterpret_cast<const char*>(c->lastcmd ? c->lastcmd->name : "<unknown>");
if (c->id == CLIENT_ID_AOF) {
to = "AOF-loading-client";
from = "server";
} else if (ctype == CLIENT_TYPE_MASTER) {
to = "master";
from = "replica";
} else {
to = "replica";
from = "master";
}
const char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error " serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
"to its %s: '%s' after processing the command " "to its %s: '%s' after processing the command "
"'%s'", from, to, s, cmdname); "'%s'", from, to, s, cmdname);
@ -498,7 +512,8 @@ void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync)
std::string str = escapeString(c->querybuf); std::string str = escapeString(c->querybuf);
printf("\tquerybuf: %s\n", str.c_str()); printf("\tquerybuf: %s\n", str.c_str());
} }
c->master_error = 1;
g_pserver->stat_unexpected_error_replies++;
} }
} }
@ -1100,7 +1115,7 @@ void clientAcceptHandler(connection *conn) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Error accepting a client connection: %s", "Error accepting a client connection: %s",
connGetLastError(conn)); connGetLastError(conn));
freeClient(c); freeClientAsync(c);
return; return;
} }
@ -1146,7 +1161,7 @@ void clientAcceptHandler(connection *conn) {
/* Nothing to do, Just to avoid the warning... */ /* Nothing to do, Just to avoid the warning... */
} }
g_pserver->stat_rejected_conn++; g_pserver->stat_rejected_conn++;
freeClient(c); freeClientAsync(c);
return; return;
} }
} }
@ -1207,9 +1222,10 @@ static void acceptCommonHandler(connection *conn, int flags, char *ip, int iel)
*/ */
if (connAccept(conn, clientAcceptHandler) == C_ERR) { if (connAccept(conn, clientAcceptHandler) == C_ERR) {
char conninfo[100]; char conninfo[100];
serverLog(LL_WARNING, if (connGetState(conn) == CONN_STATE_ERROR)
"Error accepting a client connection: %s (conn: %s)", serverLog(LL_WARNING,
connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo))); "Error accepting a client connection: %s (conn: %s)",
connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
freeClient((client*)connGetPrivateData(conn)); freeClient((client*)connGetPrivateData(conn));
return; return;
} }
@ -1495,7 +1511,7 @@ bool freeClient(client *c) {
} }
/* Log link disconnection with replica */ /* Log link disconnection with replica */
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) { if (getClientType(c) == CLIENT_TYPE_SLAVE) {
serverLog(LL_WARNING,"Connection with replica %s lost.", serverLog(LL_WARNING,"Connection with replica %s lost.",
replicationGetSlaveName(c)); replicationGetSlaveName(c));
} }
@ -1546,7 +1562,7 @@ bool freeClient(client *c) {
/* We need to remember the time when we started to have zero /* We need to remember the time when we started to have zero
* attached slaves, as after some time we'll free the replication * attached slaves, as after some time we'll free the replication
* backlog. */ * backlog. */
if (c->flags & CLIENT_SLAVE && listLength(g_pserver->slaves) == 0) if (getClientType(c) == CLIENT_TYPE_SLAVE && listLength(g_pserver->slaves) == 0)
g_pserver->repl_no_slaves_since = g_pserver->unixtime; g_pserver->repl_no_slaves_since = g_pserver->unixtime;
refreshGoodSlavesCount(); refreshGoodSlavesCount();
/* Fire the replica change modules event. */ /* Fire the replica change modules event. */
@ -1696,8 +1712,8 @@ int writeToClient(client *c, int handler_installed) {
* just deliver as much data as it is possible to deliver. * just deliver as much data as it is possible to deliver.
* *
* Moreover, we also send as much as possible if the client is * Moreover, we also send as much as possible if the client is
* a replica (otherwise, on high-speed traffic, the replication * a replica or a monitor (otherwise, on high-speed traffic, the
* buffer will grow indefinitely) */ * replication/output buffer will grow indefinitely) */
if (totwritten > NET_MAX_WRITES_PER_EVENT && if (totwritten > NET_MAX_WRITES_PER_EVENT &&
(g_pserver->maxmemory == 0 || (g_pserver->maxmemory == 0 ||
zmalloc_used_memory() < g_pserver->maxmemory) && zmalloc_used_memory() < g_pserver->maxmemory) &&
@ -1847,7 +1863,7 @@ void ProcessPendingAsyncWrites()
* we can just write the replies to the client output buffer without any * we can just write the replies to the client output buffer without any
* need to use a syscall in order to install the writable event handler, * need to use a syscall in order to install the writable event handler,
* get it called, and so forth. */ * get it called, and so forth. */
int handleClientsWithPendingWrites(int iel) { int handleClientsWithPendingWrites(int iel, int aof_state) {
std::unique_lock<fastlock> lockf(g_pserver->rgthreadvar[iel].lockPendingWrite); std::unique_lock<fastlock> lockf(g_pserver->rgthreadvar[iel].lockPendingWrite);
auto &vec = g_pserver->rgthreadvar[iel].clients_pending_write; auto &vec = g_pserver->rgthreadvar[iel].clients_pending_write;
int processed = (int)vec.size(); int processed = (int)vec.size();
@ -1859,7 +1875,7 @@ int handleClientsWithPendingWrites(int iel) {
* so that in the middle of receiving the query, and serving it * so that in the middle of receiving the query, and serving it
* to the client, we'll call beforeSleep() that will do the * to the client, we'll call beforeSleep() that will do the
* actual fsync of AOF to disk. AE_BARRIER ensures that. */ * actual fsync of AOF to disk. AE_BARRIER ensures that. */
if (g_pserver->aof_state == AOF_ON && if (aof_state == AOF_ON &&
g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) g_pserver->aof_fsync == AOF_FSYNC_ALWAYS)
{ {
ae_flags |= AE_BARRIER; ae_flags |= AE_BARRIER;
@ -1924,6 +1940,12 @@ void resetClient(client *c) {
if (!(c->flags & CLIENT_MULTI) && prevcmd != askingCommand) if (!(c->flags & CLIENT_MULTI) && prevcmd != askingCommand)
c->flags &= ~CLIENT_ASKING; c->flags &= ~CLIENT_ASKING;
/* We do the same for the CACHING command as well. It also affects
* the next command or transaction executed, in a way very similar
* to ASKING. */
if (!(c->flags & CLIENT_MULTI) && prevcmd != clientCommand)
c->flags &= ~CLIENT_TRACKING_CACHING;
/* Remove the CLIENT_REPLY_SKIP flag if any so that the reply /* Remove the CLIENT_REPLY_SKIP flag if any so that the reply
* to the next command will be sent, but set the flag if the command * to the next command will be sent, but set the flag if the command
* we just processed was "CLIENT REPLY SKIP". */ * we just processed was "CLIENT REPLY SKIP". */
@ -2007,7 +2029,7 @@ int processInlineBuffer(client *c) {
/* Newline from slaves can be used to refresh the last ACK time. /* Newline from slaves can be used to refresh the last ACK time.
* This is useful for a replica to ping back while loading a big * This is useful for a replica to ping back while loading a big
* RDB file. */ * RDB file. */
if (querylen == 0 && c->flags & CLIENT_SLAVE) if (querylen == 0 && getClientType(c) == CLIENT_TYPE_SLAVE)
c->repl_ack_time = g_pserver->unixtime; c->repl_ack_time = g_pserver->unixtime;
/* Move querybuffer position to the next query in the buffer. */ /* Move querybuffer position to the next query in the buffer. */
@ -2591,7 +2613,6 @@ int clientSetNameOrReply(client *c, robj *name) {
void clientCommand(client *c) { void clientCommand(client *c) {
listNode *ln; listNode *ln;
listIter li; listIter li;
client *client;
if (c->argc == 2 && !strcasecmp((const char*)ptrFromObj(c->argv[1]),"help")) { if (c->argc == 2 && !strcasecmp((const char*)ptrFromObj(c->argv[1]),"help")) {
const char *help[] = { const char *help[] = {
@ -2608,7 +2629,7 @@ void clientCommand(client *c) {
"REPLY (on|off|skip) -- Control the replies sent to the current connection.", "REPLY (on|off|skip) -- Control the replies sent to the current connection.",
"SETNAME <name> -- Assign the name <name> to the current connection.", "SETNAME <name> -- Assign the name <name> to the current connection.",
"UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.", "UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
"TRACKING (on|off) [REDIRECT <id>] -- Enable client keys tracking for client side caching.", "TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first] [PREFIX second] [OPTIN] [OPTOUT]... -- Enable client keys tracking for client side caching.",
"GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.", "GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.",
NULL NULL
}; };
@ -2705,7 +2726,7 @@ NULL
/* Iterate clients killing all the matching clients. */ /* Iterate clients killing all the matching clients. */
listRewind(g_pserver->clients,&li); listRewind(g_pserver->clients,&li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
client = (struct client*)listNodeValue(ln); client *client = (struct client*)listNodeValue(ln);
if (addr && strcmp(getClientPeerId(client),addr) != 0) continue; if (addr && strcmp(getClientPeerId(client),addr) != 0) continue;
if (type != -1 && getClientType(client) != type) continue; if (type != -1 && getClientType(client) != type) continue;
if (id != 0 && client->id != id) continue; if (id != 0 && client->id != id) continue;
@ -2794,38 +2815,131 @@ NULL
UNIT_MILLISECONDS) != C_OK) return; UNIT_MILLISECONDS) != C_OK) return;
pauseClients(duration); pauseClients(duration);
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"tracking") && } else if (!strcasecmp(szFromObj(c->argv[1]),"tracking") && c->argc >= 3) {
(c->argc == 3 || c->argc == 5)) /* CLIENT TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first]
{ * [PREFIX second] [OPTIN] [OPTOUT] ... */
/* CLIENT TRACKING (on|off) [REDIRECT <id>] */
long long redir = 0; long long redir = 0;
uint64_t options = 0;
robj **prefix = NULL;
size_t numprefix = 0;
/* Parse the redirection option: we'll require the client with /* Parse the options. */
* the specified ID to exist right now, even if it is possible for (int j = 3; j < c->argc; j++) {
* it will get disconnected later. */ int moreargs = (c->argc-1) - j;
if (c->argc == 5) {
if (strcasecmp(szFromObj(c->argv[3]),"redirect") != 0) { if (!strcasecmp(szFromObj(c->argv[j]),"redirect") && moreargs) {
addReply(c,shared.syntaxerr); j++;
return; if (redir != 0) {
} else { addReplyError(c,"A client can only redirect to a single "
if (getLongLongFromObjectOrReply(c,c->argv[4],&redir,NULL) != "other client");
C_OK) return; zfree(prefix);
return;
}
if (getLongLongFromObjectOrReply(c,c->argv[j],&redir,NULL) !=
C_OK)
{
zfree(prefix);
return;
}
/* We will require the client with the specified ID to exist
* right now, even if it is possible that it gets disconnected
* later. Still a valid sanity check. */
if (lookupClientByID(redir) == NULL) { if (lookupClientByID(redir) == NULL) {
addReplyError(c,"The client ID you want redirect to " addReplyError(c,"The client ID you want redirect to "
"does not exist"); "does not exist");
zfree(prefix);
return; return;
} }
} else if (!strcasecmp(szFromObj(c->argv[j]),"bcast")) {
options |= CLIENT_TRACKING_BCAST;
} else if (!strcasecmp(szFromObj(c->argv[j]),"optin")) {
options |= CLIENT_TRACKING_OPTIN;
} else if (!strcasecmp(szFromObj(c->argv[j]),"optout")) {
options |= CLIENT_TRACKING_OPTOUT;
} else if (!strcasecmp(szFromObj(c->argv[j]),"prefix") && moreargs) {
j++;
prefix = (robj**)zrealloc(prefix,sizeof(robj*)*(numprefix+1), MALLOC_LOCAL);
prefix[numprefix++] = c->argv[j];
} else {
zfree(prefix);
addReply(c,shared.syntaxerr);
return;
} }
} }
/* Options are ok: enable or disable the tracking for this client. */
if (!strcasecmp(szFromObj(c->argv[2]),"on")) { if (!strcasecmp(szFromObj(c->argv[2]),"on")) {
enableTracking(c,redir); /* Before enabling tracking, make sure options are compatible
* among each other and with the current state of the client. */
if (!(options & CLIENT_TRACKING_BCAST) && numprefix) {
addReplyError(c,
"PREFIX option requires BCAST mode to be enabled");
zfree(prefix);
return;
}
if (c->flags & CLIENT_TRACKING) {
int oldbcast = !!(c->flags & CLIENT_TRACKING_BCAST);
int newbcast = !!(options & CLIENT_TRACKING_BCAST);
if (oldbcast != newbcast) {
addReplyError(c,
"You can't switch BCAST mode on/off before disabling "
"tracking for this client, and then re-enabling it with "
"a different mode.");
zfree(prefix);
return;
}
}
if (options & CLIENT_TRACKING_BCAST &&
options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT))
{
addReplyError(c,
"OPTIN and OPTOUT are not compatible with BCAST");
zfree(prefix);
return;
}
enableTracking(c,redir,options,prefix,numprefix);
} else if (!strcasecmp(szFromObj(c->argv[2]),"off")) { } else if (!strcasecmp(szFromObj(c->argv[2]),"off")) {
disableTracking(c); disableTracking(c);
} else {
zfree(prefix);
addReply(c,shared.syntaxerr);
return;
}
zfree(prefix);
addReply(c,shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"caching") && c->argc >= 3) {
if (!(c->flags & CLIENT_TRACKING)) {
addReplyError(c,"CLIENT CACHING can be called only when the "
"client is in tracking mode with OPTIN or "
"OPTOUT mode enabled");
return;
}
char *opt = szFromObj(c->argv[2]);
if (!strcasecmp(opt,"yes")) {
if (c->flags & CLIENT_TRACKING_OPTIN) {
c->flags |= CLIENT_TRACKING_CACHING;
} else {
addReplyError(c,"CLIENT CACHING YES is only valid when tracking is enabled in OPTIN mode.");
return;
}
} else if (!strcasecmp(opt,"no")) {
if (c->flags & CLIENT_TRACKING_OPTOUT) {
c->flags |= CLIENT_TRACKING_CACHING;
} else {
addReplyError(c,"CLIENT CACHING NO is only valid when tracking is enabled in OPTOUT mode.");
return;
}
} else { } else {
addReply(c,shared.syntaxerr); addReply(c,shared.syntaxerr);
return; return;
} }
/* Common reply for when we succeeded. */
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"getredir") && c->argc == 2) { } else if (!strcasecmp(szFromObj(c->argv[1]),"getredir") && c->argc == 2) {
/* CLIENT GETREDIR */ /* CLIENT GETREDIR */
@ -3016,12 +3130,14 @@ unsigned long getClientOutputBufferMemoryUsage(client *c) {
* *
* The function will return one of the following: * The function will return one of the following:
* CLIENT_TYPE_NORMAL -> Normal client * CLIENT_TYPE_NORMAL -> Normal client
* CLIENT_TYPE_SLAVE -> Slave or client executing MONITOR command * CLIENT_TYPE_SLAVE -> Slave
* CLIENT_TYPE_PUBSUB -> Client subscribed to Pub/Sub channels * CLIENT_TYPE_PUBSUB -> Client subscribed to Pub/Sub channels
* CLIENT_TYPE_MASTER -> The client representing our replication master. * CLIENT_TYPE_MASTER -> The client representing our replication master.
*/ */
int getClientType(client *c) { int getClientType(client *c) {
if (c->flags & CLIENT_MASTER) return CLIENT_TYPE_MASTER; if (c->flags & CLIENT_MASTER) return CLIENT_TYPE_MASTER;
/* Even though MONITOR clients are marked as replicas, we
* want the expose them as normal clients. */
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR))
return CLIENT_TYPE_SLAVE; return CLIENT_TYPE_SLAVE;
if (c->flags & CLIENT_PUBSUB) return CLIENT_TYPE_PUBSUB; if (c->flags & CLIENT_PUBSUB) return CLIENT_TYPE_PUBSUB;
@ -3251,6 +3367,7 @@ int processEventsWhileBlocked(int iel) {
} }
int aof_state = g_pserver->aof_state;
aeReleaseLock(); aeReleaseLock();
serverAssertDebug(!GlobalLocksAcquired()); serverAssertDebug(!GlobalLocksAcquired());
try try
@ -3258,7 +3375,7 @@ int processEventsWhileBlocked(int iel) {
while (iterations--) { while (iterations--) {
int events = 0; int events = 0;
events += aeProcessEvents(g_pserver->rgthreadvar[iel].el, AE_FILE_EVENTS|AE_DONT_WAIT); events += aeProcessEvents(g_pserver->rgthreadvar[iel].el, AE_FILE_EVENTS|AE_DONT_WAIT);
events += handleClientsWithPendingWrites(iel); events += handleClientsWithPendingWrites(iel, aof_state);
if (!events) break; if (!events) break;
count += events; count += events;
} }

View File

@ -82,10 +82,10 @@ 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_KEY_MISS) res = sdscatlen(res,"m",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);
if (flags & NOTIFY_KEY_MISS) res = sdscatlen(res,"m",1);
return res; return res;
} }

View File

@ -32,6 +32,7 @@
#include "cron.h" #include "cron.h"
#include <math.h> #include <math.h>
#include <ctype.h> #include <ctype.h>
#include <mutex>
#ifdef __CYGWIN__ #ifdef __CYGWIN__
#define strtold(a,b) ((long double)strtod((a),(b))) #define strtold(a,b) ((long double)strtod((a),(b)))
@ -660,21 +661,13 @@ int getDoubleFromObjectOrReply(client *c, robj *o, double *target, const char *m
int getLongDoubleFromObject(robj *o, long double *target) { int getLongDoubleFromObject(robj *o, long double *target) {
long double value; long double value;
char *eptr;
if (o == NULL) { if (o == NULL) {
value = 0; value = 0;
} else { } else {
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING); serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
if (sdsEncodedObject(o)) { if (sdsEncodedObject(o)) {
errno = 0; if (!string2ld(szFromObj(o), sdslen(szFromObj(o)), &value))
value = strtold(szFromObj(o), &eptr);
if (sdslen(szFromObj(o)) == 0 ||
isspace(((const char*)szFromObj(o))[0]) ||
(size_t)(eptr-(char*)szFromObj(o)) != sdslen(szFromObj(o)) ||
(errno == ERANGE &&
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
std::isnan(value))
return C_ERR; return C_ERR;
} else if (o->encoding == OBJ_ENCODING_INT) { } else if (o->encoding == OBJ_ENCODING_INT) {
value = (long)szFromObj(o); value = (long)szFromObj(o);
@ -1031,40 +1024,31 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mh->repl_backlog = mem; mh->repl_backlog = mem;
mem_total += mem; mem_total += mem;
mem = 0;
if (listLength(g_pserver->slaves)) {
listIter li;
listNode *ln;
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *c = (client*)listNodeValue(ln);
if (c->flags & CLIENT_CLOSE_ASAP)
continue;
mem += getClientOutputBufferMemoryUsage(c);
mem += sdsAllocSize(c->querybuf);
mem += sizeof(client);
}
}
mh->clients_slaves = mem;
mem_total+=mem;
mem = 0; mem = 0;
if (listLength(g_pserver->clients)) { if (listLength(g_pserver->clients)) {
listIter li; listIter li;
listNode *ln; listNode *ln;
size_t mem_normal = 0, mem_slaves = 0;
listRewind(g_pserver->clients,&li); listRewind(g_pserver->clients,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
size_t mem_curr = 0;
client *c = (client*)listNodeValue(ln); client *c = (client*)listNodeValue(ln);
if (c->flags & CLIENT_SLAVE && !(c->flags & CLIENT_MONITOR)) std::unique_lock<fastlock> ul(c->lock);
continue;
mem += getClientOutputBufferMemoryUsage(c); int type = getClientType(c);
mem += sdsAllocSize(c->querybuf); mem_curr += getClientOutputBufferMemoryUsage(c);
mem += sizeof(client); mem_curr += sdsAllocSize(c->querybuf);
mem_curr += sizeof(client);
if (type == CLIENT_TYPE_SLAVE)
mem_slaves += mem_curr;
else
mem_normal += mem_curr;
} }
mh->clients_slaves = mem_slaves;
mh->clients_normal = mem_normal;
mem = mem_slaves + mem_normal;
} }
mh->clients_normal = mem;
mem_total+=mem; mem_total+=mem;
mem = 0; mem = 0;
@ -1170,13 +1154,13 @@ sds getMemoryDoctorReport(void) {
num_reports++; num_reports++;
} }
/* Allocator fss is higher than 1.1 and 10MB ? */ /* Allocator rss is higher than 1.1 and 10MB ? */
if (mh->allocator_rss > 1.1 && mh->allocator_rss_bytes > 10<<20) { if (mh->allocator_rss > 1.1 && mh->allocator_rss_bytes > 10<<20) {
high_alloc_rss = 1; high_alloc_rss = 1;
num_reports++; num_reports++;
} }
/* Non-Allocator fss is higher than 1.1 and 10MB ? */ /* Non-Allocator rss is higher than 1.1 and 10MB ? */
if (mh->rss_extra > 1.1 && mh->rss_extra_bytes > 10<<20) { if (mh->rss_extra > 1.1 && mh->rss_extra_bytes > 10<<20) {
high_proc_rss = 1; high_proc_rss = 1;
num_reports++; num_reports++;

View File

@ -28,6 +28,7 @@
*/ */
#include "server.h" #include "server.h"
#include <mutex>
int clientSubscriptionsCount(client *c); int clientSubscriptionsCount(client *c);
@ -35,7 +36,11 @@ int clientSubscriptionsCount(client *c);
* Pubsub client replies API * Pubsub client replies API
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
/* Send a pubsub message of type "message" to the client. */ /* Send a pubsub message of type "message" to the client.
* Normally 'msg' is a Redis object containing the string to send as
* message. However if the caller sets 'msg' as NULL, it will be able
* to send a special message (for instance an Array type) by using the
* addReply*() API family. */
void addReplyPubsubMessage(client *c, robj *channel, robj *msg) { void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
if (c->resp == 2) if (c->resp == 2)
addReplyAsync(c,shared.mbulkhdr[3]); addReplyAsync(c,shared.mbulkhdr[3]);
@ -43,7 +48,7 @@ void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
addReplyPushLenAsync(c,3); addReplyPushLenAsync(c,3);
addReplyAsync(c,shared.messagebulk); addReplyAsync(c,shared.messagebulk);
addReplyBulkAsync(c,channel); addReplyBulkAsync(c,channel);
addReplyBulkAsync(c,msg); if (msg) addReplyBulkAsync(c,msg);
} }
/* Send a pubsub message of type "pmessage" to the client. The difference /* Send a pubsub message of type "pmessage" to the client. The difference
@ -205,6 +210,8 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */ /* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
int pubsubSubscribePattern(client *c, robj *pattern) { int pubsubSubscribePattern(client *c, robj *pattern) {
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
dictEntry *de;
list *clients;
int retval = 0; int retval = 0;
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) { if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
@ -216,6 +223,16 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
pat->pattern = getDecodedObject(pattern); pat->pattern = getDecodedObject(pattern);
pat->pclient = c; pat->pclient = c;
listAddNodeTail(g_pserver->pubsub_patterns,pat); listAddNodeTail(g_pserver->pubsub_patterns,pat);
/* Add the client to the pattern -> list of clients hash table */
de = dictFind(g_pserver->pubsub_patterns_dict,pattern);
if (de == NULL) {
clients = listCreate();
dictAdd(g_pserver->pubsub_patterns_dict,pattern,clients);
incrRefCount(pattern);
} else {
clients = (list*)dictGetVal(de);
}
listAddNodeTail(clients,c);
} }
/* Notify the client */ /* Notify the client */
addReplyPubsubPatSubscribed(c,pattern); addReplyPubsubPatSubscribed(c,pattern);
@ -225,6 +242,8 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or /* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
* 0 if the client was not subscribed to the specified channel. */ * 0 if the client was not subscribed to the specified channel. */
int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) { int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
dictEntry *de;
list *clients;
listNode *ln; listNode *ln;
pubsubPattern pat; pubsubPattern pat;
int retval = 0; int retval = 0;
@ -237,6 +256,18 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
pat.pattern = pattern; pat.pattern = pattern;
ln = listSearchKey(g_pserver->pubsub_patterns,&pat); ln = listSearchKey(g_pserver->pubsub_patterns,&pat);
listDelNode(g_pserver->pubsub_patterns,ln); listDelNode(g_pserver->pubsub_patterns,ln);
/* Remove the client from the pattern -> clients list hash table */
de = dictFind(g_pserver->pubsub_patterns_dict,pattern);
serverAssertWithInfo(c,NULL,de != NULL);
clients = (list*)dictGetVal(de);
ln = listSearchKey(clients,c);
serverAssertWithInfo(c,NULL,ln != NULL);
listDelNode(clients,ln);
if (listLength(clients) == 0) {
/* Free the list and associated hash entry at all if this was
* the latest client. */
dictDelete(g_pserver->pubsub_patterns_dict,pattern);
}
} }
/* Notify the client */ /* Notify the client */
if (notify) addReplyPubsubPatUnsubscribed(c,pattern); if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
@ -286,6 +317,7 @@ int pubsubPublishMessage(robj *channel, robj *message) {
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
int receivers = 0; int receivers = 0;
dictEntry *de; dictEntry *de;
dictIterator *di;
listNode *ln; listNode *ln;
listIter li; listIter li;
@ -310,29 +342,31 @@ int pubsubPublishMessage(robj *channel, robj *message) {
} }
} }
/* Send to clients listening to matching channels */ /* Send to clients listening to matching channels */
if (listLength(g_pserver->pubsub_patterns)) { di = dictGetIterator(g_pserver->pubsub_patterns_dict);
listRewind(g_pserver->pubsub_patterns,&li); if (di) {
channel = getDecodedObject(channel); channel = getDecodedObject(channel);
while ((ln = listNext(&li)) != NULL) { while((de = dictNext(di)) != NULL) {
pubsubPattern *pat = (pubsubPattern*)ln->value; robj *pattern = (robj*)dictGetKey(de);
list *clients = (list*)dictGetVal(de);
if (!stringmatchlen(szFromObj(pattern),
sdslen(szFromObj(pattern)),
szFromObj(channel),
sdslen(szFromObj(channel)),0)) continue;
if (stringmatchlen((char*)ptrFromObj(pat->pattern), listRewind(clients,&li);
sdslen(szFromObj(pat->pattern)), while ((ln = listNext(&li)) != NULL) {
(char*)ptrFromObj(channel), client *c = (client*)listNodeValue(ln);
sdslen(szFromObj(channel)),0)) if (c->flags & CLIENT_CLOSE_ASAP)
{
if (pat->pclient->flags & CLIENT_CLOSE_ASAP)
continue; continue;
if (FCorrectThread(pat->pclient)) std::unique_lock<fastlock> l(c->lock, std::defer_lock);
fastlock_lock(&pat->pclient->lock); if (FCorrectThread(c))
addReplyPubsubPatMessage(pat->pclient, l.lock();
pat->pattern,channel,message); addReplyPubsubPatMessage(c,pattern,channel,message);
if (FCorrectThread(pat->pclient))
fastlock_unlock(&pat->pclient->lock);
receivers++; receivers++;
} }
} }
decrRefCount(channel); decrRefCount(channel);
dictReleaseIterator(di);
} }
return receivers; return receivers;
} }

View File

@ -70,6 +70,12 @@ static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
} while (0); } while (0);
#endif #endif
/* Bookmarks forward declarations */
#define QL_MAX_BM ((1 << QL_BM_BITS)-1)
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name);
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node);
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm);
/* Simple way to give quicklistEntry structs default values with one call. */ /* Simple way to give quicklistEntry structs default values with one call. */
#define initEntry(e) \ #define initEntry(e) \
do { \ do { \
@ -100,10 +106,11 @@ quicklist *quicklistCreate(void) {
quicklist->count = 0; quicklist->count = 0;
quicklist->compress = 0; quicklist->compress = 0;
quicklist->fill = -2; quicklist->fill = -2;
quicklist->bookmark_count = 0;
return quicklist; return quicklist;
} }
#define COMPRESS_MAX (1 << 16) #define COMPRESS_MAX (1 << QL_COMP_BITS)
void quicklistSetCompressDepth(quicklist *quicklist, int compress) { void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
if (compress > COMPRESS_MAX) { if (compress > COMPRESS_MAX) {
compress = COMPRESS_MAX; compress = COMPRESS_MAX;
@ -113,7 +120,7 @@ void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
quicklist->compress = compress; quicklist->compress = compress;
} }
#define FILL_MAX (1 << 15) #define FILL_MAX (1 << (QL_FILL_BITS-1))
void quicklistSetFill(quicklist *quicklist, int fill) { void quicklistSetFill(quicklist *quicklist, int fill) {
if (fill > FILL_MAX) { if (fill > FILL_MAX) {
fill = FILL_MAX; fill = FILL_MAX;
@ -169,6 +176,7 @@ void quicklistRelease(quicklist *quicklist) {
quicklist->len--; quicklist->len--;
current = next; current = next;
} }
quicklistBookmarksClear(quicklist);
zfree(quicklist); zfree(quicklist);
} }
@ -578,6 +586,15 @@ quicklist *quicklistCreateFromZiplist(int fill, int compress,
REDIS_STATIC void __quicklistDelNode(quicklist *quicklist, REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
quicklistNode *node) { quicklistNode *node) {
/* Update the bookmark if any */
quicklistBookmark *bm = _quicklistBookmarkFindByNode(quicklist, node);
if (bm) {
bm->node = node->next;
/* if the bookmark was to the last node, delete it. */
if (!bm->node)
_quicklistBookmarkDelete(quicklist, bm);
}
if (node->next) if (node->next)
node->next->prev = node->prev; node->next->prev = node->prev;
if (node->prev) if (node->prev)
@ -1410,6 +1427,87 @@ void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
} }
} }
/* Create or update a bookmark in the list which will be updated to the next node
* automatically when the one referenced gets deleted.
* Returns 1 on success (creation of new bookmark or override of an existing one).
* Returns 0 on failure (reached the maximum supported number of bookmarks).
* NOTE: use short simple names, so that string compare on find is quick.
* NOTE: bookmakrk creation may re-allocate the quicklist, so the input pointer
may change and it's the caller responsibilty to update the reference.
*/
int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node) {
quicklist *ql = *ql_ref;
if (ql->bookmark_count >= QL_MAX_BM)
return 0;
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
if (bm) {
bm->node = node;
return 1;
}
ql = zrealloc(ql, sizeof(quicklist) + (ql->bookmark_count+1) * sizeof(quicklistBookmark), MALLOC_SHARED);
*ql_ref = ql;
ql->bookmarks[ql->bookmark_count].node = node;
ql->bookmarks[ql->bookmark_count].name = zstrdup(name);
ql->bookmark_count++;
return 1;
}
/* Find the quicklist node referenced by a named bookmark.
* When the bookmarked node is deleted the bookmark is updated to the next node,
* and if that's the last node, the bookmark is deleted (so find returns NULL). */
quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name) {
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
if (!bm) return NULL;
return bm->node;
}
/* Delete a named bookmark.
* returns 0 if bookmark was not found, and 1 if deleted.
* Note that the bookmark memory is not freed yet, and is kept for future use. */
int quicklistBookmarkDelete(quicklist *ql, const char *name) {
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
if (!bm)
return 0;
_quicklistBookmarkDelete(ql, bm);
return 1;
}
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name) {
unsigned i;
for (i=0; i<ql->bookmark_count; i++) {
if (!strcmp(ql->bookmarks[i].name, name)) {
return &ql->bookmarks[i];
}
}
return NULL;
}
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node) {
unsigned i;
for (i=0; i<ql->bookmark_count; i++) {
if (ql->bookmarks[i].node == node) {
return &ql->bookmarks[i];
}
}
return NULL;
}
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm) {
int index = bm - ql->bookmarks;
zfree(bm->name);
ql->bookmark_count--;
memmove(bm, bm+1, (ql->bookmark_count - index)* sizeof(*bm));
/* NOTE: We do not shrink (realloc) the quicklist yet (to avoid resonance,
* it may be re-used later (a call to realloc may NOP). */
}
void quicklistBookmarksClear(quicklist *ql) {
while (ql->bookmark_count)
zfree(ql->bookmarks[--ql->bookmark_count].name);
/* NOTE: We do not shrink (realloc) the quick list. main use case for this
* function is just before releasing the allocation. */
}
/* The rest of this file is test cases and test helpers. */ /* The rest of this file is test cases and test helpers. */
#ifdef REDIS_TEST #ifdef REDIS_TEST
#include <stdint.h> #include <stdint.h>
@ -2641,6 +2739,54 @@ int quicklistTest(int argc, char *argv[]) {
printf("Compressions: %0.2f seconds.\n", (float)(stop - start) / 1000); printf("Compressions: %0.2f seconds.\n", (float)(stop - start) / 1000);
printf("\n"); printf("\n");
TEST("bookmark get updated to next item") {
quicklist *ql = quicklistNew(1, 0);
quicklistPushTail(ql, "1", 1);
quicklistPushTail(ql, "2", 1);
quicklistPushTail(ql, "3", 1);
quicklistPushTail(ql, "4", 1);
quicklistPushTail(ql, "5", 1);
assert(ql->len==5);
/* add two bookmarks, one pointing to the node before the last. */
assert(quicklistBookmarkCreate(&ql, "_dummy", ql->head->next));
assert(quicklistBookmarkCreate(&ql, "_test", ql->tail->prev));
/* test that the bookmark returns the right node, delete it and see that the bookmark points to the last node */
assert(quicklistBookmarkFind(ql, "_test") == ql->tail->prev);
assert(quicklistDelRange(ql, -2, 1));
assert(quicklistBookmarkFind(ql, "_test") == ql->tail);
/* delete the last node, and see that the bookmark was deleted. */
assert(quicklistDelRange(ql, -1, 1));
assert(quicklistBookmarkFind(ql, "_test") == NULL);
/* test that other bookmarks aren't affected */
assert(quicklistBookmarkFind(ql, "_dummy") == ql->head->next);
assert(quicklistBookmarkFind(ql, "_missing") == NULL);
assert(ql->len==3);
quicklistBookmarksClear(ql); /* for coverage */
assert(quicklistBookmarkFind(ql, "_dummy") == NULL);
quicklistRelease(ql);
}
TEST("bookmark limit") {
int i;
quicklist *ql = quicklistNew(1, 0);
quicklistPushHead(ql, "1", 1);
for (i=0; i<QL_MAX_BM; i++)
assert(quicklistBookmarkCreate(&ql, genstr("",i), ql->head));
/* when all bookmarks are used, creation fails */
assert(!quicklistBookmarkCreate(&ql, "_test", ql->head));
/* delete one and see that we can now create another */
assert(quicklistBookmarkDelete(ql, "0"));
assert(quicklistBookmarkCreate(&ql, "_test", ql->head));
/* delete one and see that the rest survive */
assert(quicklistBookmarkDelete(ql, "_test"));
for (i=1; i<QL_MAX_BM; i++)
assert(quicklistBookmarkFind(ql, genstr("",i)) == ql->head);
/* make sure the deleted ones are indeed gone */
assert(!quicklistBookmarkFind(ql, "0"));
assert(!quicklistBookmarkFind(ql, "_test"));
quicklistRelease(ql);
}
if (!err) if (!err)
printf("ALL TESTS PASSED!\n"); printf("ALL TESTS PASSED!\n");
else else

View File

@ -28,6 +28,8 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <stdint.h> // for UINTPTR_MAX
#ifndef __QUICKLIST_H__ #ifndef __QUICKLIST_H__
#define __QUICKLIST_H__ #define __QUICKLIST_H__
@ -72,19 +74,53 @@ typedef struct quicklistLZF {
#endif #endif
} quicklistLZF; } quicklistLZF;
/* Bookmarks are padded with realloc at the end of of the quicklist struct.
* They should only be used for very big lists if thousands of nodes were the
* excess memory usage is negligible, and there's a real need to iterate on them
* in portions.
* When not used, they don't add any memory overhead, but when used and then
* deleted, some overhead remains (to avoid resonance).
* The number of bookmarks used should be kept to minimum since it also adds
* overhead on node deletion (searching for a bookmark to update). */
typedef struct quicklistBookmark {
quicklistNode *node;
char *name;
} quicklistBookmark;
#if UINTPTR_MAX == 0xffffffff
/* 32-bit */
# define QL_FILL_BITS 14
# define QL_COMP_BITS 14
# define QL_BM_BITS 4
#elif UINTPTR_MAX == 0xffffffffffffffff
/* 64-bit */
# define QL_FILL_BITS 16
# define QL_COMP_BITS 16
# define QL_BM_BITS 4 /* we can encode more, but we rather limit the user
since they cause performance degradation. */
#else
# error unknown arch bits count
#endif
/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist. /* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
* 'count' is the number of total entries. * 'count' is the number of total entries.
* 'len' is the number of quicklist nodes. * 'len' is the number of quicklist nodes.
* 'compress' is: -1 if compression disabled, otherwise it's the number * 'compress' is: -1 if compression disabled, otherwise it's the number
* of quicklistNodes to leave uncompressed at ends of quicklist. * of quicklistNodes to leave uncompressed at ends of quicklist.
* 'fill' is the user-requested (or default) fill factor. */ * 'fill' is the user-requested (or default) fill factor.
* 'bookmakrs are an optional feature that is used by realloc this struct,
* so that they don't consume memory when not used. */
typedef struct quicklist { typedef struct quicklist {
quicklistNode *head; quicklistNode *head;
quicklistNode *tail; quicklistNode *tail;
unsigned long count; /* total count of all entries in all ziplists */ unsigned long count; /* total count of all entries in all ziplists */
unsigned long len; /* number of quicklistNodes */ unsigned long len; /* number of quicklistNodes */
int fill : 16; /* fill factor for individual nodes */ int fill : QL_FILL_BITS; /* fill factor for individual nodes */
unsigned int compress : 16; /* depth of end nodes not to compress;0=off */ unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
unsigned int bookmark_count: QL_BM_BITS;
#ifndef __cplusplus
quicklistBookmark bookmarks[];
#endif
} quicklist; } quicklist;
typedef struct quicklistIter { typedef struct quicklistIter {
@ -170,6 +206,12 @@ unsigned long quicklistCount(const quicklist *ql);
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len); int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
size_t quicklistGetLzf(const quicklistNode *node, void **data); size_t quicklistGetLzf(const quicklistNode *node, void **data);
/* bookmarks */
int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node);
int quicklistBookmarkDelete(quicklist *ql, const char *name);
quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name);
void quicklistBookmarksClear(quicklist *ql);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int quicklistTest(int argc, char *argv[]); int quicklistTest(int argc, char *argv[]);
#endif #endif

View File

@ -1766,6 +1766,7 @@ int raxRandomWalk(raxIterator *it, size_t steps) {
if (n->iskey) steps--; if (n->iskey) steps--;
} }
it->node = n; it->node = n;
it->data = raxGetData(it->node);
return 1; return 1;
} }

View File

@ -1530,6 +1530,7 @@ int rdbSaveBackground(rdbSaveInfo *rsi) {
latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000); latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000);
serverLog(LL_NOTICE,"Background saving started"); serverLog(LL_NOTICE,"Background saving started");
g_pserver->rdb_save_time_start = time(NULL); g_pserver->rdb_save_time_start = time(NULL);
serverAssert(!g_pserver->rdbThreadVars.fRdbThreadActive);
g_pserver->rdbThreadVars.fRdbThreadActive = true; g_pserver->rdbThreadVars.fRdbThreadActive = true;
g_pserver->rdbThreadVars.rdb_child_thread = child; g_pserver->rdbThreadVars.rdb_child_thread = child;
g_pserver->rdb_child_type = RDB_CHILD_TYPE_DISK; g_pserver->rdb_child_type = RDB_CHILD_TYPE_DISK;
@ -2054,7 +2055,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
} }
} else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) { } else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) {
uint64_t moduleid = rdbLoadLen(rdb,NULL); uint64_t moduleid = rdbLoadLen(rdb,NULL);
if (rioGetReadError(rdb)) return NULL; if (rioGetReadError(rdb)) {
rdbReportReadError("Short read module id");
return NULL;
}
moduleType *mt = moduleTypeLookupModuleByID(moduleid); moduleType *mt = moduleTypeLookupModuleByID(moduleid);
char name[10]; char name[10];
@ -2422,7 +2426,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
io.ver = 2; io.ver = 2;
/* Call the rdb_load method of the module providing the 10 bit /* Call the rdb_load method of the module providing the 10 bit
* encoding version in the lower 10 bits of the module ID. */ * encoding version in the lower 10 bits of the module ID. */
if (mt->aux_load(&io,moduleid&1023, when) || io.error) { if (mt->aux_load(&io,moduleid&1023, when) != REDISMODULE_OK || io.error) {
moduleTypeNameByID(name,moduleid); moduleTypeNameByID(name,moduleid);
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name); serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
exit(1); exit(1);
@ -2466,7 +2470,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
* received from the master. In the latter case, the master is * received from the master. In the latter case, the master is
* responsible for key expiry. If we would expire keys here, the * responsible for key expiry. If we would expire keys here, the
* snapshot taken by the master may not be reflected on the replica. */ * snapshot taken by the master may not be reflected on the replica. */
bool fExpiredKey = (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica) && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now; bool fExpiredKey = iAmMaster() && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now;
if (fStaleMvccKey || fExpiredKey) { if (fStaleMvccKey || fExpiredKey) {
if (fStaleMvccKey && !fExpiredKey && rsi != nullptr && rsi->mi != nullptr && rsi->mi->staleKeyMap != nullptr && lookupKeyRead(db, key) == nullptr) { if (fStaleMvccKey && !fExpiredKey && rsi != nullptr && rsi->mi != nullptr && rsi->mi->staleKeyMap != nullptr && lookupKeyRead(db, key) == nullptr) {
// We have a key that we've already deleted and is not back in our database. // We have a key that we've already deleted and is not back in our database.
@ -2697,7 +2701,7 @@ void killRDBChild(bool fSynchronous) {
aeReleaseLock(); aeReleaseLock();
serverAssert(!GlobalLocksAcquired()); serverAssert(!GlobalLocksAcquired());
void *result; void *result;
pthread_join(g_pserver->rdbThreadVars.rdb_child_thread, &result); int err = pthread_join(g_pserver->rdbThreadVars.rdb_child_thread, &result);
g_pserver->rdbThreadVars.fRdbThreadCancel = false; g_pserver->rdbThreadVars.fRdbThreadCancel = false;
aeAcquireLock(); aeAcquireLock();
} }
@ -2823,6 +2827,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
serverLog(LL_NOTICE,"Background RDB transfer started"); serverLog(LL_NOTICE,"Background RDB transfer started");
g_pserver->rdb_save_time_start = time(NULL); g_pserver->rdb_save_time_start = time(NULL);
serverAssert(!g_pserver->rdbThreadVars.fRdbThreadActive);
g_pserver->rdbThreadVars.rdb_child_thread = child; g_pserver->rdbThreadVars.rdb_child_thread = child;
g_pserver->rdbThreadVars.fRdbThreadActive = true; g_pserver->rdbThreadVars.fRdbThreadActive = true;
g_pserver->rdb_child_type = RDB_CHILD_TYPE_SOCKET; g_pserver->rdb_child_type = RDB_CHILD_TYPE_SOCKET;

View File

@ -205,7 +205,7 @@ int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
} }
} }
} }
//if (offending_len != NULL) *offending_len = offending_p - *offending; if (offending_len != NULL) *offending_len = offending_p - *offending;
dictReleaseIterator(iter); dictReleaseIterator(iter);
dictRelease(related); dictRelease(related);
} }

View File

@ -51,6 +51,7 @@
#include <hiredis.h> #include <hiredis.h>
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/err.h>
#include <hiredis_ssl.h> #include <hiredis_ssl.h>
#endif #endif
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */ #include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
@ -1102,7 +1103,11 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
(argc == 3 && !strcasecmp(command,"latency") && (argc == 3 && !strcasecmp(command,"latency") &&
!strcasecmp(argv[1],"graph")) || !strcasecmp(argv[1],"graph")) ||
(argc == 2 && !strcasecmp(command,"latency") && (argc == 2 && !strcasecmp(command,"latency") &&
!strcasecmp(argv[1],"doctor"))) !strcasecmp(argv[1],"doctor")) ||
/* Format PROXY INFO command for Redis Cluster Proxy:
* https://github.com/artix75/redis-cluster-proxy */
(argc >= 2 && !strcasecmp(command,"proxy") &&
!strcasecmp(argv[1],"info")))
{ {
output_raw = 1; output_raw = 1;
} }
@ -1261,6 +1266,8 @@ static int parseOptions(int argc, char **argv) {
config.dbnum = atoi(argv[++i]); config.dbnum = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--no-auth-warning")) { } else if (!strcmp(argv[i], "--no-auth-warning")) {
config.no_auth_warning = 1; config.no_auth_warning = 1;
} else if (!strcmp(argv[i], "--askpass")) {
config.askpass = 1;
} else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass")) } else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
&& !lastarg) && !lastarg)
{ {
@ -1406,15 +1413,15 @@ static int parseOptions(int argc, char **argv) {
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
} else if (!strcmp(argv[i],"--tls")) { } else if (!strcmp(argv[i],"--tls")) {
config.tls = 1; config.tls = 1;
} else if (!strcmp(argv[i],"--sni")) { } else if (!strcmp(argv[i],"--sni") && !lastarg) {
config.sni = argv[++i]; config.sni = argv[++i];
} else if (!strcmp(argv[i],"--cacertdir")) { } else if (!strcmp(argv[i],"--cacertdir") && !lastarg) {
config.cacertdir = argv[++i]; config.cacertdir = argv[++i];
} else if (!strcmp(argv[i],"--cacert")) { } else if (!strcmp(argv[i],"--cacert") && !lastarg) {
config.cacert = argv[++i]; config.cacert = argv[++i];
} else if (!strcmp(argv[i],"--cert")) { } else if (!strcmp(argv[i],"--cert") && !lastarg) {
config.cert = argv[++i]; config.cert = argv[++i];
} else if (!strcmp(argv[i],"--key")) { } else if (!strcmp(argv[i],"--key") && !lastarg) {
config.key = argv[++i]; config.key = argv[++i];
#endif #endif
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
@ -1499,8 +1506,11 @@ static void usage(void) {
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n" " You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
" variable to pass this password more safely\n" " variable to pass this password more safely\n"
" (if both are used, this argument takes predecence).\n" " (if both are used, this argument takes predecence).\n"
" -user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n" " --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
" -pass <password> Alias of -a for consistency with the new --user option.\n" " --pass <password> Alias of -a for consistency with the new --user option.\n"
" --askpass Force user to input password with mask from STDIN.\n"
" If this argument is used, '-a' and " REDIS_CLI_AUTH_ENV "\n"
" environment variable will be ignored.\n"
" -u <uri> Server URI.\n" " -u <uri> Server URI.\n"
" -r <repeat> Execute specified command N times.\n" " -r <repeat> Execute specified command N times.\n"
" -i <interval> When -r is used, waits <interval> seconds per command.\n" " -i <interval> When -r is used, waits <interval> seconds per command.\n"
@ -1512,12 +1522,13 @@ static void usage(void) {
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n" " -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
" --tls Establish a secure TLS connection.\n" " --tls Establish a secure TLS connection.\n"
" --cacert CA Certificate file to verify with.\n" " --sni <host> Server name indication for TLS.\n"
" --cacertdir Directory where trusted CA certificates are stored.\n" " --cacert <file> CA Certificate file to verify with.\n"
" --cacertdir <dir> Directory where trusted CA certificates are stored.\n"
" If neither cacert nor cacertdir are specified, the default\n" " If neither cacert nor cacertdir are specified, the default\n"
" system-wide trusted root certs configuration will apply.\n" " system-wide trusted root certs configuration will apply.\n"
" --cert Client certificate to authenticate with.\n" " --cert <file> Client certificate to authenticate with.\n"
" --key Private key file to authenticate with.\n" " --key <file> Private key file to authenticate with.\n"
#endif #endif
" --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"
@ -1793,6 +1804,8 @@ static void repl(void) {
if (config.eval) { if (config.eval) {
config.eval_ldb = 1; config.eval_ldb = 1;
config.output = OUTPUT_RAW; config.output = OUTPUT_RAW;
sdsfreesplitres(argv,argc);
linenoiseFree(line);
return; /* Return to evalMode to restart the session. */ return; /* Return to evalMode to restart the session. */
} else { } else {
printf("Use 'restart' only in Lua debugging mode."); printf("Use 'restart' only in Lua debugging mode.");
@ -6625,7 +6638,7 @@ static void LRUTestMode(void) {
* to fill the target instance easily. */ * to fill the target instance easily. */
start_cycle = mstime(); start_cycle = mstime();
long long hits = 0, misses = 0; long long hits = 0, misses = 0;
while(mstime() - start_cycle < 1000) { while(mstime() - start_cycle < LRU_CYCLE_PERIOD) {
/* Write cycle. */ /* Write cycle. */
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) { for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
char val[6]; char val[6];
@ -6746,6 +6759,13 @@ static void intrinsicLatencyMode(void) {
} }
} }
static sds askPassword() {
linenoiseMaskModeEnable();
sds auth = linenoise("Please input password: ");
linenoiseMaskModeDisable();
return auth;
}
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
* Program main() * Program main()
*--------------------------------------------------------------------------- */ *--------------------------------------------------------------------------- */
@ -6783,6 +6803,7 @@ int main(int argc, char **argv) {
config.hotkeys = 0; config.hotkeys = 0;
config.stdinarg = 0; config.stdinarg = 0;
config.auth = NULL; config.auth = NULL;
config.askpass = 0;
config.user = NULL; config.user = NULL;
config.eval = NULL; config.eval = NULL;
config.eval_ldb = 0; config.eval_ldb = 0;
@ -6824,6 +6845,18 @@ int main(int argc, char **argv) {
parseEnv(); parseEnv();
if (config.askpass) {
config.auth = askPassword();
}
#ifdef USE_OPENSSL
if (config.tls) {
ERR_load_crypto_strings();
SSL_load_error_strings();
SSL_library_init();
}
#endif
/* Cluster Manager mode */ /* Cluster Manager mode */
if (CLUSTER_MANAGER_MODE()) { if (CLUSTER_MANAGER_MODE()) {
clusterManagerCommandProc *proc = validateClusterManagerCommand(); clusterManagerCommandProc *proc = validateClusterManagerCommand();
@ -6942,3 +6975,4 @@ int main(int argc, char **argv) {
return noninteractive(argc,convertToSds(argc,argv)); return noninteractive(argc,convertToSds(argc,argv));
} }
} }

View File

@ -163,6 +163,7 @@ extern struct config {
int hotkeys; int hotkeys;
int stdinarg; /* get last arg from stdin. (-x option) */ int stdinarg; /* get last arg from stdin. (-x option) */
char *auth; char *auth;
int askpass;
char *user; char *user;
int output; /* output mode, see OUTPUT_* defines */ int output; /* output mode, see OUTPUT_* defines */
sds mb_delim; sds mb_delim;

View File

@ -131,8 +131,8 @@ extern "C" {
#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */ #define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */ #define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */ #define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m */ #define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */
#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_KEY_MISS) /* A */ #define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */
/* A special pointer that we can use between the core and the module to signal /* A special pointer that we can use between the core and the module to signal
@ -471,6 +471,7 @@ size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *r
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx); RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
@ -676,7 +677,7 @@ int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithUser)(RedisModuleCtx
void REDISMODULE_API_FUNC(RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id); void REDISMODULE_API_FUNC(RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id);
#endif #endif
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX) #define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF)
/* This is included inline inside each Redis module. */ /* This is included inline inside each Redis module. */
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused)); static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
@ -730,6 +731,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(CreateStringFromCallReply); REDISMODULE_GET_API(CreateStringFromCallReply);
REDISMODULE_GET_API(CreateString); REDISMODULE_GET_API(CreateString);
REDISMODULE_GET_API(CreateStringFromLongLong); REDISMODULE_GET_API(CreateStringFromLongLong);
REDISMODULE_GET_API(CreateStringFromDouble);
REDISMODULE_GET_API(CreateStringFromLongDouble); REDISMODULE_GET_API(CreateStringFromLongDouble);
REDISMODULE_GET_API(CreateStringFromString); REDISMODULE_GET_API(CreateStringFromString);
REDISMODULE_GET_API(CreateStringPrintf); REDISMODULE_GET_API(CreateStringPrintf);

View File

@ -53,6 +53,11 @@ void putSlaveOnline(client *replica);
int cancelReplicationHandshake(redisMaster *mi); int cancelReplicationHandshake(redisMaster *mi);
static void propagateMasterStaleKeys(); static void propagateMasterStaleKeys();
/* We take a global flag to remember if this instance generated an RDB
* because of replication, so that we can remove the RDB file in case
* the instance is configured to have no persistence. */
int RDBGeneratedByReplication = 0;
/* --------------------------- Utility functions ---------------------------- */ /* --------------------------- Utility functions ---------------------------- */
/* Return the pointer to a string representing the replica ip:listening_port /* Return the pointer to a string representing the replica ip:listening_port
@ -150,6 +155,34 @@ client *replicaFromMaster(redisMaster *mi)
return nullptr; return nullptr;
} }
/* Plain unlink() can block for quite some time in order to actually apply
* the file deletion to the filesystem. This call removes the file in a
* background thread instead. We actually just do close() in the thread,
* by using the fact that if there is another instance of the same file open,
* the foreground unlink() will not really do anything, and deleting the
* file will only happen once the last reference is lost. */
int bg_unlink(const char *filename) {
int fd = open(filename,O_RDONLY|O_NONBLOCK);
if (fd == -1) {
/* Can't open the file? Fall back to unlinking in the main thread. */
return unlink(filename);
} else {
/* The following unlink() will not do anything since file
* is still open. */
int retval = unlink(filename);
if (retval == -1) {
/* If we got an unlink error, we just return it, closing the
* new reference we have to the file. */
int old_errno = errno;
close(fd); /* This would overwrite our errno. So we saved it. */
errno = old_errno;
return -1;
}
bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)fd,NULL,NULL);
return 0; /* Success. */
}
}
/* ---------------------------------- MASTER -------------------------------- */ /* ---------------------------------- MASTER -------------------------------- */
void createReplicationBacklog(void) { void createReplicationBacklog(void) {
@ -214,6 +247,7 @@ void feedReplicationBacklog(const void *ptr, size_t len) {
const unsigned char *p = (const unsigned char*)ptr; const unsigned char *p = (const unsigned char*)ptr;
g_pserver->master_repl_offset += len; g_pserver->master_repl_offset += len;
g_pserver->master_repl_meaningful_offset = g_pserver->master_repl_offset;
/* This is a circular buffer, so write as much data we can at every /* This is a circular buffer, so write as much data we can at every
* iteration and rewind the "idx" index if we reach the limit. */ * iteration and rewind the "idx" index if we reach the limit. */
@ -252,6 +286,8 @@ void feedReplicationBacklogWithObject(robj *o) {
feedReplicationBacklog(p,len); feedReplicationBacklog(p,len);
} }
sds catCommandForAofAndActiveReplication(sds buf, struct redisCommand *cmd, robj **argv, int argc);
void replicationFeedSlave(client *replica, int dictid, robj **argv, int argc, bool fSendRaw) void replicationFeedSlave(client *replica, int dictid, robj **argv, int argc, bool fSendRaw)
{ {
char llstr[LONG_STR_SIZE]; char llstr[LONG_STR_SIZE];
@ -290,13 +326,43 @@ void replicationFeedSlave(client *replica, int dictid, robj **argv, int argc, bo
* are queued in the output buffer until the initial SYNC completes), * are queued in the output buffer until the initial SYNC completes),
* or are already in sync with the master. */ * or are already in sync with the master. */
/* Add the multi bulk length. */ if (fSendRaw)
addReplyArrayLenAsync(replica,argc); {
/* Add the multi bulk length. */
addReplyArrayLenAsync(replica,argc);
/* Finally any additional argument that was not stored inside the /* Finally any additional argument that was not stored inside the
* static buffer if any (from j to argc). */ * static buffer if any (from j to argc). */
for (int j = 0; j < argc; j++) for (int j = 0; j < argc; j++)
addReplyBulkAsync(replica,argv[j]); addReplyBulkAsync(replica,argv[j]);
}
else
{
struct redisCommand *cmd = lookupCommand(szFromObj(argv[0]));
sds buf = catCommandForAofAndActiveReplication(sdsempty(), cmd, argv, argc);
addReplyProtoAsync(replica, buf, sdslen(buf));
sdsfree(buf);
}
}
static int writeProtoNum(char *dst, const size_t cchdst, long long num)
{
if (cchdst < 1)
return 0;
dst[0] = '$';
int cch = 1;
cch += ll2string(dst + cch, cchdst - cch, digits10(num));
int chCpyT = std::min<int>(cchdst - cch, 2);
memcpy(dst + cch, "\r\n", chCpyT);
cch += chCpyT;
cch += ll2string(dst + cch, cchdst-cch, num);
chCpyT = std::min<int>(cchdst - cch, 3);
memcpy(dst + cch, "\r\n", chCpyT);
if (chCpyT == 3)
cch += 2;
else
cch += chCpyT;
return cch;
} }
/* Propagate write commands to slaves, and populate the replication backlog /* Propagate write commands to slaves, and populate the replication backlog
@ -309,6 +375,8 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
listIter li, liReply; listIter li, liReply;
int j, len; int j, len;
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
static client *fake = nullptr;
if (dictid < 0) if (dictid < 0)
dictid = 0; // this can happen if we send a PING before any real operation dictid = 0; // this can happen if we send a PING before any real operation
@ -326,8 +394,12 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
/* We can't have slaves attached and no backlog. */ /* We can't have slaves attached and no backlog. */
serverAssert(!(listLength(slaves) != 0 && g_pserver->repl_backlog == NULL)); serverAssert(!(listLength(slaves) != 0 && g_pserver->repl_backlog == NULL));
client *fake = createClient(nullptr, serverTL - g_pserver->rgthreadvar); if (fake == nullptr)
fake->flags |= CLIENT_FORCE_REPLY; {
fake = createClient(nullptr, serverTL - g_pserver->rgthreadvar);
fake->flags |= CLIENT_FORCE_REPLY;
}
bool fSendRaw = !g_pserver->fActiveReplica; bool fSendRaw = !g_pserver->fActiveReplica;
replicationFeedSlave(fake, dictid, argv, argc, fSendRaw); // Note: updates the repl log, keep above the repl update code below replicationFeedSlave(fake, dictid, argv, argc, fSendRaw); // Note: updates the repl log, keep above the repl update code below
@ -343,24 +415,37 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
serverAssert(argc > 0); serverAssert(argc > 0);
serverAssert(cchbuf > 0); serverAssert(cchbuf > 0);
char uuid[40] = {'\0'}; // The code below used to be: snprintf(proto, sizeof(proto), "*5\r\n$7\r\nRREPLAY\r\n$%d\r\n%s\r\n$%lld\r\n", (int)strlen(uuid), uuid, cchbuf);
uuid_unparse(cserver.uuid, uuid); // but that was much too slow
static const char *protoRREPLAY = "*5\r\n$7\r\nRREPLAY\r\n$36\r\n00000000-0000-0000-0000-000000000000\r\n$";
char proto[1024]; char proto[1024];
int cchProto = snprintf(proto, sizeof(proto), "*5\r\n$7\r\nRREPLAY\r\n$%d\r\n%s\r\n$%lld\r\n", (int)strlen(uuid), uuid, cchbuf); int cchProto = 0;
cchProto = std::min((int)sizeof(proto), cchProto); if (!fSendRaw)
{
char uuid[37];
uuid_unparse(cserver.uuid, uuid);
cchProto = strlen(protoRREPLAY);
memcpy(proto, protoRREPLAY, strlen(protoRREPLAY));
memcpy(proto + 22, uuid, 36); // Note UUID_STR_LEN includes the \0 trailing byte which we don't want
cchProto += ll2string(proto + cchProto, sizeof(proto)-cchProto, cchbuf);
memcpy(proto + cchProto, "\r\n", 3);
cchProto += 2;
}
long long master_repl_offset_start = g_pserver->master_repl_offset; long long master_repl_offset_start = g_pserver->master_repl_offset;
char szDbNum[128]; char szDbNum[128];
int cchDictIdNum = snprintf(szDbNum, sizeof(szDbNum), "%d", dictid); int cchDbNum = 0;
int cchDbNum = snprintf(szDbNum, sizeof(szDbNum), "$%d\r\n%d\r\n", cchDictIdNum, dictid); if (!fSendRaw)
cchDbNum = std::min<int>(cchDbNum, sizeof(szDbNum)); // snprintf is tricky like that cchDbNum = writeProtoNum(szDbNum, sizeof(szDbNum), dictid);
char szMvcc[128]; char szMvcc[128];
incrementMvccTstamp(); int cchMvcc = 0;
uint64_t mvccTstamp = getMvccTstamp(); incrementMvccTstamp(); // Always increment MVCC tstamp so we're consistent with active and normal replication
int cchMvccNum = snprintf(szMvcc, sizeof(szMvcc), "%" PRIu64, mvccTstamp); if (!fSendRaw)
int cchMvcc = snprintf(szMvcc, sizeof(szMvcc), "$%d\r\n%" PRIu64 "\r\n", cchMvccNum, mvccTstamp); cchMvcc = writeProtoNum(szMvcc, sizeof(szMvcc), getMvccTstamp());
cchMvcc = std::min<int>(cchMvcc, sizeof(szMvcc)); // tricky snprintf
/* Write the command to the replication backlog if any. */ /* Write the command to the replication backlog if any. */
if (g_pserver->repl_backlog) if (g_pserver->repl_backlog)
@ -449,7 +534,11 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
} }
} }
freeClient(fake); // Cleanup cached fake client output buffers
fake->bufpos = 0;
fake->sentlen = 0;
fake->reply_bytes = 0;
listEmpty(fake->reply);
} }
/* This function is used in order to proxy what we receive from our master /* This function is used in order to proxy what we receive from our master
@ -789,6 +878,14 @@ int startBgsaveForReplication(int mincapa) {
retval = C_ERR; retval = C_ERR;
} }
/* If we succeeded to start a BGSAVE with disk target, let's remember
* this fact, so that we can later delete the file if needed. Note
* that we don't set the flag to 1 if the feature is disabled, otherwise
* it would never be cleared: the file is not deleted. This way if
* the user enables it later with CONFIG SET, we are fine. */
if (retval == C_OK && !socket_target && g_pserver->rdb_del_sync_files)
RDBGeneratedByReplication = 1;
/* If we failed to BGSAVE, remove the slaves waiting for a full /* If we failed to BGSAVE, remove the slaves waiting for a full
* resynchronization from the list of slaves, inform them with * resynchronization from the list of slaves, inform them with
* an error about what happened, close the connection ASAP. */ * an error about what happened, close the connection ASAP. */
@ -1148,6 +1245,55 @@ void putSlaveOnline(client *replica) {
} }
} }
/* We call this function periodically to remove an RDB file that was
* generated because of replication, in an instance that is otherwise
* without any persistence. We don't want instances without persistence
* to take RDB files around, this violates certain policies in certain
* environments. */
void removeRDBUsedToSyncReplicas(void) {
serverAssert(GlobalLocksAcquired());
/* If the feature is disabled, return ASAP but also clear the
* RDBGeneratedByReplication flag in case it was set. Otherwise if the
* feature was enabled, but gets disabled later with CONFIG SET, the
* flag may remain set to one: then next time the feature is re-enabled
* via CONFIG SET we have have it set even if no RDB was generated
* because of replication recently. */
if (!g_pserver->rdb_del_sync_files) {
RDBGeneratedByReplication = 0;
return;
}
if (allPersistenceDisabled() && RDBGeneratedByReplication) {
client *slave;
listNode *ln;
listIter li;
int delrdb = 1;
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
slave = (client*)ln->value;
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START ||
slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END ||
slave->replstate == SLAVE_STATE_SEND_BULK)
{
delrdb = 0;
break; /* No need to check the other replicas. */
}
}
if (delrdb) {
struct stat sb;
if (lstat(g_pserver->rdb_filename,&sb) != -1) {
RDBGeneratedByReplication = 0;
serverLog(LL_NOTICE,
"Removing the RDB file used to feed replicas "
"in a persistence-less instance");
bg_unlink(g_pserver->rdb_filename);
}
}
}
}
void sendBulkToSlave(connection *conn) { void sendBulkToSlave(connection *conn) {
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
@ -1155,6 +1301,7 @@ void sendBulkToSlave(connection *conn) {
serverAssert(FCorrectThread(replica)); serverAssert(FCorrectThread(replica));
char buf[PROTO_IOBUF_LEN]; char buf[PROTO_IOBUF_LEN];
ssize_t nwritten, buflen; ssize_t nwritten, buflen;
std::unique_lock<fastlock> ul(replica->lock);
/* Before sending the RDB file, we send the preamble as configured by the /* Before sending the RDB file, we send the preamble as configured by the
* replication process. Currently the preamble is just the bulk count of * replication process. Currently the preamble is just the bulk count of
@ -1162,7 +1309,8 @@ void sendBulkToSlave(connection *conn) {
if (replica->replpreamble) { if (replica->replpreamble) {
nwritten = connWrite(conn,replica->replpreamble,sdslen(replica->replpreamble)); nwritten = connWrite(conn,replica->replpreamble,sdslen(replica->replpreamble));
if (nwritten == -1) { if (nwritten == -1) {
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s", serverLog(LL_VERBOSE,
"Write error sending RDB preamble to replica: %s",
connGetLastError(conn)); connGetLastError(conn));
freeClient(replica); freeClient(replica);
return; return;
@ -1214,11 +1362,9 @@ void rdbPipeWriteHandlerConnRemoved(struct connection *conn) {
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) {
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, []{ if (aeCreateFileEvent(serverTL->el, g_pserver->rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
if (aeCreateFileEvent(serverTL->el, g_pserver->rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) { serverPanic("Unrecoverable error creating g_pserver->rdb_pipe_read file event.");
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event."); }
}
});
} }
} }
@ -1277,8 +1423,6 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData,
UNUSED(clientData); UNUSED(clientData);
UNUSED(eventLoop); UNUSED(eventLoop);
serverAssert(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el == eventLoop);
int i; int i;
if (!g_pserver->rdb_pipe_buff) if (!g_pserver->rdb_pipe_buff)
g_pserver->rdb_pipe_buff = (char*)zmalloc(PROTO_IOBUF_LEN); g_pserver->rdb_pipe_buff = (char*)zmalloc(PROTO_IOBUF_LEN);
@ -1704,9 +1848,9 @@ void disklessLoadRestoreBackups(const redisDbPersistentDataSnapshot **backup, in
/* Asynchronously read the SYNC payload we receive from a master */ /* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */ #define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(connection *conn) { void readSyncBulkPayload(connection *conn) {
char buf[4096]; char buf[PROTO_IOBUF_LEN];
ssize_t nread, readlen, nwritten; ssize_t nread, readlen, nwritten;
int use_diskless_load; int use_diskless_load = useDisklessLoad();
const redisDbPersistentDataSnapshot **diskless_load_backup = NULL; const redisDbPersistentDataSnapshot **diskless_load_backup = NULL;
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
int empty_db_flags = g_pserver->repl_slave_lazy_flush ? EMPTYDB_ASYNC : int empty_db_flags = g_pserver->repl_slave_lazy_flush ? EMPTYDB_ASYNC :
@ -1769,19 +1913,18 @@ void readSyncBulkPayload(connection *conn) {
mi->repl_transfer_size = 0; mi->repl_transfer_size = 0;
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"MASTER <-> REPLICA sync: receiving streamed RDB from master with EOF %s", "MASTER <-> REPLICA sync: receiving streamed RDB from master with EOF %s",
useDisklessLoad()? "to parser":"to disk"); use_diskless_load? "to parser":"to disk");
} else { } else {
usemark = 0; usemark = 0;
mi->repl_transfer_size = strtol(buf+1,NULL,10); mi->repl_transfer_size = strtol(buf+1,NULL,10);
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"MASTER <-> REPLICA sync: receiving %lld bytes from master %s", "MASTER <-> REPLICA sync: receiving %lld bytes from master %s",
(long long) mi->repl_transfer_size, (long long) mi->repl_transfer_size,
useDisklessLoad() ? "to parser" : "to disk"); use_diskless_load? "to parser":"to disk");
} }
return; return;
} }
use_diskless_load = useDisklessLoad();
if (!use_diskless_load) { if (!use_diskless_load) {
/* Read the data from the socket, store it to a file and search /* Read the data from the socket, store it to a file and search
* for the EOF. */ * for the EOF. */
@ -1884,18 +2027,20 @@ void readSyncBulkPayload(connection *conn) {
* the RDB, otherwise we'll create a copy-on-write disaster. */ * the RDB, otherwise we'll create a copy-on-write disaster. */
if (g_pserver->aof_state != AOF_OFF) stopAppendOnly(); if (g_pserver->aof_state != AOF_OFF) stopAppendOnly();
if (use_diskless_load &&
g_pserver->repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB)
{
/* Create a backup of server.db[] and initialize to empty
* dictionaries */
diskless_load_backup = disklessLoadMakeBackups();
}
if (!fUpdate) if (!fUpdate)
{ {
signalFlushedDb(-1); /* We call to emptyDb even in case of REPL_DISKLESS_LOAD_SWAPDB
* (Where disklessLoadMakeBackups left server.db empty) because we
/* When diskless RDB loading is used by replicas, it may be configured * want to execute all the auxiliary logic of emptyDb (Namely,
* in order to save the current DB instead of throwing it away, * fire module events) */
* so that we can restore it in case of failed transfer. */
if (use_diskless_load &&
g_pserver->repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB)
{
diskless_load_backup = disklessLoadMakeBackups();
}
emptyDb(-1,empty_db_flags,replicationEmptyDbCallback); emptyDb(-1,empty_db_flags,replicationEmptyDbCallback);
} }
@ -2014,12 +2159,24 @@ void readSyncBulkPayload(connection *conn) {
"Failed trying to load the MASTER synchronization " "Failed trying to load the MASTER synchronization "
"DB from disk"); "DB from disk");
cancelReplicationHandshake(mi); cancelReplicationHandshake(mi);
if (g_pserver->rdb_del_sync_files && allPersistenceDisabled()) {
serverLog(LL_NOTICE,"Removing the RDB file obtained from "
"the master. This replica has persistence "
"disabled");
bg_unlink(g_pserver->rdb_filename);
}
/* Note that there's no point in restarting the AOF on sync failure, /* Note that there's no point in restarting the AOF on sync failure,
it'll be restarted when sync succeeds or replica promoted. */ it'll be restarted when sync succeeds or replica promoted. */
return; return;
} }
/* Cleanup. */ /* Cleanup. */
if (g_pserver->rdb_del_sync_files && allPersistenceDisabled()) {
serverLog(LL_NOTICE,"Removing the RDB file obtained from "
"the master. This replica has persistence "
"disabled");
bg_unlink(g_pserver->rdb_filename);
}
if (fUpdate) if (fUpdate)
unlink(mi->repl_transfer_tmpfile); unlink(mi->repl_transfer_tmpfile);
zfree(mi->repl_transfer_tmpfile); zfree(mi->repl_transfer_tmpfile);
@ -2053,6 +2210,7 @@ void readSyncBulkPayload(connection *conn) {
* we are starting a new history. */ * we are starting a new history. */
memcpy(g_pserver->replid,mi->master->replid,sizeof(g_pserver->replid)); memcpy(g_pserver->replid,mi->master->replid,sizeof(g_pserver->replid));
g_pserver->master_repl_offset = mi->master->reploff; g_pserver->master_repl_offset = mi->master->reploff;
g_pserver->master_repl_meaningful_offset = mi->master->reploff;
} }
clearReplicationId2(); clearReplicationId2();
@ -2643,6 +2801,10 @@ 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) {
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections.\n");
redisCommunicateSystemd("READY=1\n");
}
return; return;
} }
@ -2938,6 +3100,10 @@ void replicationUnsetMaster(redisMaster *mi) {
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED, moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER, REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER,
NULL); NULL);
/* Restart the AOF subsystem in case we shut it down during a sync when
* we were still a slave. */
if (g_pserver->aof_enabled && g_pserver->aof_state == AOF_OFF) restartAOFAfterSYNC();
} }
/* This function is called when the replica lose the connection with the /* This function is called when the replica lose the connection with the
@ -2980,9 +3146,6 @@ void replicaofCommand(client *c) {
serverLog(LL_NOTICE,"MASTER MODE enabled (user request from '%s')", serverLog(LL_NOTICE,"MASTER MODE enabled (user request from '%s')",
client); client);
sdsfree(client); sdsfree(client);
/* Restart the AOF subsystem in case we shut it down during a sync when
* we were still a slave. */
if (g_pserver->aof_enabled && g_pserver->aof_state == AOF_OFF) restartAOFAfterSYNC();
} }
} else { } else {
long port; long port;
@ -3056,9 +3219,15 @@ void roleCommand(client *c) {
listNode *ln; listNode *ln;
listRewind(g_pserver->masters, &li); listRewind(g_pserver->masters, &li);
if (listLength(g_pserver->masters) > 1)
addReplyArrayLen(c,listLength(g_pserver->masters));
while ((ln = listNext(&li))) while ((ln = listNext(&li)))
{ {
redisMaster *mi = (redisMaster*)listNodeValue(ln); redisMaster *mi = (redisMaster*)listNodeValue(ln);
std::unique_lock<fastlock> lock;
if (mi->master != nullptr)
lock = std::unique_lock<fastlock>(mi->master->lock);
const char *slavestate = NULL; const char *slavestate = NULL;
addReplyArrayLen(c,5); addReplyArrayLen(c,5);
if (g_pserver->fActiveReplica) if (g_pserver->fActiveReplica)
@ -3172,15 +3341,53 @@ void replicationCacheMaster(redisMaster *mi, client *c) {
* current offset if no data was lost during the failover. So we use our * current offset if no data was lost during the failover. So we use our
* current replication ID and offset in order to synthesize a cached master. */ * current replication ID and offset in order to synthesize a cached master. */
void replicationCacheMasterUsingMyself(redisMaster *mi) { void replicationCacheMasterUsingMyself(redisMaster *mi) {
serverLog(LL_NOTICE,
"Before turning into a replica, using my own master parameters "
"to synthesize a cached master: I may be able to synchronize with "
"the new master with just a partial transfer.");
if (mi->cached_master != nullptr) if (mi->cached_master != nullptr)
{ {
// This can happen on first load of the RDB, the master we created in config load is stale // This can happen on first load of the RDB, the master we created in config load is stale
freeClient(mi->cached_master); freeClient(mi->cached_master);
} }
/* This will be used to populate the field server.master->reploff
* by replicationCreateMasterClient(). We'll later set the created
* master as server.cached_master, so the replica will use such
* offset for PSYNC. */
mi->master_initial_offset = g_pserver->master_repl_offset;
/* However if the "meaningful" offset, that is the offset without
* the final PINGs in the stream, is different, use this instead:
* often when the master is no longer reachable, replicas will never
* receive the PINGs, however the master will end with an incremented
* offset because of the PINGs and will not be able to incrementally
* PSYNC with the new master. */
if (g_pserver->master_repl_offset > g_pserver->master_repl_meaningful_offset) {
long long delta = g_pserver->master_repl_offset -
g_pserver->master_repl_meaningful_offset;
serverLog(LL_NOTICE,
"Using the meaningful offset %lld instead of %lld to exclude "
"the final PINGs (%lld bytes difference)",
g_pserver->master_repl_meaningful_offset,
g_pserver->master_repl_offset,
delta);
mi->master_initial_offset = g_pserver->master_repl_meaningful_offset;
g_pserver->master_repl_offset = g_pserver->master_repl_meaningful_offset;
if (g_pserver->repl_backlog_histlen <= delta) {
g_pserver->repl_backlog_histlen = 0;
g_pserver->repl_backlog_idx = 0;
} else {
g_pserver->repl_backlog_histlen -= delta;
g_pserver->repl_backlog_idx =
(g_pserver->repl_backlog_idx + (g_pserver->repl_backlog_size - delta)) %
g_pserver->repl_backlog_size;
}
}
/* The master client we create can be set to any DBID, because /* The master client we create can be set to any DBID, because
* the new master will start its replication stream with SELECT. */ * the new master will start its replication stream with SELECT. */
mi->master_initial_offset = g_pserver->master_repl_offset;
replicationCreateMasterClient(mi,NULL,-1); replicationCreateMasterClient(mi,NULL,-1);
std::lock_guard<decltype(mi->master->lock)> lock(mi->master->lock); std::lock_guard<decltype(mi->master->lock)> lock(mi->master->lock);
@ -3191,7 +3398,6 @@ void replicationCacheMasterUsingMyself(redisMaster *mi) {
unlinkClient(mi->master); unlinkClient(mi->master);
mi->cached_master = mi->master; mi->cached_master = mi->master;
mi->master = NULL; mi->master = NULL;
serverLog(LL_NOTICE,"Before turning into a replica, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.");
} }
/* Free a cached master, called when there are no longer the conditions for /* Free a cached master, called when there are no longer the conditions for
@ -3596,10 +3802,18 @@ void replicationCron(void) {
clientsArePaused(); clientsArePaused();
if (!manual_failover_in_progress) { if (!manual_failover_in_progress) {
long long before_ping = g_pserver->master_repl_meaningful_offset;
ping_argv[0] = createStringObject("PING",4); ping_argv[0] = createStringObject("PING",4);
replicationFeedSlaves(g_pserver->slaves, g_pserver->replicaseldb, replicationFeedSlaves(g_pserver->slaves, g_pserver->replicaseldb,
ping_argv, 1); ping_argv, 1);
decrRefCount(ping_argv[0]); decrRefCount(ping_argv[0]);
/* The server.master_repl_meaningful_offset variable represents
* the offset of the replication stream without the pending PINGs.
* This is useful to set the right replication offset for PSYNC
* when the master is turned into a replica. Otherwise pending
* PINGs may not allow it to perform an incremental sync with the
* new master. */
g_pserver->master_repl_meaningful_offset = before_ping;
} }
} }
@ -3739,6 +3953,10 @@ void replicationCron(void) {
propagateMasterStaleKeys(); propagateMasterStaleKeys();
/* Remove the RDB file used for replication if Redis is not running
* with any persistence. */
removeRDBUsedToSyncReplicas();
/* Refresh the number of slaves with lag <= min-slaves-max-lag. */ /* Refresh the number of slaves with lag <= min-slaves-max-lag. */
refreshGoodSlavesCount(); refreshGoodSlavesCount();
replication_cron_loops++; /* Incremented with frequency 1 HZ. */ replication_cron_loops++; /* Incremented with frequency 1 HZ. */
@ -3853,6 +4071,13 @@ struct RemoteMasterState
{ {
uint64_t mvcc = 0; uint64_t mvcc = 0;
client *cFake = nullptr; client *cFake = nullptr;
~RemoteMasterState()
{
aeAcquireLock();
freeClient(cFake);
aeReleaseLock();
}
}; };
static std::unordered_map<std::string, RemoteMasterState> g_mapremote; static std::unordered_map<std::string, RemoteMasterState> g_mapremote;

View File

@ -620,8 +620,10 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
} }
/* Check the ACLs. */ /* Check the ACLs. */
acl_retval = ACLCheckCommandPerm(c); int acl_keypos;
acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
if (acl_retval != ACL_OK) { if (acl_retval != ACL_OK) {
addACLLogEntry(c,acl_retval,acl_keypos,NULL);
if (acl_retval == ACL_DENIED_CMD) if (acl_retval == ACL_DENIED_CMD)
luaPushError(lua, "The user executing the script can't run this " luaPushError(lua, "The user executing the script can't run this "
"command or subcommand"); "command or subcommand");
@ -2491,6 +2493,7 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1))); ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
lua_pop(lua,1); lua_pop(lua,1);
sdsfree(code); sdsfree(code);
sdsfree(expr);
return; return;
} }
} }

View File

@ -36,8 +36,13 @@
* the include of your alternate allocator if needed (not needed in order * the include of your alternate allocator if needed (not needed in order
* to use the default libc allocator). */ * to use the default libc allocator). */
#ifndef __SDS_ALLOC_H__
#define __SDS_ALLOC_H__
#include "zmalloc.h" #include "zmalloc.h"
#include "storage.h" #include "storage.h"
#define s_malloc zmalloc #define s_malloc zmalloc
#define s_realloc zrealloc #define s_realloc zrealloc
#define s_free zfree #define s_free zfree
#endif

View File

@ -207,7 +207,8 @@ typedef struct sentinelRedisInstance {
dict *slaves; /* Slaves for this master instance. */ dict *slaves; /* Slaves for this master instance. */
unsigned int quorum;/* Number of sentinels that need to agree on failure. */ unsigned int quorum;/* Number of sentinels that need to agree on failure. */
int parallel_syncs; /* How many slaves to reconfigure at same time. */ int parallel_syncs; /* How many slaves to reconfigure at same time. */
char *auth_pass; /* Password to use for AUTH against master & slaves. */ char *auth_pass; /* Password to use for AUTH against master & replica. */
char *auth_user; /* Username for ACLs AUTH against master & replica. */
/* 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. */
@ -1233,6 +1234,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
SENTINEL_DEFAULT_DOWN_AFTER; SENTINEL_DEFAULT_DOWN_AFTER;
ri->master_link_down_time = 0; ri->master_link_down_time = 0;
ri->auth_pass = NULL; ri->auth_pass = NULL;
ri->auth_user = NULL;
ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY; ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY;
ri->slave_reconf_sent_time = 0; ri->slave_reconf_sent_time = 0;
ri->slave_master_host = NULL; ri->slave_master_host = NULL;
@ -1291,6 +1293,7 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) {
sdsfree(ri->slave_master_host); sdsfree(ri->slave_master_host);
sdsfree(ri->leader); sdsfree(ri->leader);
sdsfree(ri->auth_pass); sdsfree(ri->auth_pass);
sdsfree(ri->auth_user);
sdsfree(ri->info); sdsfree(ri->info);
releaseSentinelAddr(ri->addr); releaseSentinelAddr(ri->addr);
dictRelease(ri->renamed_commands); dictRelease(ri->renamed_commands);
@ -1656,19 +1659,19 @@ const char *sentinelHandleConfiguration(char **argv, int argc) {
ri->failover_timeout = atoi(argv[2]); ri->failover_timeout = atoi(argv[2]);
if (ri->failover_timeout <= 0) if (ri->failover_timeout <= 0)
return "negative or zero time parameter."; return "negative or zero time parameter.";
} else if (!strcasecmp(argv[0],"parallel-syncs") && argc == 3) { } else if (!strcasecmp(argv[0],"parallel-syncs") && argc == 3) {
/* parallel-syncs <name> <milliseconds> */ /* parallel-syncs <name> <milliseconds> */
ri = sentinelGetMasterByName(argv[1]); ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name."; if (!ri) return "No such master with specified name.";
ri->parallel_syncs = atoi(argv[2]); ri->parallel_syncs = atoi(argv[2]);
} else if (!strcasecmp(argv[0],"notification-script") && argc == 3) { } else if (!strcasecmp(argv[0],"notification-script") && argc == 3) {
/* notification-script <name> <path> */ /* notification-script <name> <path> */
ri = sentinelGetMasterByName(argv[1]); ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name."; if (!ri) return "No such master with specified name.";
if (access(argv[2],X_OK) == -1) if (access(argv[2],X_OK) == -1)
return "Notification script seems non existing or non executable."; return "Notification script seems non existing or non executable.";
ri->notification_script = sdsnew(argv[2]); ri->notification_script = sdsnew(argv[2]);
} else if (!strcasecmp(argv[0],"client-reconfig-script") && argc == 3) { } else if (!strcasecmp(argv[0],"client-reconfig-script") && argc == 3) {
/* client-reconfig-script <name> <path> */ /* client-reconfig-script <name> <path> */
ri = sentinelGetMasterByName(argv[1]); ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name."; if (!ri) return "No such master with specified name.";
@ -1676,11 +1679,16 @@ const char *sentinelHandleConfiguration(char **argv, int argc) {
return "Client reconfiguration script seems non existing or " return "Client reconfiguration script seems non existing or "
"non executable."; "non executable.";
ri->client_reconfig_script = sdsnew(argv[2]); ri->client_reconfig_script = sdsnew(argv[2]);
} else if (!strcasecmp(argv[0],"auth-pass") && argc == 3) { } else if (!strcasecmp(argv[0],"auth-pass") && argc == 3) {
/* auth-pass <name> <password> */ /* auth-pass <name> <password> */
ri = sentinelGetMasterByName(argv[1]); ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name."; if (!ri) return "No such master with specified name.";
ri->auth_pass = sdsnew(argv[2]); ri->auth_pass = sdsnew(argv[2]);
} else if (!strcasecmp(argv[0],"auth-user") && argc == 3) {
/* auth-user <name> <username> */
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
ri->auth_user = sdsnew(argv[2]);
} else if (!strcasecmp(argv[0],"current-epoch") && argc == 2) { } else if (!strcasecmp(argv[0],"current-epoch") && argc == 2) {
/* current-epoch <epoch> */ /* current-epoch <epoch> */
unsigned long long current_epoch = strtoull(argv[1],NULL,10); unsigned long long current_epoch = strtoull(argv[1],NULL,10);
@ -1838,7 +1846,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,"sentinel",line,1); rewriteConfigRewriteLine(state,"sentinel",line,1);
} }
/* sentinel auth-pass */ /* sentinel auth-pass & auth-user */
if (master->auth_pass) { if (master->auth_pass) {
line = sdscatprintf(sdsempty(), line = sdscatprintf(sdsempty(),
"sentinel auth-pass %s %s", "sentinel auth-pass %s %s",
@ -1846,6 +1854,13 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,"sentinel",line,1); rewriteConfigRewriteLine(state,"sentinel",line,1);
} }
if (master->auth_user) {
line = sdscatprintf(sdsempty(),
"sentinel auth-user %s %s",
master->name, master->auth_user);
rewriteConfigRewriteLine(state,"sentinel",line,1);
}
/* sentinel config-epoch */ /* sentinel config-epoch */
line = sdscatprintf(sdsempty(), line = sdscatprintf(sdsempty(),
"sentinel config-epoch %s %llu", "sentinel config-epoch %s %llu",
@ -1970,19 +1985,29 @@ werr:
* will disconnect and reconnect the link and so forth. */ * will disconnect and reconnect the link and so forth. */
void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) { void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
char *auth_pass = NULL; char *auth_pass = NULL;
char *auth_user = NULL;
if (ri->flags & SRI_MASTER) { if (ri->flags & SRI_MASTER) {
auth_pass = ri->auth_pass; auth_pass = ri->auth_pass;
auth_user = ri->auth_user;
} else if (ri->flags & SRI_SLAVE) { } else if (ri->flags & SRI_SLAVE) {
auth_pass = ri->master->auth_pass; auth_pass = ri->master->auth_pass;
auth_user = ri->master->auth_user;
} else if (ri->flags & SRI_SENTINEL) { } else if (ri->flags & SRI_SENTINEL) {
auth_pass = ACLDefaultUserFirstPassword(); auth_pass = g_pserver->requirepass;
auth_user = NULL;
} }
if (auth_pass) { if (auth_pass && auth_user == NULL) {
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s", if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s",
sentinelInstanceMapCommand(ri,"AUTH"), sentinelInstanceMapCommand(ri,"AUTH"),
auth_pass) == C_OK) ri->link->pending_commands++; auth_pass) == C_OK) ri->link->pending_commands++;
} else if (auth_pass && auth_user) {
/* If we also have an username, use the ACL-style AUTH command
* with two arguments, username and password. */
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s %s",
sentinelInstanceMapCommand(ri,"AUTH"),
auth_user, auth_pass) == C_OK) ri->link->pending_commands++;
} }
} }
@ -3524,6 +3549,12 @@ void sentinelSetCommand(client *c) {
sdsfree(ri->auth_pass); sdsfree(ri->auth_pass);
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL; ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
changes++; changes++;
} else if (!strcasecmp(option,"auth-user") && moreargs > 0) {
/* auth-user <username> */
char *value = szFromObj(c->argv[++j]);
sdsfree(ri->auth_user);
ri->auth_user = strlen(value) ? sdsnew(value) : NULL;
changes++;
} else if (!strcasecmp(option,"quorum") && moreargs > 0) { } else if (!strcasecmp(option,"quorum") && moreargs > 0) {
/* quorum <count> */ /* quorum <count> */
robj *o = c->argv[++j]; robj *o = c->argv[++j];

View File

@ -260,6 +260,10 @@ struct redisCommand redisCommandTable[] = {
"write use-memory @bitmap", "write use-memory @bitmap",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},
{"bitfield_ro",bitfieldroCommand,-2,
"read-only fast @bitmap",
0,NULL,1,1,1,0,0,0},
{"setrange",setrangeCommand,4, {"setrange",setrangeCommand,4,
"write use-memory @string", "write use-memory @string",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},
@ -601,7 +605,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"select",selectCommand,2, {"select",selectCommand,2,
"ok-loading fast @keyspace", "ok-loading fast ok-stale @keyspace",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"swapdb",swapdbCommand,3, {"swapdb",swapdbCommand,3,
@ -638,6 +642,10 @@ struct redisCommand redisCommandTable[] = {
"write fast @keyspace", "write fast @keyspace",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},
{"pexpirememberat", pexpireMemberAtCommand, 4,
"write fast @keyspace",
0,NULL,1,1,1,0,0,0},
{"pexpire",pexpireCommand,3, {"pexpire",pexpireCommand,3,
"write fast @keyspace", "write fast @keyspace",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},
@ -690,7 +698,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"lastsave",lastsaveCommand,1, {"lastsave",lastsaveCommand,1,
"read-only random fast @admin @dangerous", "read-only random fast ok-loading ok-stale @admin @dangerous",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"type",typeCommand,2, {"type",typeCommand,2,
@ -738,7 +746,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"monitor",monitorCommand,1, {"monitor",monitorCommand,1,
"admin no-script", "admin no-script ok-loading ok-stale",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"ttl",ttlCommand,-2, {"ttl",ttlCommand,-2,
@ -770,7 +778,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"debug",debugCommand,-2, {"debug",debugCommand,-2,
"admin no-script", "admin no-script ok-loading ok-stale",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"config",configCommand,-2, {"config",configCommand,-2,
@ -847,14 +855,14 @@ struct redisCommand redisCommandTable[] = {
{"memory",memoryCommand,-2, {"memory",memoryCommand,-2,
"random read-only", "random read-only",
0,NULL,0,0,0,0,0,0}, 0,memoryGetKeys,0,0,0,0,0,0},
{"client",clientCommand,-2, {"client",clientCommand,-2,
"admin no-script random @connection", "admin no-script random ok-loading ok-stale @connection",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"hello",helloCommand,-2, {"hello",helloCommand,-2,
"no-auth no-script fast no-monitor no-slowlog @connection", "no-auth no-script fast no-monitor ok-loading ok-stale no-slowlog @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
@ -868,7 +876,7 @@ struct redisCommand redisCommandTable[] = {
0,evalGetKeys,0,0,0,0,0,0}, 0,evalGetKeys,0,0,0,0,0,0},
{"slowlog",slowlogCommand,-2, {"slowlog",slowlogCommand,-2,
"admin random", "admin random ok-loading ok-stale",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"script",scriptCommand,-2, {"script",scriptCommand,-2,
@ -876,7 +884,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"time",timeCommand,1, {"time",timeCommand,1,
"read-only random fast", "read-only random fast ok-loading ok-stale",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"bitop",bitopCommand,-4, {"bitop",bitopCommand,-4,
@ -1531,12 +1539,20 @@ void updateDictResizePolicy(void) {
dictDisableResize(); dictDisableResize();
} }
/* Return true if there are no active children processes doing RDB saving,
* AOF rewriting, or some side process spawned by a loaded module. */
int hasActiveChildProcess() { int hasActiveChildProcess() {
return g_pserver->FRdbSaveInProgress() || return g_pserver->FRdbSaveInProgress() ||
g_pserver->aof_child_pid != -1 || g_pserver->aof_child_pid != -1 ||
g_pserver->module_child_pid != -1; g_pserver->module_child_pid != -1;
} }
/* Return true if this instance has persistence completely turned off:
* both RDB and AOF are disabled. */
int allPersistenceDisabled(void) {
return g_pserver->saveparamslen == 0 && g_pserver->aof_state == AOF_OFF;
}
/* ======================= Cron: called every 100 ms ======================== */ /* ======================= Cron: called every 100 ms ======================== */
/* Add a sample to the operations per second array of samples. */ /* Add a sample to the operations per second array of samples. */
@ -1566,42 +1582,6 @@ long long getInstantaneousMetric(int metric) {
return sum / STATS_METRIC_SAMPLES; return sum / STATS_METRIC_SAMPLES;
} }
/* Check for timeouts. Returns non-zero if the client was terminated.
* The function gets the current time in milliseconds as argument since
* it gets called multiple times in a loop, so calling gettimeofday() for
* each iteration would be costly without any actual gain. */
int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
time_t now = now_ms/1000;
if (cserver.maxidletime &&
!(c->flags & CLIENT_SLAVE) && /* no timeout for slaves */
!(c->flags & CLIENT_MASTER) && /* no timeout for masters */
!(c->flags & CLIENT_BLOCKED) && /* no timeout for BLPOP */
!(c->flags & CLIENT_PUBSUB) && /* no timeout for Pub/Sub clients */
(now - c->lastinteraction > cserver.maxidletime))
{
serverLog(LL_VERBOSE,"Closing idle client");
freeClient(c);
return 1;
} else if (c->flags & CLIENT_BLOCKED) {
/* Blocked OPS timeout is handled with milliseconds resolution.
* However note that the actual resolution is limited by
* g_pserver->hz. */
if (c->bpop.timeout != 0 && c->bpop.timeout < now_ms) {
/* Handle blocking operation specific timeout. */
replyToBlockedClientTimedOut(c);
unblockClient(c);
} else if (g_pserver->cluster_enabled) {
/* Cluster: handle unblock & redirect of clients blocked
* into keys no longer served by this g_pserver-> */
if (clusterRedirectBlockedClientIfNeeded(c))
unblockClient(c);
}
}
return 0;
}
/* The client query buffer is an sds.c string that can end with a lot of /* The client query buffer is an sds.c string that can end with a lot of
* free space not used, this function reclaims space if needed. * free space not used, this function reclaims space if needed.
* *
@ -1829,10 +1809,12 @@ void clientsCron(int iel) {
void databasesCron(void) { void databasesCron(void) {
/* Expire keys by random sampling. Not required for slaves /* Expire keys by random sampling. Not required for slaves
* as master will synthesize DELs for us. */ * as master will synthesize DELs for us. */
if (g_pserver->active_expire_enabled && (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica)) { if (g_pserver->active_expire_enabled) {
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW); if (iAmMaster()) {
} else if (listLength(g_pserver->masters) && !g_pserver->fActiveReplica) { activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
expireSlaveKeys(); } else {
expireSlaveKeys();
}
} }
/* Defrag keys gradually. */ /* Defrag keys gradually. */
@ -2289,11 +2271,15 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
/* Handle TLS pending data. (must be done before flushAppendOnlyFile) */ /* Handle TLS pending data. (must be done before flushAppendOnlyFile) */
tlsProcessPendingData(); tlsProcessPendingData();
/* If tls still has pending unread data don't sleep at all. */ /* If tls still has pending unread data don't sleep at all. */
aeSetDontWait(eventLoop, tlsHasPendingData()); aeSetDontWait(eventLoop, tlsHasPendingData());
aeAcquireLock(); aeAcquireLock();
/* Handle precise timeouts of blocked clients. */
handleBlockedClientsTimeout();
/* Call the Redis Cluster before sleep function. Note that this function /* Call the Redis Cluster before sleep function. Note that this function
* may change the state of Redis Cluster (from ok to fail or vice versa), * may change the state of Redis Cluster (from ok to fail or vice versa),
* so it's a good idea to call it before serving the unblocked clients * so it's a good idea to call it before serving the unblocked clients
@ -2335,6 +2321,10 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
processUnblockedClients(iel); processUnblockedClients(iel);
} }
/* Send the invalidation messages to clients participating to the
* client side caching protocol in broadcasting (BCAST) mode. */
trackingBroadcastInvalidationMessages();
/* Write the AOF buffer on disk */ /* Write the AOF buffer on disk */
flushAppendOnlyFile(0); flushAppendOnlyFile(0);
@ -2350,12 +2340,13 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
fFirstRun = false; fFirstRun = false;
} }
int aof_state = g_pserver->aof_state;
aeReleaseLock(); aeReleaseLock();
for (redisDb *db : vecdb) for (redisDb *db : vecdb)
db->commitChanges(); db->commitChanges();
handleClientsWithPendingWrites(iel); handleClientsWithPendingWrites(iel, aof_state);
if (serverTL->gcEpoch != 0) if (serverTL->gcEpoch != 0)
g_pserver->garbageCollector.endEpoch(serverTL->gcEpoch, true /*fNoFree*/); g_pserver->garbageCollector.endEpoch(serverTL->gcEpoch, true /*fNoFree*/);
serverTL->gcEpoch = 0; serverTL->gcEpoch = 0;
@ -2491,6 +2482,9 @@ void createSharedObjects(void) {
shared.zpopmax = makeObjectShared("ZPOPMAX",7); shared.zpopmax = makeObjectShared("ZPOPMAX",7);
shared.multi = makeObjectShared("MULTI",5); shared.multi = makeObjectShared("MULTI",5);
shared.exec = makeObjectShared("EXEC",4); shared.exec = makeObjectShared("EXEC",4);
shared.hdel = makeObjectShared(createStringObject("HDEL", 4));
shared.zrem = makeObjectShared(createStringObject("ZREM", 4));
shared.srem = makeObjectShared(createStringObject("SREM", 4));
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) { for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
shared.integers[j] = shared.integers[j] =
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j)); makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
@ -2548,6 +2542,7 @@ void initServerConfig(void) {
g_pserver->clients = listCreate(); g_pserver->clients = listCreate();
g_pserver->slaves = listCreate(); g_pserver->slaves = listCreate();
g_pserver->monitors = listCreate(); g_pserver->monitors = listCreate();
g_pserver->clients_timeout_table = raxNew();
g_pserver->timezone = getTimeZone(); /* Initialized by tzset(). */ g_pserver->timezone = getTimeZone(); /* Initialized by tzset(). */
cserver.configfile = NULL; cserver.configfile = NULL;
cserver.executable = NULL; cserver.executable = NULL;
@ -2603,6 +2598,7 @@ void initServerConfig(void) {
g_pserver->enable_multimaster = CONFIG_DEFAULT_ENABLE_MULTIMASTER; g_pserver->enable_multimaster = CONFIG_DEFAULT_ENABLE_MULTIMASTER;
g_pserver->repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT; g_pserver->repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
g_pserver->master_repl_offset = 0; g_pserver->master_repl_offset = 0;
g_pserver->master_repl_meaningful_offset = 0;
/* Replication partial resync backlog */ /* Replication partial resync backlog */
g_pserver->repl_backlog = NULL; g_pserver->repl_backlog = NULL;
@ -2642,6 +2638,8 @@ void initServerConfig(void) {
cserver.xgroupCommand = lookupCommandByCString("xgroup"); cserver.xgroupCommand = lookupCommandByCString("xgroup");
cserver.rreplayCommand = lookupCommandByCString("rreplay"); cserver.rreplayCommand = lookupCommandByCString("rreplay");
cserver.rpoplpushCommand = lookupCommandByCString("rpoplpush"); cserver.rpoplpushCommand = lookupCommandByCString("rpoplpush");
cserver.hdelCommand = lookupCommandByCString("hdel");
cserver.zremCommand = lookupCommandByCString("zrem");
/* Debugging */ /* Debugging */
g_pserver->assert_failed = "<no assertion failed>"; g_pserver->assert_failed = "<no assertion failed>";
@ -2664,6 +2662,7 @@ void initServerConfig(void) {
// so make sure its zero and initialized // so make sure its zero and initialized
g_pserver->db = (redisDb**)zcalloc(sizeof(redisDb*)*std::max(cserver.dbnum, 1), MALLOC_LOCAL); g_pserver->db = (redisDb**)zcalloc(sizeof(redisDb*)*std::max(cserver.dbnum, 1), MALLOC_LOCAL);
cserver.threadAffinityOffset = 0;
initConfigValues(); initConfigValues();
} }
@ -2718,7 +2717,17 @@ int restartServer(int flags, mstime_t delay) {
for (j = 3; j < (int)g_pserver->maxclients + 1024; j++) { for (j = 3; j < (int)g_pserver->maxclients + 1024; j++) {
/* Test the descriptor validity before closing it, otherwise /* Test the descriptor validity before closing it, otherwise
* Valgrind issues a warning on close(). */ * Valgrind issues a warning on close(). */
if (fcntl(j,F_GETFD) != -1) close(j); if (fcntl(j,F_GETFD) != -1)
{
/* This user to just close() here, but sanitizers detected that as an FD race.
The race doesn't matter since we're about to call exec() however we want
to cut down on noise, so instead we ask the kernel to close when we call
exec(), and only do it ourselves if that fails. */
if (fcntl(j, F_SETFD, FD_CLOEXEC) == -1)
{
close(j); // failed to set close on exec, close here
}
}
} }
/* Execute the server with the original command line. */ /* Execute the server with the original command line. */
@ -3004,6 +3013,7 @@ void resetServerStats(void) {
} }
g_pserver->stat_net_input_bytes = 0; g_pserver->stat_net_input_bytes = 0;
g_pserver->stat_net_output_bytes = 0; g_pserver->stat_net_output_bytes = 0;
g_pserver->stat_unexpected_error_replies = 0;
g_pserver->aof_delayed_fsync = 0; g_pserver->aof_delayed_fsync = 0;
} }
@ -3184,6 +3194,7 @@ void initServer(void) {
evictionPoolAlloc(); /* Initialize the LRU keys pool. */ evictionPoolAlloc(); /* Initialize the LRU keys pool. */
g_pserver->pubsub_channels = dictCreate(&keylistDictType,NULL); g_pserver->pubsub_channels = dictCreate(&keylistDictType,NULL);
g_pserver->pubsub_patterns = listCreate(); g_pserver->pubsub_patterns = listCreate();
g_pserver->pubsub_patterns_dict = dictCreate(&keylistDictType,NULL);
listSetFreeMethod(g_pserver->pubsub_patterns,freePubsubPattern); listSetFreeMethod(g_pserver->pubsub_patterns,freePubsubPattern);
listSetMatchMethod(g_pserver->pubsub_patterns,listMatchPubsubPattern); listSetMatchMethod(g_pserver->pubsub_patterns,listMatchPubsubPattern);
g_pserver->cronloops = 0; g_pserver->cronloops = 0;
@ -3680,13 +3691,17 @@ void call(client *c, int flags) {
if (flags & CMD_CALL_PROPAGATE) { if (flags & CMD_CALL_PROPAGATE) {
bool multi_emitted = false; bool multi_emitted = false;
/* Wrap the commands in server.also_propagate array, /* Wrap the commands in g_pserver->also_propagate array,
* but don't wrap it if we are already in MULIT context, * but don't wrap it if we are already in MULTI context,
* in case the nested MULIT/EXEC. * in case the nested MULTI/EXEC.
* *
* And if the array contains only one command, no need to * And if the array contains only one command, no need to
* wrap it, since the single command is atomic. */ * wrap it, since the single command is atomic. */
if (g_pserver->also_propagate.numops > 1 && !(c->flags & CLIENT_MULTI)) { if (g_pserver->also_propagate.numops > 1 &&
!(c->cmd->flags & CMD_MODULE) &&
!(c->flags & CLIENT_MULTI) &&
!(flags & CMD_CALL_NOWRAP))
{
execCommandPropagateMulti(c); execCommandPropagateMulti(c);
multi_emitted = true; multi_emitted = true;
} }
@ -3717,8 +3732,11 @@ void call(client *c, int flags) {
if (c->cmd->flags & CMD_READONLY) { if (c->cmd->flags & CMD_READONLY) {
client *caller = (c->flags & CLIENT_LUA && g_pserver->lua_caller) ? client *caller = (c->flags & CLIENT_LUA && g_pserver->lua_caller) ?
g_pserver->lua_caller : c; g_pserver->lua_caller : c;
if (caller->flags & CLIENT_TRACKING) if (caller->flags & CLIENT_TRACKING &&
!(caller->flags & CLIENT_TRACKING_BCAST))
{
trackingRememberKeys(caller); trackingRememberKeys(caller);
}
} }
g_pserver->stat_numcommands++; g_pserver->stat_numcommands++;
@ -3777,7 +3795,7 @@ int processCommand(client *c, int callFlags) {
/* Check if the user is authenticated. This check is skipped in case /* Check if the user is authenticated. This check is skipped in case
* the default user is flagged as "nopass" and is active. */ * the default user is flagged as "nopass" and is active. */
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) || int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
DefaultUser->flags & USER_FLAG_DISABLED) && (DefaultUser->flags & USER_FLAG_DISABLED)) &&
!c->authenticated; !c->authenticated;
if (auth_required) { if (auth_required) {
/* AUTH and HELLO and no auth modules are valid even in /* AUTH and HELLO and no auth modules are valid even in
@ -3793,8 +3811,11 @@ int processCommand(client *c, int callFlags) {
* ACLs. */ * ACLs. */
if (c->puser && !(c->puser->flags & USER_FLAG_ALLCOMMANDS)) if (c->puser && !(c->puser->flags & USER_FLAG_ALLCOMMANDS))
locker.arm(c); // ACLs require the lock locker.arm(c); // ACLs require the lock
int acl_retval = ACLCheckCommandPerm(c);
int acl_keypos;
int acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
if (acl_retval != ACL_OK) { if (acl_retval != ACL_OK) {
addACLLogEntry(c,acl_retval,acl_keypos,NULL);
flagTransaction(c); flagTransaction(c);
if (acl_retval == ACL_DENIED_CMD) if (acl_retval == ACL_DENIED_CMD)
addReplyErrorFormat(c, addReplyErrorFormat(c,
@ -3907,6 +3928,7 @@ int processCommand(client *c, int callFlags) {
!(c->flags & CLIENT_MASTER) && !(c->flags & CLIENT_MASTER) &&
c->cmd->flags & CMD_WRITE) c->cmd->flags & CMD_WRITE)
{ {
flagTransaction(c);
addReply(c, shared.roslaveerr); addReply(c, shared.roslaveerr);
return C_OK; return C_OK;
} }
@ -3920,7 +3942,10 @@ int processCommand(client *c, int callFlags) {
c->cmd->proc != unsubscribeCommand && c->cmd->proc != unsubscribeCommand &&
c->cmd->proc != psubscribeCommand && c->cmd->proc != psubscribeCommand &&
c->cmd->proc != punsubscribeCommand) { c->cmd->proc != punsubscribeCommand) {
addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context"); addReplyErrorFormat(c,
"Can't execute '%s': only (P)SUBSCRIBE / "
"(P)UNSUBSCRIBE / PING / QUIT are allowed in this context",
c->cmd->name);
return C_OK; return C_OK;
} }
@ -3948,11 +3973,19 @@ int processCommand(client *c, int callFlags) {
return C_OK; return C_OK;
} }
/* Lua script too slow? Only allow a limited number of commands. */ /* Lua script too slow? Only allow a limited number of commands.
* Note that we need to allow the transactions commands, otherwise clients
* sending a transaction with pipelining without error checking, may have
* the MULTI plus a few initial commands refused, then the timeout
* condition resolves, and the bottom-half of the transaction gets
* executed, see Github PR #7022. */
if (g_pserver->lua_timedout && if (g_pserver->lua_timedout &&
c->cmd->proc != authCommand && c->cmd->proc != authCommand &&
c->cmd->proc != helloCommand && c->cmd->proc != helloCommand &&
c->cmd->proc != replconfCommand && c->cmd->proc != replconfCommand &&
c->cmd->proc != multiCommand &&
c->cmd->proc != execCommand &&
c->cmd->proc != discardCommand &&
!(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') &&
@ -4209,6 +4242,7 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_SLOWLOG, "skip_slowlog"); flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_SLOWLOG, "skip_slowlog");
flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking"); flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking");
flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast"); flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast");
flagcount += addReplyCommandFlag(c,cmd,CMD_NO_AUTH, "no_auth");
if ((cmd->getkeys_proc && !(cmd->flags & CMD_MODULE)) || if ((cmd->getkeys_proc && !(cmd->flags & CMD_MODULE)) ||
cmd->flags & CMD_MODULE_GETKEYS) cmd->flags & CMD_MODULE_GETKEYS)
{ {
@ -4413,11 +4447,13 @@ sds genRedisInfoString(const char *section) {
"client_recent_max_output_buffer:%zu\r\n" "client_recent_max_output_buffer:%zu\r\n"
"blocked_clients:%d\r\n" "blocked_clients:%d\r\n"
"tracking_clients:%d\r\n" "tracking_clients:%d\r\n"
"clients_in_timeout_table:%" PRIu64 "\r\n"
"current_client_thread:%d\r\n", "current_client_thread:%d\r\n",
listLength(g_pserver->clients)-listLength(g_pserver->slaves), listLength(g_pserver->clients)-listLength(g_pserver->slaves),
maxin, maxout, maxin, maxout,
g_pserver->blocked_clients, g_pserver->blocked_clients,
g_pserver->tracking_clients, g_pserver->tracking_clients,
raxSize(g_pserver->clients_timeout_table),
static_cast<int>(serverTL - g_pserver->rgthreadvar)); static_cast<int>(serverTL - g_pserver->rgthreadvar));
for (int ithread = 0; ithread < cserver.cthreads; ++ithread) for (int ithread = 0; ithread < cserver.cthreads; ++ithread)
{ {
@ -4572,7 +4608,7 @@ sds genRedisInfoString(const char *section) {
"aof_last_cow_size:%zu\r\n" "aof_last_cow_size:%zu\r\n"
"module_fork_in_progress:%d\r\n" "module_fork_in_progress:%d\r\n"
"module_fork_last_cow_size:%zu\r\n", "module_fork_last_cow_size:%zu\r\n",
g_pserver->loading, g_pserver->loading.load(std::memory_order_relaxed),
g_pserver->dirty, g_pserver->dirty,
g_pserver->FRdbSaveInProgress(), g_pserver->FRdbSaveInProgress(),
(intmax_t)g_pserver->lastsave, (intmax_t)g_pserver->lastsave,
@ -4675,7 +4711,9 @@ sds genRedisInfoString(const char *section) {
"active_defrag_misses:%lld\r\n" "active_defrag_misses:%lld\r\n"
"active_defrag_key_hits:%lld\r\n" "active_defrag_key_hits:%lld\r\n"
"active_defrag_key_misses:%lld\r\n" "active_defrag_key_misses:%lld\r\n"
"tracking_used_slots:%llu\r\n", "tracking_total_keys:%lld\r\n"
"tracking_total_items:%llu\r\n"
"unexpected_error_replies:%lld\r\n",
g_pserver->stat_numconnections, g_pserver->stat_numconnections,
g_pserver->stat_numcommands, g_pserver->stat_numcommands,
getInstantaneousMetric(STATS_METRIC_COMMAND), getInstantaneousMetric(STATS_METRIC_COMMAND),
@ -4703,7 +4741,9 @@ sds genRedisInfoString(const char *section) {
g_pserver->stat_active_defrag_misses, g_pserver->stat_active_defrag_misses,
g_pserver->stat_active_defrag_key_hits, g_pserver->stat_active_defrag_key_hits,
g_pserver->stat_active_defrag_key_misses, g_pserver->stat_active_defrag_key_misses,
trackingGetUsedSlots()); (unsigned long long) trackingGetTotalKeys(),
(unsigned long long) trackingGetTotalItems(),
g_pserver->stat_unexpected_error_replies);
} }
/* Replication */ /* Replication */
@ -4831,6 +4871,7 @@ sds genRedisInfoString(const char *section) {
"master_replid:%s\r\n" "master_replid:%s\r\n"
"master_replid2:%s\r\n" "master_replid2:%s\r\n"
"master_repl_offset:%lld\r\n" "master_repl_offset:%lld\r\n"
"master_repl_meaningful_offset:%lld\r\n"
"second_repl_offset:%lld\r\n" "second_repl_offset:%lld\r\n"
"repl_backlog_active:%d\r\n" "repl_backlog_active:%d\r\n"
"repl_backlog_size:%lld\r\n" "repl_backlog_size:%lld\r\n"
@ -4839,6 +4880,7 @@ sds genRedisInfoString(const char *section) {
g_pserver->replid, g_pserver->replid,
g_pserver->replid2, g_pserver->replid2,
g_pserver->master_repl_offset, g_pserver->master_repl_offset,
g_pserver->master_repl_meaningful_offset,
g_pserver->second_replid_offset, g_pserver->second_replid_offset,
g_pserver->repl_backlog != NULL, g_pserver->repl_backlog != NULL,
g_pserver->repl_backlog_size, g_pserver->repl_backlog_size,
@ -5255,6 +5297,7 @@ void loadDataFromDisk(void) {
{ {
memcpy(g_pserver->replid,rsi.repl_id,sizeof(g_pserver->replid)); memcpy(g_pserver->replid,rsi.repl_id,sizeof(g_pserver->replid));
g_pserver->master_repl_offset = rsi.repl_offset; g_pserver->master_repl_offset = rsi.repl_offset;
g_pserver->master_repl_meaningful_offset = rsi.repl_offset;
listIter li; listIter li;
listNode *ln; listNode *ln;
@ -5467,8 +5510,12 @@ static void validateConfiguration()
} }
} }
bool initializeStorageProvider(const char **err); int iAmMaster(void) {
return ((!g_pserver->cluster_enabled && (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica)) ||
(g_pserver->cluster_enabled && nodeIsMaster(g_pserver->cluster->myself)));
}
bool initializeStorageProvider(const char **err);
int main(int argc, char **argv) { int main(int argc, char **argv) {
struct timeval tv; struct timeval tv;
int j; int j;
@ -5747,10 +5794,10 @@ int main(int argc, char **argv) {
#ifdef __linux__ #ifdef __linux__
cpu_set_t cpuset; cpu_set_t cpuset;
CPU_ZERO(&cpuset); CPU_ZERO(&cpuset);
CPU_SET(iel, &cpuset); CPU_SET(iel + cserver.threadAffinityOffset, &cpuset);
if (pthread_setaffinity_np(rgthread[iel], sizeof(cpu_set_t), &cpuset) == 0) if (pthread_setaffinity_np(rgthread[iel], sizeof(cpu_set_t), &cpuset) == 0)
{ {
serverLog(LOG_INFO, "Binding thread %d to cpu %d", iel, iel); serverLog(LOG_INFO, "Binding thread %d to cpu %d", iel, iel + cserver.threadAffinityOffset + 1);
} }
#else #else
serverLog(LL_WARNING, "CPU pinning not available on this platform"); serverLog(LL_WARNING, "CPU pinning not available on this platform");

View File

@ -474,7 +474,13 @@ public:
#define CLIENT_TRACKING (1ULL<<31) /* Client enabled keys tracking in order to #define CLIENT_TRACKING (1ULL<<31) /* Client enabled keys tracking in order to
perform client side caching. */ perform client side caching. */
#define CLIENT_TRACKING_BROKEN_REDIR (1ULL<<32) /* Target client is invalid. */ #define CLIENT_TRACKING_BROKEN_REDIR (1ULL<<32) /* Target client is invalid. */
#define CLIENT_FORCE_REPLY (1ULL<<33) /* Should addReply be forced to write the text? */ #define CLIENT_TRACKING_BCAST (1ULL<<33) /* Tracking in BCAST mode. */
#define CLIENT_TRACKING_OPTIN (1ULL<<34) /* Tracking in opt-in mode. */
#define CLIENT_TRACKING_OPTOUT (1ULL<<35) /* Tracking in opt-out mode. */
#define CLIENT_TRACKING_CACHING (1ULL<<36) /* CACHING yes/no was given,
depending on optin/optout mode. */
#define CLIENT_IN_TO_TABLE (1ULL<<37) /* This client is in the timeout table. */
#define CLIENT_FORCE_REPLY (1ULL<<38) /* 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. */
@ -569,7 +575,7 @@ public:
/* Anti-warning macro... */ /* Anti-warning macro... */
#define UNUSED(V) ((void) V) #define UNUSED(V) ((void) V)
#define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */ #define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */
#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */ #define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */
/* Append only defines */ /* Append only defines */
@ -623,6 +629,8 @@ public:
#define CMD_CALL_PROPAGATE_REPL (1<<3) #define CMD_CALL_PROPAGATE_REPL (1<<3)
#define CMD_CALL_PROPAGATE (CMD_CALL_PROPAGATE_AOF|CMD_CALL_PROPAGATE_REPL) #define CMD_CALL_PROPAGATE (CMD_CALL_PROPAGATE_AOF|CMD_CALL_PROPAGATE_REPL)
#define CMD_CALL_FULL (CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_PROPAGATE) #define CMD_CALL_FULL (CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_PROPAGATE)
#define CMD_CALL_NOWRAP (1<<4) /* Don't wrap also propagate array into
MULTI/EXEC: the caller will handle it. */
/* Command propagation flags, see propagate() function */ /* Command propagation flags, see propagate() function */
#define PROPAGATE_NONE 0 #define PROPAGATE_NONE 0
@ -647,8 +655,8 @@ public:
#define NOTIFY_EXPIRED (1<<8) /* x */ #define NOTIFY_EXPIRED (1<<8) /* x */
#define NOTIFY_EVICTED (1<<9) /* e */ #define NOTIFY_EVICTED (1<<9) /* e */
#define NOTIFY_STREAM (1<<10) /* t */ #define NOTIFY_STREAM (1<<10) /* t */
#define NOTIFY_KEY_MISS (1<<11) /* m */ #define NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from NOTIFY_ALL on purpose) */
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_KEY_MISS) /* A flag */ #define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */
/* Get the first bind addr or NULL */ /* Get the first bind addr or NULL */
#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)
@ -1674,7 +1682,9 @@ typedef struct client {
* invalidation messages for keys fetched by this client will be send to * invalidation messages for keys fetched by this client will be send to
* the specified client ID. */ * the specified client ID. */
uint64_t client_tracking_redirection; uint64_t client_tracking_redirection;
rax *client_tracking_prefixes; /* A dictionary of prefixes we are already
subscribed to in BCAST mode, in the
context of client side caching. */
/* Response buffer */ /* Response buffer */
int bufpos; int bufpos;
char buf[PROTO_REPLY_CHUNK_BYTES]; char buf[PROTO_REPLY_CHUNK_BYTES];
@ -1711,7 +1721,7 @@ struct sharedObjectsStruct {
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk, *busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink, *unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
*rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan, *rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan,
*multi, *exec, *multi, *exec, *srem, *hdel, *zrem,
*select[PROTO_SHARED_SELECT_CMDS], *select[PROTO_SHARED_SELECT_CMDS],
*integers[OBJ_SHARED_INTEGERS], *integers[OBJ_SHARED_INTEGERS],
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */ *mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
@ -1951,6 +1961,7 @@ struct redisServerConst {
int cthreads; /* Number of main worker threads */ int cthreads; /* Number of main worker threads */
int fThreadAffinity; /* Should we pin threads to cores? */ int fThreadAffinity; /* Should we pin threads to cores? */
int threadAffinityOffset = 0; /* Where should we start pinning them? */
char *pidfile; /* PID file path */ char *pidfile; /* PID file path */
/* Fast pointers to often looked up command */ /* Fast pointers to often looked up command */
@ -1958,7 +1969,8 @@ struct redisServerConst {
*lpopCommand, *rpopCommand, *zpopminCommand, *lpopCommand, *rpopCommand, *zpopminCommand,
*zpopmaxCommand, *sremCommand, *execCommand, *zpopmaxCommand, *sremCommand, *execCommand,
*expireCommand, *pexpireCommand, *xclaimCommand, *expireCommand, *pexpireCommand, *xclaimCommand,
*xgroupCommand, *rreplayCommand, *rpoplpushCommand; *xgroupCommand, *rreplayCommand, *rpoplpushCommand,
*hdelCommand, *zremCommand;
/* Configuration */ /* Configuration */
char *default_masteruser; /* AUTH with this user and masterauth with master */ char *default_masteruser; /* AUTH with this user and masterauth with master */
@ -2008,7 +2020,7 @@ struct redisServer {
struct redisServerThreadVars rgthreadvar[MAX_EVENT_LOOPS]; struct redisServerThreadVars rgthreadvar[MAX_EVENT_LOOPS];
std::atomic<unsigned int> lruclock; /* Clock for LRU eviction */ std::atomic<unsigned int> lruclock; /* Clock for LRU eviction */
int shutdown_asap; /* SHUTDOWN needed ASAP */ std::atomic<int> shutdown_asap; /* SHUTDOWN needed ASAP */
int activerehashing; /* Incremental rehash in serverCron() */ int activerehashing; /* Incremental rehash in serverCron() */
int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */ int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */
int cronloops; /* Number of times the cron function run */ int cronloops; /* Number of times the cron function run */
@ -2036,13 +2048,14 @@ struct redisServer {
list *clients; /* List of active clients */ list *clients; /* List of active clients */
list *clients_to_close; /* Clients to close asynchronously */ list *clients_to_close; /* Clients to close asynchronously */
list *slaves, *monitors; /* List of slaves and MONITORs */ list *slaves, *monitors; /* List of slaves and MONITORs */
rax *clients_timeout_table; /* Radix tree for blocked clients timeouts. */
rax *clients_index; /* Active clients dictionary by client ID. */ rax *clients_index; /* Active clients dictionary by client ID. */
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */ mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
dict *migrate_cached_sockets;/* MIGRATE cached sockets */ dict *migrate_cached_sockets;/* MIGRATE cached sockets */
std::atomic<uint64_t> next_client_id; /* Next client unique ID. Incremental. */ std::atomic<uint64_t> next_client_id; /* Next client unique ID. Incremental. */
int protected_mode; /* Don't accept external connections. */ int protected_mode; /* Don't accept external connections. */
/* RDB / AOF loading information */ /* RDB / AOF loading information */
int loading; /* We are loading data from disk if true */ std::atomic<int> loading; /* We are loading data from disk if true */
off_t loading_total_bytes; off_t loading_total_bytes;
off_t loading_loaded_bytes; off_t loading_loaded_bytes;
time_t loading_start_time; time_t loading_start_time;
@ -2082,6 +2095,7 @@ struct redisServer {
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. */
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */ size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */ size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
/* The following two are used to track instantaneous metrics, like /* The following two are used to track instantaneous metrics, like
* number of operations per second, network traffic. */ * number of operations per second, network traffic. */
struct { struct {
@ -2138,7 +2152,7 @@ struct redisServer {
struct _rdbThreadVars struct _rdbThreadVars
{ {
bool fRdbThreadActive = false; bool fRdbThreadActive = false;
volatile bool fRdbThreadCancel = false; std::atomic<bool> fRdbThreadCancel {false};
pthread_t rdb_child_thread; /* PID of RDB saving child */ pthread_t rdb_child_thread; /* PID of RDB saving child */
int tmpfileNum = 0; int tmpfileNum = 0;
} rdbThreadVars; } rdbThreadVars;
@ -2148,6 +2162,8 @@ struct redisServer {
char *rdb_s3bucketpath; /* Path for AWS S3 backup of RDB file */ char *rdb_s3bucketpath; /* Path for AWS S3 backup of RDB file */
int rdb_compression; /* Use compression in RDB? */ int rdb_compression; /* Use compression in RDB? */
int rdb_checksum; /* Use RDB checksum? */ int rdb_checksum; /* Use RDB checksum? */
int rdb_del_sync_files; /* Remove RDB files used only for SYNC if
the instance does not use persistence. */
time_t lastsave; /* Unix time of last successful save */ time_t lastsave; /* Unix time of last successful save */
time_t lastbgsave_try; /* Unix time of last attempted bgsave */ time_t lastbgsave_try; /* Unix time of last attempted bgsave */
time_t rdb_save_time_last; /* Time used by last RDB save run. */ time_t rdb_save_time_last; /* Time used by last RDB save run. */
@ -2185,6 +2201,7 @@ struct redisServer {
char replid[CONFIG_RUN_ID_SIZE+1]; /* My current replication ID. */ char replid[CONFIG_RUN_ID_SIZE+1]; /* My current replication ID. */
char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from master*/ char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from master*/
long long master_repl_offset; /* My current replication offset */ long long master_repl_offset; /* My current replication offset */
long long master_repl_meaningful_offset; /* Offset minus latest PINGs. */
long long second_replid_offset; /* Accept offsets up to this for replid2. */ long long second_replid_offset; /* Accept offsets up to this for replid2. */
int replicaseldb; /* Last SELECTed DB in replication output */ int replicaseldb; /* Last SELECTed DB in replication output */
int repl_ping_slave_period; /* Master pings the replica every N seconds */ int repl_ping_slave_period; /* Master pings the replica every N seconds */
@ -2240,7 +2257,7 @@ struct redisServer {
list *ready_keys; /* List of readyList structures for BLPOP & co */ list *ready_keys; /* List of readyList structures for BLPOP & co */
/* Client side caching. */ /* Client side caching. */
unsigned int tracking_clients; /* # of clients with tracking enabled.*/ unsigned int tracking_clients; /* # of clients with tracking enabled.*/
int tracking_table_max_fill; /* Max fill percentage. */ size_t tracking_table_max_keys; /* Max number of keys in tracking table. */
/* Sort parameters - qsort_r() is only available under BSD so we /* Sort parameters - qsort_r() is only available under BSD so we
* have to take this state global, in order to pass it to sortCompare() */ * have to take this state global, in order to pass it to sortCompare() */
int sort_desc; int sort_desc;
@ -2268,6 +2285,7 @@ struct redisServer {
/* Pubsub */ /* Pubsub */
dict *pubsub_channels; /* Map channels to list of subscribed clients */ dict *pubsub_channels; /* Map channels to list of subscribed clients */
list *pubsub_patterns; /* A list of pubsub_patterns */ list *pubsub_patterns; /* A list of pubsub_patterns */
dict *pubsub_patterns_dict; /* A dict of pubsub_patterns */
int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
xor of NOTIFY_... flags. */ xor of NOTIFY_... flags. */
/* Cluster */ /* Cluster */
@ -2318,6 +2336,10 @@ struct redisServer {
dict *latency_events; dict *latency_events;
/* ACLs */ /* ACLs */
char *acl_filename; /* ACL Users file. NULL if not configured. */ char *acl_filename; /* ACL Users file. NULL if not configured. */
unsigned long acllog_max_len; /* Maximum length of the ACL LOG list. */
sds requirepass; /* Remember the cleartext password set with the
old "requirepass" directive for backward
compatibility with Redis <= 5. */
/* Assert & bug reporting */ /* Assert & bug reporting */
const char *assert_failed; const char *assert_failed;
const char *assert_file; const char *assert_file;
@ -2589,7 +2611,7 @@ void pauseClients(mstime_t duration);
int clientsArePaused(void); int clientsArePaused(void);
void unpauseClientsIfNecessary(); void unpauseClientsIfNecessary();
int processEventsWhileBlocked(int iel); int processEventsWhileBlocked(int iel);
int handleClientsWithPendingWrites(int iel); int handleClientsWithPendingWrites(int iel, int aof_state);
int clientHasPendingReplies(client *c); int clientHasPendingReplies(client *c);
void unlinkClient(client *c); void unlinkClient(client *c);
int writeToClient(client *c, int handler_installed); int writeToClient(client *c, int handler_installed);
@ -2628,13 +2650,15 @@ void addReplyStatusFormat(client *c, const char *fmt, ...);
#endif #endif
/* Client side caching (tracking mode) */ /* Client side caching (tracking mode) */
void enableTracking(client *c, uint64_t redirect_to); void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix);
void disableTracking(client *c); void disableTracking(client *c);
void trackingRememberKeys(client *c); void trackingRememberKeys(client *c);
void trackingInvalidateKey(robj *keyobj); void trackingInvalidateKey(robj *keyobj);
void trackingInvalidateKeysOnFlush(int dbid); void trackingInvalidateKeysOnFlush(int dbid);
void trackingLimitUsedSlots(void); void trackingLimitUsedSlots(void);
unsigned long long trackingGetUsedSlots(void); uint64_t trackingGetTotalItems(void);
uint64_t trackingGetTotalKeys(void);
void trackingBroadcastInvalidationMessages(void);
/* List data type */ /* List data type */
void listTypeTryConversion(robj *subject, robj *value); void listTypeTryConversion(robj *subject, robj *value);
@ -2768,6 +2792,7 @@ void loadingProgress(off_t pos);
void stopLoading(int success); void stopLoading(int success);
void startSaving(int rdbflags); void startSaving(int rdbflags);
void stopSaving(int success); void stopSaving(int success);
int allPersistenceDisabled(void);
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */ #define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */ #define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
@ -2812,11 +2837,12 @@ void ACLInit(void);
#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
#define ACL_DENIED_AUTH 3 /* Only used for ACL LOG entries. */
int ACLCheckUserCredentials(robj *username, robj *password); int ACLCheckUserCredentials(robj *username, robj *password);
int ACLAuthenticateUser(client *c, robj *username, robj *password); int ACLAuthenticateUser(client *c, robj *username, robj *password);
unsigned long ACLGetCommandID(const char *cmdname); unsigned long ACLGetCommandID(const char *cmdname);
user *ACLGetUserByName(const char *name, size_t namelen); user *ACLGetUserByName(const char *name, size_t namelen);
int ACLCheckCommandPerm(client *c); int ACLCheckCommandPerm(client *c, int *keyidxptr);
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);
@ -2828,6 +2854,7 @@ void ACLLoadUsersAtStartup(void);
void addReplyCommandCategories(client *c, struct redisCommand *cmd); void addReplyCommandCategories(client *c, struct redisCommand *cmd);
user *ACLCreateUnlinkedUser(); user *ACLCreateUnlinkedUser();
void ACLFreeUserAndKillClients(user *u); void ACLFreeUserAndKillClients(user *u);
void addACLLogEntry(client *c, int reason, int keypos, sds username);
/* Sorted sets data type */ /* Sorted sets data type */
@ -3007,6 +3034,7 @@ void initConfigValues();
int removeExpire(redisDb *db, robj *key); int removeExpire(redisDb *db, robj *key);
int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey); int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey);
void propagateExpire(redisDb *db, robj *key, int lazy); void propagateExpire(redisDb *db, robj *key, int lazy);
void propagateSubkeyExpire(redisDb *db, int type, robj *key, robj *subkey);
int expireIfNeeded(redisDb *db, robj *key); int expireIfNeeded(redisDb *db, robj *key);
void setExpire(client *c, redisDb *db, robj *key, robj *subkey, long long when); void setExpire(client *c, redisDb *db, robj *key, robj *subkey, long long when);
void setExpire(client *c, redisDb *db, robj *key, expireEntry &&entry); void setExpire(client *c, redisDb *db, robj *key, expireEntry &&entry);
@ -3037,6 +3065,7 @@ int dbnumFromDb(redisDb *db);
#define EMPTYDB_NO_FLAGS 0 /* No flags. */ #define EMPTYDB_NO_FLAGS 0 /* No flags. */
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */ #define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
#define EMPTYDB_BACKUP (1<<2) /* DB array is a backup for REPL_DISKLESS_LOAD_SWAPDB. */
long long emptyDb(int dbnum, int flags, void(callback)(void*)); long long emptyDb(int dbnum, int flags, void(callback)(void*));
long long emptyDbGeneric(redisDb **dbarray, int dbnum, int flags, void(callback)(void*)); long long emptyDbGeneric(redisDb **dbarray, int dbnum, int flags, void(callback)(void*));
void flushAllDataAndResetRDB(int flags); void flushAllDataAndResetRDB(int flags);
@ -3068,6 +3097,7 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys); int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys); int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys); int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
/* Cluster */ /* Cluster */
void clusterInit(void); void clusterInit(void);
@ -3111,6 +3141,12 @@ void handleClientsBlockedOnKeys(void);
void signalKeyAsReady(redisDb *db, robj *key); void signalKeyAsReady(redisDb *db, robj *key);
void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, streamID *ids); void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, streamID *ids);
/* timeout.c -- Blocked clients timeout and connections timeout. */
void addClientToTimeoutTable(client *c);
void removeClientFromTimeoutTable(client *c);
void handleBlockedClientsTimeout(void);
int clientsCronHandleTimeout(client *c, mstime_t now_ms);
/* expire.c -- Handling of expired keys */ /* expire.c -- Handling of expired keys */
void activeExpireCycle(int type); void activeExpireCycle(int type);
void expireSlaveKeys(void); void expireSlaveKeys(void);
@ -3136,6 +3172,8 @@ extern "C" char *redisGitDirty(void);
extern "C" uint64_t redisBuildId(void); extern "C" uint64_t redisBuildId(void);
extern "C" char *redisBuildIdString(void); extern "C" char *redisBuildIdString(void);
int parseUnitString(const char *sz);
/* Commands prototypes */ /* Commands prototypes */
void authCommand(client *c); void authCommand(client *c);
void pingCommand(client *c); void pingCommand(client *c);
@ -3152,6 +3190,7 @@ void existsCommand(client *c);
void setbitCommand(client *c); void setbitCommand(client *c);
void getbitCommand(client *c); void getbitCommand(client *c);
void bitfieldCommand(client *c); void bitfieldCommand(client *c);
void bitfieldroCommand(client *c);
void setrangeCommand(client *c); void setrangeCommand(client *c);
void getrangeCommand(client *c); void getrangeCommand(client *c);
void incrCommand(client *c); void incrCommand(client *c);
@ -3213,6 +3252,7 @@ void expireCommand(client *c);
void expireatCommand(client *c); void expireatCommand(client *c);
void expireMemberCommand(client *c); void expireMemberCommand(client *c);
void expireMemberAtCommand(client *c); void expireMemberAtCommand(client *c);
void pexpireMemberAtCommand(client *c);
void pexpireCommand(client *c); void pexpireCommand(client *c);
void pexpireatCommand(client *c); void pexpireatCommand(client *c);
void getsetCommand(client *c); void getsetCommand(client *c);
@ -3414,4 +3454,6 @@ class ShutdownException
#define redisDebugMark() \ #define redisDebugMark() \
printf("-- MARK %s:%d --\n", __FILE__, __LINE__) printf("-- MARK %s:%d --\n", __FILE__, __LINE__)
int iAmMaster(void);
#endif #endif

View File

@ -98,7 +98,7 @@ struct client;
stream *streamNew(void); stream *streamNew(void);
void freeStream(stream *s); void freeStream(stream *s);
unsigned long streamLength(const robj *subject); unsigned long streamLength(robj_roptr subject);
size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi); size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi);
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev); void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev);
int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields); int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);

View File

@ -420,7 +420,7 @@ void spopWithCountCommand(client *c) {
/* Make sure a key with the name inputted exists, and that it's type is /* Make sure a key with the name inputted exists, and that it's type is
* indeed a set. Otherwise, return nil */ * indeed a set. Otherwise, return nil */
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp])) if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.emptyset[c->resp]))
== NULL || checkType(c,set,OBJ_SET)) return; == NULL || checkType(c,set,OBJ_SET)) return;
/* If count is zero, serve an empty set ASAP to avoid special /* If count is zero, serve an empty set ASAP to avoid special

View File

@ -68,7 +68,7 @@ void freeStream(stream *s) {
} }
/* Return the length of a stream. */ /* Return the length of a stream. */
unsigned long streamLength(const robj *subject) { unsigned long streamLength(robj_roptr subject) {
stream *s = (stream*)ptrFromObj(subject); stream *s = (stream*)ptrFromObj(subject);
return s->length; return s->length;
} }
@ -853,7 +853,7 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
argv[11] = createStringObject("JUSTID",6); argv[11] = createStringObject("JUSTID",6);
argv[12] = createStringObject("LASTID",6); argv[12] = createStringObject("LASTID",6);
argv[13] = createObjectFromStreamID(&group->last_id); argv[13] = createObjectFromStreamID(&group->last_id);
propagate(cserver.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL); alsoPropagate(cserver.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]); decrRefCount(argv[0]);
decrRefCount(argv[3]); decrRefCount(argv[3]);
decrRefCount(argv[4]); decrRefCount(argv[4]);
@ -880,7 +880,7 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
argv[2] = key; argv[2] = key;
argv[3] = groupname; argv[3] = groupname;
argv[4] = createObjectFromStreamID(&group->last_id); argv[4] = createObjectFromStreamID(&group->last_id);
propagate(cserver.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL); alsoPropagate(cserver.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]); decrRefCount(argv[0]);
decrRefCount(argv[1]); decrRefCount(argv[1]);
decrRefCount(argv[4]); decrRefCount(argv[4]);
@ -1073,9 +1073,7 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
* by the user by other means. In that case we signal it emitting * by the user by other means. In that case we signal it emitting
* the ID but then a NULL entry for the fields. */ * the ID but then a NULL entry for the fields. */
addReplyArrayLen(c,2); addReplyArrayLen(c,2);
streamID id; addReplyStreamID(c,&thisid);
streamDecodeID(ri.key,&id);
addReplyStreamID(c,&id);
addReplyNullArray(c); addReplyNullArray(c);
} else { } else {
streamNACK *nack = (streamNACK*)ri.data; streamNACK *nack = (streamNACK*)ri.data;
@ -1856,6 +1854,8 @@ NULL
g_pserver->dirty++; g_pserver->dirty++;
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-destroy", notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-destroy",
c->argv[2],c->db->id); c->argv[2],c->db->id);
/* We want to unblock any XREADGROUP consumers with -NOGROUP. */
signalKeyAsReady(c->db,c->argv[2]);
} else { } else {
addReply(c,shared.czero); addReply(c,shared.czero);
} }

192
src/timeout.cpp Normal file
View File

@ -0,0 +1,192 @@
/* Copyright (c) 2009-2020, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "server.h"
#include "cluster.h"
#include <mutex>
/* ========================== Clients timeouts ============================= */
/* Check if this blocked client timedout (does nothing if the client is
* not blocked right now). If so send a reply, unblock it, and return 1.
* Otherwise 0 is returned and no operation is performed. */
int checkBlockedClientTimeout(client *c, mstime_t now) {
if (c->flags & CLIENT_BLOCKED &&
c->bpop.timeout != 0
&& c->bpop.timeout < now)
{
/* Handle blocking operation specific timeout. */
replyToBlockedClientTimedOut(c);
unblockClient(c);
return 1;
} else {
return 0;
}
}
/* Check for timeouts. Returns non-zero if the client was terminated.
* The function gets the current time in milliseconds as argument since
* it gets called multiple times in a loop, so calling gettimeofday() for
* each iteration would be costly without any actual gain. */
int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
time_t now = now_ms/1000;
if (cserver.maxidletime &&
/* This handles the idle clients connection timeout if set. */
!(c->flags & CLIENT_SLAVE) && /* No timeout for slaves and monitors */
!(c->flags & CLIENT_MASTER) && /* No timeout for masters */
!(c->flags & CLIENT_BLOCKED) && /* No timeout for BLPOP */
!(c->flags & CLIENT_PUBSUB) && /* No timeout for Pub/Sub clients */
(now - c->lastinteraction > cserver.maxidletime))
{
serverLog(LL_VERBOSE,"Closing idle client");
freeClient(c);
return 1;
} else if (c->flags & CLIENT_BLOCKED) {
/* Cluster: handle unblock & redirect of clients blocked
* into keys no longer served by this server. */
if (g_pserver->cluster_enabled) {
if (clusterRedirectBlockedClientIfNeeded(c))
unblockClient(c);
}
}
return 0;
}
/* For blocked clients timeouts we populate a radix tree of 128 bit keys
* composed as such:
*
* [8 byte big endian expire time]+[8 byte client ID]
*
* We don't do any cleanup in the Radix tree: when we run the clients that
* reached the timeout already, if they are no longer existing or no longer
* blocked with such timeout, we just go forward.
*
* Every time a client blocks with a timeout, we add the client in
* the tree. In beforeSleep() we call clientsHandleTimeout() to run
* the tree and unblock the clients. */
#define CLIENT_ST_KEYLEN 16 /* 8 bytes mstime + 8 bytes client ID. */
/* Given client ID and timeout, write the resulting radix tree key in buf. */
void encodeTimeoutKey(unsigned char *buf, uint64_t timeout, client *c) {
timeout = htonu64(timeout);
memcpy(buf,&timeout,sizeof(timeout));
memcpy(buf+8,&c,sizeof(c));
if (sizeof(c) == 4) memset(buf+12,0,4); /* Zero padding for 32bit target. */
}
/* Given a key encoded with encodeTimeoutKey(), resolve the fields and write
* the timeout into *toptr and the client pointer into *cptr. */
void decodeTimeoutKey(unsigned char *buf, uint64_t *toptr, client **cptr) {
memcpy(toptr,buf,sizeof(*toptr));
*toptr = ntohu64(*toptr);
memcpy(cptr,buf+8,sizeof(*cptr));
}
/* Add the specified client id / timeout as a key in the radix tree we use
* to handle blocked clients timeouts. The client is not added to the list
* if its timeout is zero (block forever). */
void addClientToTimeoutTable(client *c) {
if (c->bpop.timeout == 0) return;
uint64_t timeout = c->bpop.timeout;
unsigned char buf[CLIENT_ST_KEYLEN];
encodeTimeoutKey(buf,timeout,c);
if (raxTryInsert(g_pserver->clients_timeout_table,buf,sizeof(buf),NULL,NULL))
c->flags |= CLIENT_IN_TO_TABLE;
}
/* Remove the client from the table when it is unblocked for reasons
* different than timing out. */
void removeClientFromTimeoutTable(client *c) {
if (!(c->flags & CLIENT_IN_TO_TABLE)) return;
c->flags &= ~CLIENT_IN_TO_TABLE;
uint64_t timeout = c->bpop.timeout;
unsigned char buf[CLIENT_ST_KEYLEN];
encodeTimeoutKey(buf,timeout,c);
raxRemove(g_pserver->clients_timeout_table,buf,sizeof(buf),NULL);
}
/* This function is called in beforeSleep() in order to unblock clients
* that are waiting in blocking operations with a timeout set. */
void handleBlockedClientsTimeout(void) {
if (raxSize(g_pserver->clients_timeout_table) == 0) return;
uint64_t now = mstime();
raxIterator ri;
raxStart(&ri,g_pserver->clients_timeout_table);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
uint64_t timeout;
client *c;
decodeTimeoutKey(ri.key,&timeout,&c);
if (timeout >= now) break; /* All the timeouts are in the future. */
std::unique_lock<fastlock> lock(c->lock);
c->flags &= ~CLIENT_IN_TO_TABLE;
checkBlockedClientTimeout(c,now);
raxRemove(g_pserver->clients_timeout_table,ri.key,ri.key_len,NULL);
raxSeek(&ri,"^",NULL,0);
}
}
/* Get a timeout value from an object and store it into 'timeout'.
* The final timeout is always stored as milliseconds as a time where the
* timeout will expire, however the parsing is performed according to
* the 'unit' that can be seconds or milliseconds.
*
* Note that if the timeout is zero (usually from the point of view of
* commands API this means no timeout) the value stored into 'timeout'
* is zero. */
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit) {
long long tval;
long double ftval;
if (unit == UNIT_SECONDS) {
if (getLongDoubleFromObjectOrReply(c,object,&ftval,
"timeout is not an float or out of range") != C_OK)
return C_ERR;
tval = (long long) (ftval * 1000.0);
} else {
if (getLongLongFromObjectOrReply(c,object,&tval,
"timeout is not an integer or out of range") != C_OK)
return C_ERR;
}
if (tval < 0) {
addReplyErrorAsync(c,"timeout is negative");
return C_ERR;
}
if (tval > 0) {
tval += mstime();
}
*timeout = tval;
return C_OK;
}

View File

@ -30,39 +30,34 @@
#include "server.h" #include "server.h"
/* The tracking table is constituted by 2^24 radix trees (each tree, and the /* The tracking table is constituted by a radix tree of keys, each pointing
* table itself, are allocated in a lazy way only when needed) tracking * to a radix tree of client IDs, used to track the clients that may have
* clients that may have certain keys in their local, client side, cache. * certain keys in their local, client side, cache.
*
* Keys are grouped into 2^24 slots, in a way similar to Redis Cluster hash
* slots, however here the function we use is crc64, taking the least
* significant 24 bits of the output.
* *
* When a client enables tracking with "CLIENT TRACKING on", each key served to * When a client enables tracking with "CLIENT TRACKING on", each key served to
* the client is hashed to one of such slots, and Redis will remember what * the client is remembered in the table mapping the keys to the client IDs.
* client may have keys about such slot. Later, when a key in a given slot is * Later, when a key is modified, all the clients that may have local copy
* modified, all the clients that may have local copies of keys in that slot * of such key will receive an invalidation message.
* will receive an invalidation message. There is no distinction of database
* number: a single table is used.
* *
* Clients will normally take frequently requested objects in memory, removing * Clients will normally take frequently requested objects in memory, removing
* them when invalidation messages are received. A strategy clients may use is * them when invalidation messages are received. */
* to just cache objects in a dictionary, associating to each cached object rax *TrackingTable = NULL;
* some incremental epoch, or just a timestamp. When invalidation messages are rax *PrefixTable = NULL;
* received clients may store, in a different table, the timestamp (or epoch) uint64_t TrackingTableTotalItems = 0; /* Total number of IDs stored across
* of the invalidation of such given slot: later when accessing objects, the the whole tracking table. This gives
* eviction of stale objects may be performed in a lazy way by checking if the an hint about the total memory we
* cached object timestamp is older than the invalidation timestamp for such are using server side for CSC. */
* objects.
*
* The output of the 24 bit hash function is very large (more than 16 million
* possible slots), so clients that may want to use less resources may only
* use the most significant bits instead of the full 24 bits. */
#define TRACKING_TABLE_SIZE (1<<24)
rax **TrackingTable = NULL;
unsigned long TrackingTableUsedSlots = 0;
robj *TrackingChannelName; robj *TrackingChannelName;
/* This is the structure that we have as value of the PrefixTable, and
* represents the list of keys modified, and the list of clients that need
* to be notified, for a given prefix. */
typedef struct bcastState {
rax *keys; /* Keys modified in the current event loop cycle. */
rax *clients; /* Clients subscribed to the notification events for this
prefix. */
} bcastState;
/* Remove the tracking state from the client 'c'. Note that there is not much /* Remove the tracking state from the client 'c'. Note that there is not much
* to do for us here, if not to decrement the counter of the clients in * to do for us here, if not to decrement the counter of the clients in
* tracking mode, because we just store the ID of the client in the tracking * tracking mode, because we just store the ID of the client in the tracking
@ -70,9 +65,56 @@ robj *TrackingChannelName;
* client with many entries in the table is removed, it would cost a lot of * client with many entries in the table is removed, it would cost a lot of
* time to do the cleanup. */ * time to do the cleanup. */
void disableTracking(client *c) { void disableTracking(client *c) {
/* If this client is in broadcasting mode, we need to unsubscribe it
* from all the prefixes it is registered to. */
if (c->flags & CLIENT_TRACKING_BCAST) {
raxIterator ri;
raxStart(&ri,c->client_tracking_prefixes);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
bcastState *bs = (bcastState*)raxFind(PrefixTable,ri.key,ri.key_len);
serverAssert(bs != raxNotFound);
raxRemove(bs->clients,(unsigned char*)&c,sizeof(c),NULL);
/* Was it the last client? Remove the prefix from the
* table. */
if (raxSize(bs->clients) == 0) {
raxFree(bs->clients);
raxFree(bs->keys);
zfree(bs);
raxRemove(PrefixTable,ri.key,ri.key_len,NULL);
}
}
raxStop(&ri);
raxFree(c->client_tracking_prefixes);
c->client_tracking_prefixes = NULL;
}
/* Clear flags and adjust the count. */
if (c->flags & CLIENT_TRACKING) { if (c->flags & CLIENT_TRACKING) {
g_pserver->tracking_clients--; g_pserver->tracking_clients--;
c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR); c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR|
CLIENT_TRACKING_BCAST|CLIENT_TRACKING_OPTIN|
CLIENT_TRACKING_OPTOUT|CLIENT_TRACKING_CACHING);
}
}
/* Set the client 'c' to track the prefix 'prefix'. If the client 'c' is
* already registered for the specified prefix, no operation is performed. */
void enableBcastTrackingForPrefix(client *c, const char *prefix, size_t plen) {
bcastState *bs = (bcastState*)raxFind(PrefixTable,(unsigned char*)prefix,sdslen(prefix));
/* If this is the first client subscribing to such prefix, create
* the prefix in the table. */
if (bs == raxNotFound) {
bs = (bcastState*)zmalloc(sizeof(*bs));
bs->keys = raxNew();
bs->clients = raxNew();
raxInsert(PrefixTable,(unsigned char*)prefix,plen,bs,NULL);
}
if (raxTryInsert(bs->clients,(unsigned char*)&c,sizeof(c),NULL,NULL)) {
if (c->client_tracking_prefixes == NULL)
c->client_tracking_prefixes = raxNew();
raxInsert(c->client_tracking_prefixes,
(unsigned char*)prefix,plen,NULL,NULL);
} }
} }
@ -83,44 +125,74 @@ void disableTracking(client *c) {
* eventually get freed, we'll send a message to the original client to * eventually get freed, we'll send a message to the original client to
* inform it of the condition. Multiple clients can redirect the invalidation * inform it of the condition. Multiple clients can redirect the invalidation
* messages to the same client ID. */ * messages to the same client ID. */
void enableTracking(client *c, uint64_t redirect_to) { void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix) {
if (c->flags & CLIENT_TRACKING) return; if (!(c->flags & CLIENT_TRACKING)) g_pserver->tracking_clients++;
c->flags |= CLIENT_TRACKING; c->flags |= CLIENT_TRACKING;
c->flags &= ~CLIENT_TRACKING_BROKEN_REDIR; c->flags &= ~(CLIENT_TRACKING_BROKEN_REDIR|CLIENT_TRACKING_BCAST|
CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
c->client_tracking_redirection = redirect_to; c->client_tracking_redirection = redirect_to;
g_pserver->tracking_clients++;
if (TrackingTable == NULL) { if (TrackingTable == NULL) {
TrackingTable = (rax**)zcalloc(sizeof(rax*) * TRACKING_TABLE_SIZE, MALLOC_LOCAL); TrackingTable = raxNew();
PrefixTable = raxNew();
TrackingChannelName = createStringObject("__redis__:invalidate",20); TrackingChannelName = createStringObject("__redis__:invalidate",20);
} }
if (options & CLIENT_TRACKING_BCAST) {
c->flags |= CLIENT_TRACKING_BCAST;
if (numprefix == 0) enableBcastTrackingForPrefix(c,"",0);
for (size_t j = 0; j < numprefix; j++) {
sds sdsprefix = szFromObj(prefix[j]);
enableBcastTrackingForPrefix(c,sdsprefix,sdslen(sdsprefix));
}
}
c->flags |= options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
} }
/* This function is called after the excution of a readonly command in the /* This function is called after the execution of a readonly command in the
* case the client 'c' has keys tracking enabled. It will populate the * case the client 'c' has keys tracking enabled and the tracking is not
* tracking ivalidation table according to the keys the user fetched, so that * in BCAST mode. It will populate the tracking invalidation table according
* Redis will know what are the clients that should receive an invalidation * to the keys the user fetched, so that Redis will know what are the clients
* message with certain groups of keys are modified. */ * that should receive an invalidation message with certain groups of keys
* are modified. */
void trackingRememberKeys(client *c) { void trackingRememberKeys(client *c) {
/* Return if we are in optin/out mode and the right CACHING command
* was/wasn't given in order to modify the default behavior. */
uint64_t optin = c->flags & CLIENT_TRACKING_OPTIN;
uint64_t optout = c->flags & CLIENT_TRACKING_OPTOUT;
uint64_t caching_given = c->flags & CLIENT_TRACKING_CACHING;
if ((optin && !caching_given) || (optout && caching_given)) return;
int numkeys; int numkeys;
int *keys = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys); int *keys = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
if (keys == NULL) return; if (keys == NULL) return;
for(int j = 0; j < numkeys; j++) { for(int j = 0; j < numkeys; j++) {
int idx = keys[j]; int idx = keys[j];
sds sdskey = (sds)ptrFromObj(c->argv[idx]); sds sdskey = szFromObj(c->argv[idx]);
uint64_t hash = crc64(0, rax *ids = (rax*)raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1); if (ids == raxNotFound) {
if (TrackingTable[hash] == NULL) { ids = raxNew();
TrackingTable[hash] = raxNew(); int inserted = raxTryInsert(TrackingTable,(unsigned char*)sdskey,
TrackingTableUsedSlots++; sdslen(sdskey),ids, NULL);
serverAssert(inserted == 1);
} }
raxTryInsert(TrackingTable[hash], if (raxTryInsert(ids,(unsigned char*)&c->id,sizeof(c->id),NULL,NULL))
(unsigned char*)&c->id,sizeof(c->id),NULL,NULL); TrackingTableTotalItems++;
} }
getKeysFreeResult(keys); getKeysFreeResult(keys);
} }
void sendTrackingMessage(client *c, long long hash) { /* Given a key name, this function sends an invalidation message in the
* proper channel (depending on RESP version: PubSub or Push message) and
* to the proper client (in case fo redirection), in the context of the
* client 'c' with tracking enabled.
*
* In case the 'proto' argument is non zero, the function will assume that
* 'keyname' points to a buffer of 'keylen' bytes already expressed in the
* form of Redis RESP protocol, representing an array of keys to send
* to the client as value of the invalidation. This is used in BCAST mode
* in order to optimized the implementation to use less CPU time. */
void sendTrackingMessage(client *c, const char *keyname, size_t keylen, int proto) {
int using_redirection = 0; int using_redirection = 0;
if (c->client_tracking_redirection) { if (c->client_tracking_redirection) {
client *redir = lookupClientByID(c->client_tracking_redirection); client *redir = lookupClientByID(c->client_tracking_redirection);
@ -146,36 +218,45 @@ void sendTrackingMessage(client *c, long long hash) {
if (c->resp > 2) { if (c->resp > 2) {
addReplyPushLen(c,2); addReplyPushLen(c,2);
addReplyBulkCBuffer(c,"invalidate",10); addReplyBulkCBuffer(c,"invalidate",10);
addReplyLongLong(c,hash);
} else if (using_redirection && c->flags & CLIENT_PUBSUB) { } else if (using_redirection && c->flags & CLIENT_PUBSUB) {
robj *msg = createStringObjectFromLongLong(hash); /* We use a static object to speedup things, however we assume
addReplyPubsubMessage(c,TrackingChannelName,msg); * that addReplyPubsubMessage() will not take a reference. */
decrRefCount(msg); addReplyPubsubMessage(c,TrackingChannelName,NULL);
} else {
/* If are here, the client is not using RESP3, nor is
* redirecting to another client. We can't send anything to
* it since RESP2 does not support push messages in the same
* connection. */
return;
}
/* Send the "value" part, which is the array of keys. */
if (proto) {
addReplyProto(c,keyname,keylen);
} else {
addReplyArrayLen(c,1);
addReplyBulkCBuffer(c,keyname,keylen);
} }
} }
/* Invalidates a caching slot: this is actually the low level implementation /* This function is called when a key is modified in Redis and in the case
* of the API that Redis calls externally, that is trackingInvalidateKey(). */ * we have at least one client with the BCAST mode enabled.
void trackingInvalidateSlot(uint64_t slot) { * Its goal is to set the key in the right broadcast state if the key
if (TrackingTable == NULL || TrackingTable[slot] == NULL) return; * matches one or more prefixes in the prefix table. Later when we
* return to the event loop, we'll send invalidation messages to the
* clients subscribed to each prefix. */
void trackingRememberKeyToBroadcast(char *keyname, size_t keylen) {
raxIterator ri; raxIterator ri;
raxStart(&ri,TrackingTable[slot]); raxStart(&ri,PrefixTable);
raxSeek(&ri,"^",NULL,0); raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) { while(raxNext(&ri)) {
uint64_t id; if (ri.key_len > keylen) continue;
memcpy(&id,ri.key,sizeof(id)); if (ri.key_len != 0 && memcmp(ri.key,keyname,ri.key_len) != 0)
client *c = lookupClientByID(id); continue;
if (c == NULL || !(c->flags & CLIENT_TRACKING)) continue; bcastState *bs = (bcastState*)ri.data;
sendTrackingMessage(c,slot); raxTryInsert(bs->keys,(unsigned char*)keyname,keylen,NULL,NULL);
} }
raxStop(&ri); raxStop(&ri);
/* Free the tracking table: we'll create the radix tree and populate it
* again if more keys will be modified in this caching slot. */
raxFree(TrackingTable[slot]);
TrackingTable[slot] = NULL;
TrackingTableUsedSlots--;
} }
/* This function is called from signalModifiedKey() or other places in Redis /* This function is called from signalModifiedKey() or other places in Redis
@ -183,28 +264,55 @@ void trackingInvalidateSlot(uint64_t slot) {
* to send a notification to every client that may have keys about such caching * to send a notification to every client that may have keys about such caching
* slot. */ * slot. */
void trackingInvalidateKey(robj *keyobj) { void trackingInvalidateKey(robj *keyobj) {
if (TrackingTable == NULL || TrackingTableUsedSlots == 0) return; if (TrackingTable == NULL) return;
sds sdskey = szFromObj(keyobj);
sds sdskey = (sds)ptrFromObj(keyobj); if (raxSize(PrefixTable) > 0)
uint64_t hash = crc64(0, trackingRememberKeyToBroadcast(sdskey,sdslen(sdskey));
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
trackingInvalidateSlot(hash); rax *ids = (rax*)raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
if (ids == raxNotFound) return;
raxIterator ri;
raxStart(&ri,ids);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
uint64_t id;
memcpy(&id,ri.key,sizeof(id));
client *c = lookupClientByID(id);
/* Note that if the client is in BCAST mode, we don't want to
* send invalidation messages that were pending in the case
* previously the client was not in BCAST mode. This can happen if
* TRACKING is enabled normally, and then the client switches to
* BCAST mode. */
if (c == NULL ||
!(c->flags & CLIENT_TRACKING)||
c->flags & CLIENT_TRACKING_BCAST)
{
continue;
}
sendTrackingMessage(c,sdskey,sdslen(sdskey),0);
}
raxStop(&ri);
/* Free the tracking table: we'll create the radix tree and populate it
* again if more keys will be modified in this caching slot. */
TrackingTableTotalItems -= raxSize(ids);
raxFree(ids);
raxRemove(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey),NULL);
} }
/* This function is called when one or all the Redis databases are flushed /* This function is called when one or all the Redis databases are flushed
* (dbid == -1 in case of FLUSHALL). Caching slots are not specific for * (dbid == -1 in case of FLUSHALL). Caching keys are not specific for
* each DB but are global: currently what we do is sending a special * each DB but are global: currently what we do is send a special
* notification to clients with tracking enabled, invalidating the caching * notification to clients with tracking enabled, invalidating the caching
* slot "-1", which means, "all the keys", in order to avoid flooding clients * key "", which means, "all the keys", in order to avoid flooding clients
* with many invalidation messages for all the keys they may hold. * with many invalidation messages for all the keys they may hold.
* */
* However trying to flush the tracking table here is very costly: void freeTrackingRadixTree(void *rt) {
* we need scanning 16 million caching slots in the table to check raxFree((rax*)rt);
* if they are used, this introduces a big delay. So what we do is to really }
* flush the table in the case of FLUSHALL. When a FLUSHDB is called instead
* we just send the invalidation message to all the clients, but don't
* flush the table: it will slowly get garbage collected as more keys
* are modified in the used caching slots. */
void trackingInvalidateKeysOnFlush(int dbid) { void trackingInvalidateKeysOnFlush(int dbid) {
if (g_pserver->tracking_clients) { if (g_pserver->tracking_clients) {
listNode *ln; listNode *ln;
@ -213,84 +321,131 @@ void trackingInvalidateKeysOnFlush(int dbid) {
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
client *c = (client*)listNodeValue(ln); client *c = (client*)listNodeValue(ln);
if (c->flags & CLIENT_TRACKING) { if (c->flags & CLIENT_TRACKING) {
sendTrackingMessage(c,-1); sendTrackingMessage(c,"",1,0);
} }
} }
} }
/* In case of FLUSHALL, reclaim all the memory used by tracking. */ /* In case of FLUSHALL, reclaim all the memory used by tracking. */
if (dbid == -1 && TrackingTable) { if (dbid == -1 && TrackingTable) {
for (int j = 0; j < TRACKING_TABLE_SIZE && TrackingTableUsedSlots > 0; j++) { raxFreeWithCallback(TrackingTable,freeTrackingRadixTree);
if (TrackingTable[j] != NULL) { TrackingTable = raxNew();
raxFree(TrackingTable[j]); TrackingTableTotalItems = 0;
TrackingTable[j] = NULL;
TrackingTableUsedSlots--;
}
}
/* If there are no clients with tracking enabled, we can even
* reclaim the memory used by the table itself. The code assumes
* the table is allocated only if there is at least one client alive
* with tracking enabled. */
if (g_pserver->tracking_clients == 0) {
zfree(TrackingTable);
TrackingTable = NULL;
}
} }
} }
/* Tracking forces Redis to remember information about which client may have /* Tracking forces Redis to remember information about which client may have
* keys about certian caching slots. In workloads where there are a lot of * certain keys. In workloads where there are a lot of reads, but keys are
* reads, but keys are hardly modified, the amount of information we have * hardly modified, the amount of information we have to remember server side
* to remember server side could be a lot: for each 16 millions of caching * could be a lot, with the number of keys being totally not bound.
* slots we may end with a radix tree containing many entries.
* *
* So Redis allows the user to configure a maximum fill rate for the * So Redis allows the user to configure a maximum number of keys for the
* invalidation table. This function makes sure that we don't go over the * invalidation table. This function makes sure that we don't go over the
* specified fill rate: if we are over, we can just evict informations about * specified fill rate: if we are over, we can just evict informations about
* random caching slots, and send invalidation messages to clients like if * a random key, and send invalidation messages to clients like if the key was
* the key was modified. */ * modified. */
void trackingLimitUsedSlots(void) { void trackingLimitUsedSlots(void) {
static unsigned int timeout_counter = 0; static unsigned int timeout_counter = 0;
if (TrackingTable == NULL) return;
if (g_pserver->tracking_table_max_fill == 0) return; /* No limits set. */ if (g_pserver->tracking_table_max_keys == 0) return; /* No limits set. */
unsigned int max_slots = size_t max_keys = g_pserver->tracking_table_max_keys;
(TRACKING_TABLE_SIZE/100) * g_pserver->tracking_table_max_fill; if (raxSize(TrackingTable) <= max_keys) {
if (TrackingTableUsedSlots <= max_slots) {
timeout_counter = 0; timeout_counter = 0;
return; /* Limit not reached. */ return; /* Limit not reached. */
} }
/* We have to invalidate a few slots to reach the limit again. The effort /* We have to invalidate a few keys to reach the limit again. The effort
* we do here is proportional to the number of times we entered this * we do here is proportional to the number of times we entered this
* function and found that we are still over the limit. */ * function and found that we are still over the limit. */
int effort = 100 * (timeout_counter+1); int effort = 100 * (timeout_counter+1);
/* Let's start at a random position, and perform linear probing, in order /* We just remove one key after another by using a random walk. */
* to improve cache locality. However once we are able to find an used raxIterator ri;
* slot, jump again randomly, in order to avoid creating big holes in the raxStart(&ri,TrackingTable);
* table (that will make this funciton use more resourced later). */
while(effort > 0) { while(effort > 0) {
unsigned int idx = rand() % TRACKING_TABLE_SIZE; effort--;
do { raxSeek(&ri,"^",NULL,0);
effort--; raxRandomWalk(&ri,0);
idx = (idx+1) % TRACKING_TABLE_SIZE; rax *ids = (rax*)ri.data;
if (TrackingTable[idx] != NULL) { TrackingTableTotalItems -= raxSize(ids);
trackingInvalidateSlot(idx); raxFree(ids);
if (TrackingTableUsedSlots <= max_slots) { raxRemove(TrackingTable,ri.key,ri.key_len,NULL);
timeout_counter = 0; if (raxSize(TrackingTable) <= max_keys) {
return; /* Return ASAP: we are again under the limit. */ timeout_counter = 0;
} else { raxStop(&ri);
break; /* Jump to next random position. */ return; /* Return ASAP: we are again under the limit. */
} }
}
} while(effort > 0);
} }
/* If we reach this point, we were not able to go under the configured
* limit using the maximum effort we had for this run. */
raxStop(&ri);
timeout_counter++; timeout_counter++;
} }
/* This function will run the prefixes of clients in BCAST mode and
* keys that were modified about each prefix, and will send the
* notifications to each client in each prefix. */
void trackingBroadcastInvalidationMessages(void) {
raxIterator ri, ri2;
/* Return ASAP if there is nothing to do here. */
if (TrackingTable == NULL || !g_pserver->tracking_clients) return;
raxStart(&ri,PrefixTable);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
bcastState *bs = (bcastState*)ri.data;
if (raxSize(bs->keys)) {
/* Create the array reply with the list of keys once, then send
* it to all the clients subscribed to this prefix. */
char buf[32];
size_t len = ll2string(buf,sizeof(buf),raxSize(bs->keys));
sds proto = sdsempty();
proto = sdsMakeRoomFor(proto,raxSize(bs->keys)*15);
proto = sdscatlen(proto,"*",1);
proto = sdscatlen(proto,buf,len);
proto = sdscatlen(proto,"\r\n",2);
raxStart(&ri2,bs->keys);
raxSeek(&ri2,"^",NULL,0);
while(raxNext(&ri2)) {
len = ll2string(buf,sizeof(buf),ri2.key_len);
proto = sdscatlen(proto,"$",1);
proto = sdscatlen(proto,buf,len);
proto = sdscatlen(proto,"\r\n",2);
proto = sdscatlen(proto,ri2.key,ri2.key_len);
proto = sdscatlen(proto,"\r\n",2);
}
raxStop(&ri2);
/* Send this array of keys to every client in the list. */
raxStart(&ri2,bs->clients);
raxSeek(&ri2,"^",NULL,0);
while(raxNext(&ri2)) {
client *c;
memcpy(&c,ri2.key,sizeof(c));
sendTrackingMessage(c,proto,sdslen(proto),1);
}
raxStop(&ri2);
/* Clean up: we can remove everything from this state, because we
* want to only track the new keys that will be accumulated starting
* from now. */
sdsfree(proto);
}
raxFree(bs->keys);
bs->keys = raxNew();
}
raxStop(&ri);
}
/* This is just used in order to access the amount of used slots in the /* This is just used in order to access the amount of used slots in the
* tracking table. */ * tracking table. */
unsigned long long trackingGetUsedSlots(void) { uint64_t trackingGetTotalItems(void) {
return TrackingTableUsedSlots; return TrackingTableTotalItems;
}
uint64_t trackingGetTotalKeys(void) {
if (TrackingTable == NULL) return 0;
return raxSize(TrackingTable);
} }

View File

@ -471,13 +471,14 @@ int string2ld(const char *s, size_t slen, long double *dp) {
long double value; long double value;
char *eptr; char *eptr;
if (slen >= sizeof(buf)) return 0; if (slen == 0 || slen >= sizeof(buf)) return 0;
memcpy(buf,s,slen); memcpy(buf,s,slen);
buf[slen] = '\0'; buf[slen] = '\0';
errno = 0; errno = 0;
value = strtold(buf, &eptr); value = strtold(buf, &eptr);
if (isspace(buf[0]) || eptr[0] != '\0' || if (isspace(buf[0]) || eptr[0] != '\0' ||
(size_t)(eptr-buf) != slen ||
(errno == ERANGE && (errno == ERANGE &&
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) || (value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
errno == EINVAL || errno == EINVAL ||

View File

@ -449,9 +449,6 @@ size_t zmalloc_get_smap_bytes_by_field(const char *field, long pid) {
return bytes; return bytes;
} }
#else #else
<<<<<<< HEAD:src/zmalloc.cpp
size_t zmalloc_get_smap_bytes_by_field(const char *field, long pid) {
=======
/* Get sum of the specified field from libproc api call. /* Get sum of the specified field from libproc api call.
* As there are per page value basis we need to convert * As there are per page value basis we need to convert
* them accordingly. * them accordingly.
@ -459,7 +456,7 @@ size_t zmalloc_get_smap_bytes_by_field(const char *field, long pid) {
* Note that AnonHugePages is a no-op as THP feature * Note that AnonHugePages is a no-op as THP feature
* is not supported in this platform * is not supported in this platform
*/ */
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) { size_t zmalloc_get_smap_bytes_by_field(const char *field, long pid) {
#if defined(__APPLE__) #if defined(__APPLE__)
struct proc_regioninfo pri; struct proc_regioninfo pri;
if (proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, PROC_PIDREGIONINFO_SIZE) == if (proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, PROC_PIDREGIONINFO_SIZE) ==
@ -474,7 +471,6 @@ size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
} }
return 0; return 0;
#endif #endif
>>>>>>> redis/6.0:src/zmalloc.c
((void) field); ((void) field);
((void) pid); ((void) pid);
return 0; return 0;

View File

@ -0,0 +1,87 @@
source "../tests/includes/init-tests.tcl"
test "Create a 5 nodes cluster" {
create_cluster 5 5
}
test "Cluster should start ok" {
assert_cluster_state ok
}
test "Cluster is writable" {
cluster_write_test 0
}
proc find_non_empty_master {} {
set master_id_no {}
foreach_redis_id id {
if {[RI $id role] eq {master} && [R $id dbsize] > 0} {
set master_id_no $id
}
}
return $master_id_no
}
proc get_one_of_my_replica {id} {
set replica_port [lindex [lindex [lindex [R $id role] 2] 0] 1]
set replica_id_num [get_instance_id_by_port redis $replica_port]
return $replica_id_num
}
proc cluster_write_keys_with_expire {id ttl} {
set prefix [randstring 20 20 alpha]
set port [get_instance_attrib redis $id port]
set cluster [redis_cluster 127.0.0.1:$port]
for {set j 100} {$j < 200} {incr j} {
$cluster setex key_expire.$j $ttl $prefix.$j
}
$cluster close
}
proc test_slave_load_expired_keys {aof} {
test "Slave expired keys is loaded when restarted: appendonly=$aof" {
set master_id [find_non_empty_master]
set replica_id [get_one_of_my_replica $master_id]
set master_dbsize [R $master_id dbsize]
set slave_dbsize [R $replica_id dbsize]
assert_equal $master_dbsize $slave_dbsize
set data_ttl 5
cluster_write_keys_with_expire $master_id $data_ttl
after 100
set replica_dbsize_1 [R $replica_id dbsize]
assert {$replica_dbsize_1 > $slave_dbsize}
R $replica_id config set appendonly $aof
R $replica_id config rewrite
set start_time [clock seconds]
set end_time [expr $start_time+$data_ttl+2]
R $replica_id save
set replica_dbsize_2 [R $replica_id dbsize]
assert {$replica_dbsize_2 > $slave_dbsize}
kill_instance redis $replica_id
set master_port [get_instance_attrib redis $master_id port]
exec ../../../src/keydb-cli -h 127.0.0.1 -p $master_port debug sleep [expr $data_ttl+3] > /dev/null &
while {[clock seconds] <= $end_time} {
#wait for $data_ttl seconds
}
restart_instance redis $replica_id
wait_for_condition 200 50 {
[R $replica_id ping] eq {PONG}
} else {
fail "replica #$replica_id not started"
}
set replica_dbsize_3 [R $replica_id dbsize]
assert {$replica_dbsize_3 > $slave_dbsize}
}
}
test_slave_load_expired_keys no
after 5000
test_slave_load_expired_keys yes

View File

@ -37,7 +37,7 @@ if {[catch {cd tmp}]} {
# Execute the specified instance of the server specified by 'type', using # Execute the specified instance of the server specified by 'type', using
# the provided configuration file. Returns the PID of the process. # the provided configuration file. Returns the PID of the process.
proc exec_instance {type cfgfile} { proc exec_instance {type cfgfile dir} {
if {$type eq "redis"} { if {$type eq "redis"} {
set prgname keydb-pro-server set prgname keydb-pro-server
} elseif {$type eq "sentinel"} { } elseif {$type eq "sentinel"} {
@ -46,10 +46,13 @@ proc exec_instance {type cfgfile} {
error "Unknown instance type." error "Unknown instance type."
} }
set stdout [format "%s/%s" $dir "stdout"]
set stderr [format "%s/%s" $dir "stderr"]
if {$::valgrind} { if {$::valgrind} {
set pid [exec valgrind --track-origins=yes --suppressions=../../../src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full ../../../src/${prgname} $cfgfile &] set pid [exec valgrind --track-origins=yes --suppressions=../../../src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full ../../../src/${prgname} $cfgfile &]
} else { } else {
set pid [exec ../../../src/${prgname} $cfgfile &] set pid [exec ../../../src/${prgname} $cfgfile > $stdout 2> $stderr &]
} }
return $pid return $pid
} }
@ -92,7 +95,7 @@ proc spawn_instance {type base_port count {conf {}}} {
close $cfg close $cfg
# Finally exec it and remember the pid for later cleanup. # Finally exec it and remember the pid for later cleanup.
set pid [exec_instance $type $cfgfile] set pid [exec_instance $type $cfgfile $dirname]
lappend ::pids $pid lappend ::pids $pid
# Check availability # Check availability
@ -502,7 +505,7 @@ proc restart_instance {type id} {
# Execute the instance with its old setup and append the new pid # Execute the instance with its old setup and append the new pid
# file for cleanup. # file for cleanup.
set pid [exec_instance $type $cfgfile] set pid [exec_instance $type $cfgfile $dirname]
set_instance_attrib $type $id pid $pid set_instance_attrib $type $id pid $pid
lappend ::pids $pid lappend ::pids $pid

View File

@ -258,7 +258,9 @@ tags {"aof"} {
} }
} }
start_server {overrides {appendonly {yes} appendfilename {appendonly.aof} appendfsync always}} { # Because of how this test works its inherently unreliable with multithreading, so force threads 1
# No real client should rely on this undocumented behavior
start_server {overrides {appendonly {yes} appendfilename {appendonly.aof} appendfsync always server-threads 1}} {
test {AOF fsync always barrier issue} { test {AOF fsync always barrier issue} {
set rd [redis_deferring_client] set rd [redis_deferring_client]
# Set a sleep when aof is flushed, so that we have a chance to look # Set a sleep when aof is flushed, so that we have a chance to look
@ -288,4 +290,26 @@ tags {"aof"} {
} }
} }
} }
## Test that PEXPIREMEMBERAT is loaded correctly
create_aof {
append_to_aof [formatCommand sadd testkey a b c d]
append_to_aof [formatCommand pexpirememberat testkey a 1000]
}
start_server_aof [list dir $server_path aof-load-truncated no] {
test "AOF+EXPIREMEMBER: Server shuold have been started" {
assert_equal 1 [is_alive $srv]
}
test "AOF+PEXPIREMEMBERAT: set should have 3 values" {
set client [redis [dict get $srv host] [dict get $srv port]]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
assert_equal 3 [$client scard testkey]
}
}
} }

View File

@ -0,0 +1,61 @@
# Test the meaningful offset implementation to make sure masters
# are able to PSYNC with replicas even if the replication stream
# has pending PINGs at the end.
start_server {tags {"psync2"}} {
start_server {} {
# Config
set debug_msg 0 ; # Enable additional debug messages
for {set j 0} {$j < 2} {incr j} {
set R($j) [srv [expr 0-$j] client]
set R_host($j) [srv [expr 0-$j] host]
set R_port($j) [srv [expr 0-$j] port]
$R($j) CONFIG SET repl-ping-replica-period 1
if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"}
}
# Setup replication
test "PSYNC2 meaningful offset: setup" {
$R(1) replicaof $R_host(0) $R_port(0)
$R(0) set foo bar
wait_for_condition 50 1000 {
[$R(0) dbsize] == 1 && [$R(1) dbsize] == 1
} else {
fail "Replicas not replicating from master"
}
}
test "PSYNC2 meaningful offset: write and wait replication" {
$R(0) INCR counter
$R(0) INCR counter
$R(0) INCR counter
wait_for_condition 50 1000 {
[$R(0) GET counter] eq [$R(1) GET counter]
} else {
fail "Master and replica don't agree about counter"
}
}
# In this test we'll make sure the replica will get stuck, but with
# an active connection: this way the master will continue to send PINGs
# every second (we modified the PING period earlier)
test "PSYNC2 meaningful offset: pause replica and promote it" {
$R(1) MULTI
$R(1) DEBUG SLEEP 5
$R(1) SLAVEOF NO ONE
$R(1) EXEC
$R(1) ping ; # Wait for it to return back available
}
test "Make the old master a replica of the new one and check conditions" {
set sync_partial [status $R(1) sync_partial_ok]
assert {$sync_partial == 0}
$R(0) REPLICAOF $R_host(1) $R_port(1)
wait_for_condition 50 1000 {
[status $R(1) sync_partial_ok] == 1
} else {
fail "The new master was not able to partial sync"
}
}
}}

View File

@ -114,6 +114,27 @@ start_server {} {
} }
} }
# wait for all the slaves to be in sync with the master
set master_ofs [status $R($master_id) master_repl_offset]
wait_for_condition 500 100 {
$master_ofs == [status $R(0) master_repl_offset] &&
$master_ofs == [status $R(1) master_repl_offset] &&
$master_ofs == [status $R(2) master_repl_offset] &&
$master_ofs == [status $R(3) master_repl_offset] &&
$master_ofs == [status $R(4) master_repl_offset]
} else {
if {$debug_msg} {
for {set j 0} {$j < 5} {incr j} {
puts "$j: sync_full: [status $R($j) sync_full]"
puts "$j: id1 : [status $R($j) master_replid]:[status $R($j) master_repl_offset]"
puts "$j: id2 : [status $R($j) master_replid2]:[status $R($j) second_repl_offset]"
puts "$j: backlog : firstbyte=[status $R($j) repl_backlog_first_byte_offset] len=[status $R($j) repl_backlog_histlen]"
puts "---"
}
}
fail "Slaves are not in sync with the master after too long time."
}
# Put down the old master so that it cannot generate more # Put down the old master so that it cannot generate more
# replication stream, this way in the next master switch, the time at # replication stream, this way in the next master switch, the time at
# which we move slaves away is not important, each will have full # which we move slaves away is not important, each will have full

View File

@ -142,5 +142,93 @@ start_server {tags {"repl"}} {
fail "SPOP replication inconsistency" fail "SPOP replication inconsistency"
} }
} }
test {Replication of EXPIREMEMBER (set) command} {
$master sadd testkey a b c d
wait_for_condition 50 100 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "Failed to replicate set"
}
$master expiremember testkey a 1
after 1000
wait_for_condition 50 100 {
[$master scard testkey] eq 3
} else {
fail "expiremember failed to work on master"
}
wait_for_condition 50 100 {
[$slave scard testkey] eq 3
} else {
assert_equal [$slave scard testkey] 3
}
$master del testkey
}
test {Replication of EXPIREMEMBER (hash) command} {
$master hset testkey a value
$master hset testkey b value
wait_for_condition 50 100 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "Failed to replicate set"
}
$master expiremember testkey a 1
after 1000
wait_for_condition 50 100 {
[$master hlen testkey] eq 1
} else {
fail "expiremember failed to work on master"
}
wait_for_condition 50 100 {
[$slave hlen testkey] eq 1
} else {
assert_equal [$slave hlen testkey] 1
}
$master del testkey
}
test {Replication of EXPIREMEMBER (zset) command} {
$master zadd testkey 1 a
$master zadd testkey 2 b
wait_for_condition 50 100 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "Failed to replicate set"
}
$master expiremember testkey a 1
after 1000
wait_for_condition 50 100 {
[$master zcard testkey] eq 1
} else {
fail "expiremember failed to work on master"
}
wait_for_condition 50 100 {
[$slave zcard testkey] eq 1
} else {
assert_equal [$slave zcard testkey] 1
}
}
test {keydb.cron replicates} {
$master del testkey
$master keydb.cron testjob repeat 0 1000000 {redis.call("incr", "testkey")} 1 testkey
after 300
assert_equal 1 [$master get testkey]
assert_equal 1 [$master exists testjob]
wait_for_condition 50 100 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "KEYDB.CRON failed to replicate"
}
$master del testjob
$master del testkey
wait_for_condition 50 1000 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "cron delete failed to propogate"
}
}
} }
} }

View File

@ -77,6 +77,94 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
$master flushall $master flushall
} }
test {Replication of EXPIREMEMBER (set) command (Active)} {
$master sadd testkey a b c d
wait_for_condition 50 100 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "Failed to replicate set"
}
$master expiremember testkey a 1
after 1000
wait_for_condition 50 100 {
[$master scard testkey] eq 3
} else {
fail "expiremember failed to work on master"
}
wait_for_condition 50 100 {
[$slave scard testkey] eq 3
} else {
assert_equal [$slave scard testkey] 3
}
$master del testkey
}
test {Replication of EXPIREMEMBER (hash) command (Active)} {
$master hset testkey a value
$master hset testkey b value
wait_for_condition 50 100 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "Failed to replicate set"
}
$master expiremember testkey a 1
after 1000
wait_for_condition 50 100 {
[$master hlen testkey] eq 1
} else {
fail "expiremember failed to work on master"
}
wait_for_condition 50 100 {
[$slave hlen testkey] eq 1
} else {
assert_equal [$slave hlen testkey] 1
}
$master del testkey
}
test {Replication of EXPIREMEMBER (zset) command (Active)} {
$master zadd testkey 1 a
$master zadd testkey 2 b
wait_for_condition 50 100 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "Failed to replicate set"
}
$master expiremember testkey a 1
after 1000
wait_for_condition 50 100 {
[$master zcard testkey] eq 1
} else {
fail "expiremember failed to work on master"
}
wait_for_condition 50 100 {
[$slave zcard testkey] eq 1
} else {
assert_equal [$slave zcard testkey] 1
}
}
test {keydb.cron replicates (Active) } {
$master del testkey
$master keydb.cron testjob repeat 0 1000000 {redis.call("incr", "testkey")} 1 testkey
after 300
assert_equal 1 [$master get testkey]
assert_equal 1 [$master exists testjob]
wait_for_condition 50 100 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "KEYDB.CRON failed to replicate"
}
$master del testjob
$master del testkey
wait_for_condition 50 1000 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "cron delete failed to propogate"
}
}
test {Active replicas WAIT} { test {Active replicas WAIT} {
# Test that wait succeeds since replicas should be syncronized # Test that wait succeeds since replicas should be syncronized
$master set testkey foo $master set testkey foo
@ -113,37 +201,37 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
assert_equal {0} [$slave del testkey1] assert_equal {0} [$slave del testkey1]
} }
test {Active replica expire propogates when source is down} { test {Active replica expire propogates when source is down} {
$slave flushall $slave flushall
$slave set testkey2 foo $slave set testkey2 foo
$slave set testkey1 foo $slave set testkey1 foo
wait_for_condition 50 1000 { wait_for_condition 50 1000 {
[string match *foo* [$master get testkey1]] [string match *foo* [$master get testkey1]]
} else { } else {
fail "Replication failed to propogate" fail "Replication failed to propogate"
} }
$slave expire testkey1 2 $slave expire testkey1 2
assert_equal {1} [$slave wait 1 500] { "value should propogate assert_equal {1} [$slave wait 1 500] { "value should propogate
within 0.5 seconds" } within 0.5 seconds" }
exec kill -SIGSTOP $slave_pid exec kill -SIGSTOP $slave_pid
after 3000 after 3000
# Ensure testkey1 is gone. Note, we can't do this directly as the normal commands lie to us # Ensure testkey1 is gone. Note, we can't do this directly as the normal commands lie to us
# about what is actually in the dict. The only way to know is with a count from info # about what is actually in the dict. The only way to know is with a count from info
assert_equal {1} [expr [string first {keys=1} [$master info keyspace]] >= 0] {"slave expired"} assert_equal {1} [expr [string first {keys=1} [$master info keyspace]] >= 0] {"slave expired"}
} }
exec kill -SIGCONT $slave_pid
test {Active replica different databases} { exec kill -SIGCONT $slave_pid
$master select 3
$master set testkey abcd test {Active replica different databases} {
$master select 2 $master select 3
$master del testkey $master set testkey abcd
$slave select 3 $master select 2
wait_for_condition 50 1000 { $master del testkey
[string match abcd [$slave get testkey]] $slave select 3
} else { wait_for_condition 50 1000 {
fail "Replication failed to propogate DB 3" [string match abcd [$slave get testkey]]
} } else {
fail "Replication failed to propogate DB 3"
} }
} }
} }
@ -175,3 +263,4 @@ foreach mdl {no yes} {
} }
} }
} }
}

View File

@ -172,13 +172,13 @@ int bpopgt_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argc);
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx); RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
long long gt = (long long)RedisModule_GetBlockedClientPrivateData(ctx); long long *pgt = RedisModule_GetBlockedClientPrivateData(ctx);
fsl_t *fsl; fsl_t *fsl;
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0)) if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (!fsl || fsl->list[fsl->length-1] <= gt) if (!fsl || fsl->list[fsl->length-1] <= *pgt)
return REDISMODULE_ERR; return REDISMODULE_ERR;
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]); RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
@ -192,10 +192,8 @@ int bpopgt_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int a
} }
void bpopgt_free_privdata(RedisModuleCtx *ctx, void *privdata) { void bpopgt_free_privdata(RedisModuleCtx *ctx, void *privdata) {
/* Nothing to do because privdata is actually a 'long long',
* not a pointer to the heap */
REDISMODULE_NOT_USED(ctx); REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(privdata); RedisModule_Free(privdata);
} }
/* FSL.BPOPGT <key> <gt> <timeout> - Block clients until list has an element greater than <gt>. /* FSL.BPOPGT <key> <gt> <timeout> - Block clients until list has an element greater than <gt>.
@ -217,9 +215,12 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return REDISMODULE_OK; return REDISMODULE_OK;
if (!fsl || fsl->list[fsl->length-1] <= gt) { if (!fsl || fsl->list[fsl->length-1] <= gt) {
/* We use malloc so the tests in blockedonkeys.tcl can check for memory leaks */
long long *pgt = RedisModule_Alloc(sizeof(long long));
*pgt = gt;
/* Key is empty or has <2 elements, we must block */ /* Key is empty or has <2 elements, we must block */
RedisModule_BlockClientOnKeys(ctx, bpopgt_reply_callback, bpopgt_timeout_callback, RedisModule_BlockClientOnKeys(ctx, bpopgt_reply_callback, bpopgt_timeout_callback,
bpopgt_free_privdata, timeout, &argv[1], 1, (void*)gt); bpopgt_free_privdata, timeout, &argv[1], 1, pgt);
} else { } else {
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]); RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
} }

View File

@ -42,7 +42,7 @@ int fork_create(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
/* child */ /* child */
RedisModule_Log(ctx, "notice", "fork child started"); RedisModule_Log(ctx, "notice", "fork child started");
usleep(200000); usleep(500000);
RedisModule_Log(ctx, "notice", "fork child exiting"); RedisModule_Log(ctx, "notice", "fork child exiting");
RedisModule_ExitFromChild(code_to_exit_with); RedisModule_ExitFromChild(code_to_exit_with);
/* unreachable */ /* unreachable */

View File

@ -74,6 +74,19 @@ int test_ld_conv(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
RedisModule_ReplyWithError(ctx, err); RedisModule_ReplyWithError(ctx, err);
goto final; goto final;
} }
/* Make sure we can't convert a string that has \0 in it */
char buf[4] = "123";
buf[1] = '\0';
RedisModuleString *s3 = RedisModule_CreateString(ctx, buf, 3);
long double ld3;
if (RedisModule_StringToLongDouble(s3, &ld3) == REDISMODULE_OK) {
RedisModule_ReplyWithError(ctx, "Invalid string successfully converted to long double");
RedisModule_FreeString(ctx, s3);
goto final;
}
RedisModule_FreeString(ctx, s3);
RedisModule_ReplyWithLongDouble(ctx, ld2); RedisModule_ReplyWithLongDouble(ctx, ld2);
final: final:
RedisModule_FreeString(ctx, s1); RedisModule_FreeString(ctx, s1);

View File

@ -64,7 +64,8 @@ void *threadMain(void *arg) {
RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */ RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
RedisModule_ThreadSafeContextLock(ctx); RedisModule_ThreadSafeContextLock(ctx);
RedisModule_Replicate(ctx,"INCR","c","thread"); RedisModule_Replicate(ctx,"INCR","c","a-from-thread");
RedisModule_Replicate(ctx,"INCR","c","b-from-thread");
RedisModule_ThreadSafeContextUnlock(ctx); RedisModule_ThreadSafeContextUnlock(ctx);
} }
RedisModule_FreeThreadSafeContext(ctx); RedisModule_FreeThreadSafeContext(ctx);
@ -89,6 +90,38 @@ int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int propagateTest2Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
/* Replicate two commands to test MULTI/EXEC wrapping. */
RedisModule_Replicate(ctx,"INCR","c","counter-1");
RedisModule_Replicate(ctx,"INCR","c","counter-2");
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
int propagateTest3Command(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);
RedisModule_Replicate(ctx,"INCR","c","counter-1");
RedisModule_Replicate(ctx,"INCR","c","counter-2");
reply = RedisModule_Call(ctx, "INCR", "c!", "after-call");
RedisModule_FreeCallReply(reply);
RedisModule_ReplyWithSimpleString(ctx,"OK");
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);
@ -100,5 +133,16 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
propagateTestCommand, propagateTestCommand,
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test-2",
propagateTest2Command,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test-3",
propagateTest3Command,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK; return REDISMODULE_OK;
} }

View File

@ -53,6 +53,7 @@ proc kill_server config {
} }
# kill server and wait for the process to be totally exited # kill server and wait for the process to be totally exited
send_data_packet $::test_server_fd server-killing $pid
catch {exec kill $pid} catch {exec kill $pid}
if {$::valgrind} { if {$::valgrind} {
set max_wait 60000 set max_wait 60000
@ -140,15 +141,30 @@ proc tags {tags code} {
uplevel 1 $code uplevel 1 $code
set ::tags [lrange $::tags 0 end-[llength $tags]] set ::tags [lrange $::tags 0 end-[llength $tags]]
} }
# Write the configuration in the dictionary 'config' in the specified
# file name.
proc create_server_config_file {filename config} {
set fp [open $filename w+]
foreach directive [dict keys $config] {
puts -nonewline $fp "$directive "
puts $fp [dict get $config $directive]
}
close $fp
}
proc start_server {options {code undefined}} { proc start_server {options {code undefined}} {
# If we are running against an external server, we just push the # If we are running against an external server, we just push the
# host/port pair in the stack the first time # host/port pair in the stack the first time
if {$::external} { if {$::external} {
if {[llength $::servers] == 0} { if {[llength $::servers] == 0} {
set srv {} set srv {}
# In test_server_main(tests/test_helper.tcl:215~218), increase the value of start_port
# and assign it to ::port through the `--port` option, so we need to reduce it.
set baseport [expr {$::port-100}]
dict set srv "host" $::host dict set srv "host" $::host
dict set srv "port" $::port dict set srv "port" $baseport
set client [redis $::host $::port 0 $::tls] set client [redis $::host $baseport 0 $::tls]
dict set srv "client" $client dict set srv "client" $client
$client select 9 $client select 9
@ -214,6 +230,9 @@ proc start_server {options {code undefined}} {
set unixsocket [file normalize [format "%s/%s" [dict get $config "dir"] "socket"]] set unixsocket [file normalize [format "%s/%s" [dict get $config "dir"] "socket"]]
dict set config "unixsocket" $unixsocket dict set config "unixsocket" $unixsocket
#Ensure all tests validate multithreading
dict set config "testmode" "yes"
# apply overrides from global space and arguments # apply overrides from global space and arguments
foreach {directive arguments} [concat $::global_overrides $overrides] { foreach {directive arguments} [concat $::global_overrides $overrides] {
dict set config $directive $arguments dict set config $directive $arguments
@ -221,56 +240,91 @@ proc start_server {options {code undefined}} {
# write new configuration to temporary file # write new configuration to temporary file
set config_file [tmpfile redis.conf] set config_file [tmpfile redis.conf]
set fp [open $config_file w+] create_server_config_file $config_file $config
foreach directive [dict keys $config] {
puts -nonewline $fp "$directive "
puts $fp [dict get $config $directive]
}
close $fp
set stdout [format "%s/%s" [dict get $config "dir"] "stdout"] set stdout [format "%s/%s" [dict get $config "dir"] "stdout"]
set stderr [format "%s/%s" [dict get $config "dir"] "stderr"] set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
if {$::valgrind} { # We need a loop here to retry with different ports.
set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/keydb-pro-server $config_file > $stdout 2> $stderr &] set server_started 0
} elseif ($::stack_logging) { while {$server_started == 0} {
set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/keydb-pro-server $config_file > $stdout 2> $stderr &] if {$::verbose} {
} else { puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
set pid [exec src/keydb-pro-server $config_file > $stdout 2> $stderr &] }
}
# Tell the test server about this new instance. send_data_packet $::test_server_fd "server-spawning" "port $::port"
send_data_packet $::test_server_fd server-spawned $pid
# check that the server actually started if {$::valgrind} {
# ugly but tries to be as fast as possible... set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/keydb-pro-server $config_file > $stdout 2> $stderr &]
if {$::valgrind} {set retrynum 1000} else {set retrynum 100} } elseif ($::stack_logging) {
set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/keydb-pro-server $config_file > $stdout 2> $stderr &]
} else {
set pid [exec src/keydb-pro-server $config_file > $stdout 2> $stderr &]
}
if {$::verbose} { # Tell the test server about this new instance.
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} " send_data_packet $::test_server_fd server-spawned $pid
}
if {$code ne "undefined"} { # check that the server actually started
set serverisup [server_is_up $::host $::port $retrynum] # ugly but tries to be as fast as possible...
} else { if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
set serverisup 1
}
if {$::verbose} { # Wait for actual startup
puts "" set checkperiod 100; # Milliseconds
} set maxiter [expr {120*1000/100}] ; # Wait up to 2 minutes.
set port_busy 0
while {![info exists _pid]} {
regexp {PID:\s(\d+)} [exec cat $stdout] _ _pid
after $checkperiod
incr maxiter -1
if {$maxiter == 0} {
start_server_error $config_file "No PID detected in log $stdout"
puts "--- LOG CONTENT ---"
puts [exec cat $stdout]
puts "-------------------"
break
}
if {!$serverisup} { # Check if the port is actually busy and the server failed
set err {} # for this reason.
append err [exec cat $stdout] "\n" [exec cat $stderr] if {[regexp {Could not create server TCP} [exec cat $stdout]]} {
start_server_error $config_file $err set port_busy 1
return break
} }
}
# Wait for actual startup # Sometimes we have to try a different port, even if we checked
while {![info exists _pid]} { # for availability. Other test clients may grab the port before we
regexp {PID:\s(\d+)} [exec cat $stdout] _ _pid # are able to do it for example.
after 100 if {$port_busy} {
puts "Port $::port was already busy, trying another port..."
set ::port [find_available_port [expr {$::port+1}]]
if {$::tls} {
dict set config "tls-port" $::port
} else {
dict set config port $::port
}
create_server_config_file $config_file $config
continue; # Try again
}
if {$code ne "undefined"} {
set serverisup [server_is_up $::host $::port $retrynum]
} else {
set serverisup 1
}
if {$::verbose} {
puts ""
}
if {!$serverisup} {
set err {}
append err [exec cat $stdout] "\n" [exec cat $stderr]
start_server_error $config_file $err
return
}
set server_started 1
} }
# setup properties to be able to initialize a client object # setup properties to be able to initialize a client object

View File

@ -52,6 +52,7 @@ set ::all_tests {
integration/logging integration/logging
integration/psync2 integration/psync2
integration/psync2-reg integration/psync2-reg
integration/psync2-pingoff
unit/pubsub unit/pubsub
unit/slowlog unit/slowlog
unit/scripting unit/scripting
@ -93,7 +94,7 @@ set ::file ""; # If set, runs only the tests in this comma separated list
set ::curfile ""; # Hold the filename of the current suite set ::curfile ""; # Hold the filename of the current suite
set ::accurate 0; # If true runs fuzz tests with more iterations set ::accurate 0; # If true runs fuzz tests with more iterations
set ::force_failure 0 set ::force_failure 0
set ::timeout 600; # 10 minutes without progresses will quit the test. set ::timeout 1200; # 20 minutes without progresses will quit the test.
set ::last_progress [clock seconds] set ::last_progress [clock seconds]
set ::active_servers {} ; # Pids of active Redis instances. set ::active_servers {} ; # Pids of active Redis instances.
set ::dont_clean 0 set ::dont_clean 0
@ -295,7 +296,7 @@ proc read_from_test_client fd {
puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)" puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)"
lappend ::clients_time_history $elapsed $data lappend ::clients_time_history $elapsed $data
signal_idle_client $fd signal_idle_client $fd
set ::active_clients_task($fd) DONE set ::active_clients_task($fd) "(DONE) $data"
} elseif {$status eq {ok}} { } elseif {$status eq {ok}} {
if {!$::quiet} { if {!$::quiet} {
puts "\[[colorstr green $status]\]: $data" puts "\[[colorstr green $status]\]: $data"
@ -326,10 +327,16 @@ proc read_from_test_client fd {
exit 1 exit 1
} elseif {$status eq {testing}} { } elseif {$status eq {testing}} {
set ::active_clients_task($fd) "(IN PROGRESS) $data" set ::active_clients_task($fd) "(IN PROGRESS) $data"
} elseif {$status eq {server-spawning}} {
set ::active_clients_task($fd) "(SPAWNING SERVER) $data"
} elseif {$status eq {server-spawned}} { } elseif {$status eq {server-spawned}} {
lappend ::active_servers $data lappend ::active_servers $data
set ::active_clients_task($fd) "(SPAWNED SERVER) pid:$data"
} elseif {$status eq {server-killing}} {
set ::active_clients_task($fd) "(KILLING SERVER) pid:$data"
} elseif {$status eq {server-killed}} { } elseif {$status eq {server-killed}} {
set ::active_servers [lsearch -all -inline -not -exact $::active_servers $data] set ::active_servers [lsearch -all -inline -not -exact $::active_servers $data]
set ::active_clients_task($fd) "(KILLED SERVER) pid:$data"
} else { } else {
if {!$::quiet} { if {!$::quiet} {
puts "\[$status\]: $data" puts "\[$status\]: $data"
@ -339,7 +346,7 @@ proc read_from_test_client fd {
proc show_clients_state {} { proc show_clients_state {} {
# The following loop is only useful for debugging tests that may # The following loop is only useful for debugging tests that may
# enter an infinite loop. Commented out normally. # enter an infinite loop.
foreach x $::active_clients { foreach x $::active_clients {
if {[info exist ::active_clients_task($x)]} { if {[info exist ::active_clients_task($x)]} {
puts "$x => $::active_clients_task($x)" puts "$x => $::active_clients_task($x)"
@ -369,8 +376,6 @@ proc signal_idle_client fd {
set ::active_clients \ set ::active_clients \
[lsearch -all -inline -not -exact $::active_clients $fd] [lsearch -all -inline -not -exact $::active_clients $fd]
if 0 {show_clients_state}
# New unit to process? # New unit to process?
if {$::next_test != [llength $::all_tests]} { if {$::next_test != [llength $::all_tests]} {
if {!$::quiet} { if {!$::quiet} {
@ -386,6 +391,7 @@ proc signal_idle_client fd {
} }
} else { } else {
lappend ::idle_clients $fd lappend ::idle_clients $fd
set ::active_clients_task($fd) "SLEEPING, no more units to assign"
if {[llength $::active_clients] == 0} { if {[llength $::active_clients] == 0} {
the_end the_end
} }
@ -506,6 +512,9 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} elseif {$opt eq {--host}} { } elseif {$opt eq {--host}} {
set ::external 1 set ::external 1
set ::host $arg set ::host $arg
# If we use an external server, we can only set numclients to 1,
# otherwise the port will be miscalculated.
set ::numclients 1
incr j incr j
} elseif {$opt eq {--port}} { } elseif {$opt eq {--port}} {
set ::port $arg set ::port $arg

View File

@ -141,4 +141,118 @@ start_server {tags {"acl"}} {
r ACL setuser newuser -debug r ACL setuser newuser -debug
# The test framework will detect a leak if any. # The test framework will detect a leak if any.
} }
test {ACL LOG shows failed command executions at toplevel} {
r ACL LOG RESET
r ACL setuser antirez >foo on +set ~object:1234
r ACL setuser antirez +eval +multi +exec
r AUTH antirez foo
catch {r GET foo}
r AUTH default ""
set entry [lindex [r ACL LOG] 0]
assert {[dict get $entry username] eq {antirez}}
assert {[dict get $entry context] eq {toplevel}}
assert {[dict get $entry reason] eq {command}}
assert {[dict get $entry object] eq {get}}
}
test {ACL LOG is able to test similar events} {
r AUTH antirez foo
catch {r GET foo}
catch {r GET foo}
catch {r GET foo}
r AUTH default ""
set entry [lindex [r ACL LOG] 0]
assert {[dict get $entry count] == 4}
}
test {ACL LOG is able to log keys access violations and key name} {
r AUTH antirez foo
catch {r SET somekeynotallowed 1234}
r AUTH default ""
set entry [lindex [r ACL LOG] 0]
assert {[dict get $entry reason] eq {key}}
assert {[dict get $entry object] eq {somekeynotallowed}}
}
test {ACL LOG RESET is able to flush the entries in the log} {
r ACL LOG RESET
assert {[llength [r ACL LOG]] == 0}
}
test {ACL LOG can distinguish the transaction context (1)} {
r AUTH antirez foo
r MULTI
catch {r INCR foo}
catch {r EXEC}
r AUTH default ""
set entry [lindex [r ACL LOG] 0]
assert {[dict get $entry context] eq {multi}}
assert {[dict get $entry object] eq {incr}}
}
test {ACL LOG can distinguish the transaction context (2)} {
set rd1 [redis_deferring_client]
r ACL SETUSER antirez +incr
r AUTH antirez foo
r MULTI
r INCR object:1234
$rd1 ACL SETUSER antirez -incr
$rd1 read
catch {r EXEC}
$rd1 close
r AUTH default ""
set entry [lindex [r ACL LOG] 0]
assert {[dict get $entry context] eq {multi}}
assert {[dict get $entry object] eq {incr}}
r ACL SETUSER antirez -incr
}
test {ACL can log errors in the context of Lua scripting} {
r AUTH antirez foo
catch {r EVAL {redis.call('incr','foo')} 0}
r AUTH default ""
set entry [lindex [r ACL LOG] 0]
assert {[dict get $entry context] eq {lua}}
assert {[dict get $entry object] eq {incr}}
}
test {ACL LOG can accept a numerical argument to show less entries} {
r AUTH antirez foo
catch {r INCR foo}
catch {r INCR foo}
catch {r INCR foo}
catch {r INCR foo}
r AUTH default ""
assert {[llength [r ACL LOG]] > 1}
assert {[llength [r ACL LOG 2]] == 2}
}
test {ACL LOG can log failed auth attempts} {
catch {r AUTH antirez wrong-password}
set entry [lindex [r ACL LOG] 0]
assert {[dict get $entry context] eq {toplevel}}
assert {[dict get $entry reason] eq {auth}}
assert {[dict get $entry object] eq {AUTH}}
assert {[dict get $entry username] eq {antirez}}
}
test {ACL LOG entries are limited to a maximum amount} {
r ACL LOG RESET
r CONFIG SET acllog-max-len 5
r AUTH antirez foo
for {set j 0} {$j < 10} {incr j} {
catch {r SET obj:$j 123}
}
r AUTH default ""
assert {[llength [r ACL LOG]] == 5}
}
test {When default user is off, new connections are not authenticated} {
r ACL setuser default off
catch {set rd1 [redis_deferring_client]} e
r ACL setuser default on
set e
} {*NOAUTH*}
} }

View File

@ -199,3 +199,34 @@ start_server {tags {"bitops"}} {
r del mystring r del mystring
} }
} }
start_server {tags {"repl"}} {
start_server {} {
set master [srv -1 client]
set master_host [srv -1 host]
set master_port [srv -1 port]
set slave [srv 0 client]
test {BITFIELD: setup slave} {
$slave slaveof $master_host $master_port
wait_for_condition 50 100 {
[s 0 master_link_status] eq {up}
} else {
fail "Replication not started."
}
}
test {BITFIELD: write on master, read on slave} {
$master del bits
assert_equal 0 [$master bitfield bits set u8 0 255]
assert_equal 255 [$master bitfield bits set u8 0 100]
wait_for_ofs_sync $master $slave
assert_equal 100 [$slave bitfield_ro bits get u8 0]
}
test {BITFIELD_RO fails when write option is used} {
catch {$slave bitfield_ro bits set u8 0 100 get u8 0} err
assert_match {*ERR BITFIELD_RO only supports the GET subcommand*} $err
}
}
}

View File

@ -39,6 +39,8 @@ start_server {tags {"memefficiency"}} {
start_server {tags {"defrag"}} { start_server {tags {"defrag"}} {
if {[string match {*jemalloc*} [s mem_allocator]]} { if {[string match {*jemalloc*} [s mem_allocator]]} {
test "Active defrag" { test "Active defrag" {
r config set save "" ;# prevent bgsave from interfereing with save below
r config set hz 100
r config set activedefrag no r config set activedefrag no
r config set active-defrag-threshold-lower 5 r config set active-defrag-threshold-lower 5
r config set active-defrag-cycle-min 65 r config set active-defrag-cycle-min 65
@ -46,8 +48,8 @@ start_server {tags {"defrag"}} {
r config set active-defrag-ignore-bytes 2mb r config set active-defrag-ignore-bytes 2mb
r config set maxmemory 100mb r config set maxmemory 100mb
r config set maxmemory-policy allkeys-lru r config set maxmemory-policy allkeys-lru
r debug populate 700000 asdf 150 r debug populate 700000 asdf1 150
r debug populate 170000 asdf 300 r debug populate 170000 asdf2 300
r ping ;# trigger eviction following the previous population r ping ;# trigger eviction following the previous population
after 120 ;# serverCron only updates the info once in 100ms after 120 ;# serverCron only updates the info once in 100ms
set frag [s allocator_frag_ratio] set frag [s allocator_frag_ratio]
@ -55,6 +57,11 @@ start_server {tags {"defrag"}} {
puts "frag $frag" puts "frag $frag"
} }
assert {$frag >= 1.4} assert {$frag >= 1.4}
r config set latency-monitor-threshold 5
r latency reset
r config set maxmemory 110mb ;# prevent further eviction (not to fail the digest test)
set digest [r debug digest]
catch {r config set activedefrag yes} e catch {r config set activedefrag yes} e
if {![string match {DISABLED*} $e]} { if {![string match {DISABLED*} $e]} {
# Wait for the active defrag to start working (decision once a # Wait for the active defrag to start working (decision once a
@ -78,19 +85,37 @@ start_server {tags {"defrag"}} {
# Test the the fragmentation is lower. # Test the the fragmentation is lower.
after 120 ;# serverCron only updates the info once in 100ms after 120 ;# serverCron only updates the info once in 100ms
set frag [s allocator_frag_ratio] set frag [s allocator_frag_ratio]
set max_latency 0
foreach event [r latency latest] {
lassign $event eventname time latency max
if {$eventname == "active-defrag-cycle"} {
set max_latency $max
}
}
if {$::verbose} { if {$::verbose} {
puts "frag $frag" puts "frag $frag"
puts "max latency $max_latency"
puts [r latency latest]
puts [r latency history active-defrag-cycle]
} }
assert {$frag < 1.1} assert {$frag < 1.1}
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
assert {$max_latency <= 30}
} else { } else {
set _ "" set _ ""
} }
} {} # verify the data isn't corrupted or changed
set newdigest [r debug digest]
assert {$digest eq $newdigest}
r save ;# saving an rdb iterates over all the data / pointers
} {OK}
test "Active defrag big keys" { test "Active defrag big keys" {
r flushdb r flushdb
r config resetstat r config resetstat
r config set save "" ;# prevent bgsave from interfereing with save below r config set save "" ;# prevent bgsave from interfereing with save below
r config set hz 100
r config set activedefrag no r config set activedefrag no
r config set active-defrag-max-scan-fields 1000 r config set active-defrag-max-scan-fields 1000
r config set active-defrag-threshold-lower 5 r config set active-defrag-threshold-lower 5
@ -142,7 +167,7 @@ start_server {tags {"defrag"}} {
for {set j 0} {$j < 500000} {incr j} { for {set j 0} {$j < 500000} {incr j} {
$rd read ; # Discard replies $rd read ; # Discard replies
} }
assert {[r dbsize] == 500010} assert_equal [r dbsize] 500010
# create some fragmentation # create some fragmentation
for {set j 0} {$j < 500000} {incr j 2} { for {set j 0} {$j < 500000} {incr j 2} {
@ -151,7 +176,7 @@ start_server {tags {"defrag"}} {
for {set j 0} {$j < 500000} {incr j 2} { for {set j 0} {$j < 500000} {incr j 2} {
$rd read ; # Discard replies $rd read ; # Discard replies
} }
assert {[r dbsize] == 250010} assert_equal [r dbsize] 250010
# start defrag # start defrag
after 120 ;# serverCron only updates the info once in 100ms after 120 ;# serverCron only updates the info once in 100ms
@ -200,14 +225,106 @@ start_server {tags {"defrag"}} {
puts [r latency history active-defrag-cycle] puts [r latency history active-defrag-cycle]
} }
assert {$frag < 1.1} assert {$frag < 1.1}
# due to high fragmentation, 10hz, and active-defrag-cycle-max set to 75, # due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
# we expect max latency to be not much higher than 75ms # we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
assert {$max_latency <= 120} assert {$max_latency <= 30}
} }
# verify the data isn't corrupted or changed # verify the data isn't corrupted or changed
set newdigest [r debug digest] set newdigest [r debug digest]
assert {$digest eq $newdigest} assert {$digest eq $newdigest}
r save ;# saving an rdb iterates over all the data / pointers r save ;# saving an rdb iterates over all the data / pointers
} {OK} } {OK}
test "Active defrag big list" {
r flushdb
r config resetstat
r config set save "" ;# prevent bgsave from interfereing with save below
r config set hz 100
r config set activedefrag no
r config set active-defrag-max-scan-fields 1000
r config set active-defrag-threshold-lower 5
r config set active-defrag-cycle-min 65
r config set active-defrag-cycle-max 75
r config set active-defrag-ignore-bytes 2mb
r config set maxmemory 0
r config set list-max-ziplist-size 5 ;# list of 500k items will have 100k quicklist nodes
# create big keys with 10k items
set rd [redis_deferring_client]
set expected_frag 1.7
# add a mass of list nodes to two lists (allocations are interlaced)
set val [string repeat A 100] ;# 5 items of 100 bytes puts us in the 640 bytes bin, which has 32 regs, so high potential for fragmentation
for {set j 0} {$j < 500000} {incr j} {
$rd lpush biglist1 $val
$rd lpush biglist2 $val
}
for {set j 0} {$j < 500000} {incr j} {
$rd read ; # Discard replies
$rd read ; # Discard replies
}
# create some fragmentation
r del biglist2
# start defrag
after 120 ;# serverCron only updates the info once in 100ms
set frag [s allocator_frag_ratio]
if {$::verbose} {
puts "frag $frag"
}
assert {$frag >= $expected_frag}
r config set latency-monitor-threshold 5
r latency reset
set digest [r debug digest]
catch {r config set activedefrag yes} e
if {![string match {DISABLED*} $e]} {
# wait for the active defrag to start working (decision once a second)
wait_for_condition 50 100 {
[s active_defrag_running] ne 0
} else {
fail "defrag not started."
}
# wait for the active defrag to stop working
wait_for_condition 500 100 {
[s active_defrag_running] eq 0
} else {
after 120 ;# serverCron only updates the info once in 100ms
puts [r info memory]
puts [r info stats]
puts [r memory malloc-stats]
fail "defrag didn't stop."
}
# test the the fragmentation is lower
after 120 ;# serverCron only updates the info once in 100ms
set frag [s allocator_frag_ratio]
set max_latency 0
foreach event [r latency latest] {
lassign $event eventname time latency max
if {$eventname == "active-defrag-cycle"} {
set max_latency $max
}
}
if {$::verbose} {
puts "frag $frag"
puts "max latency $max_latency"
puts [r latency latest]
puts [r latency history active-defrag-cycle]
}
assert {$frag < 1.1}
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
assert {$max_latency <= 30}
}
# verify the data isn't corrupted or changed
set newdigest [r debug digest]
assert {$digest eq $newdigest}
r save ;# saving an rdb iterates over all the data / pointers
r del biglist1 ;# coverage for quicklistBookmarksClear
} {1}
} }
} }

View File

@ -45,18 +45,24 @@ start_server {tags {"modules"}} {
test {Module client blocked on keys (with metadata): Timeout} { test {Module client blocked on keys (with metadata): Timeout} {
r del k r del k
set rd [redis_deferring_client] set rd [redis_deferring_client]
$rd client id
set cid [$rd read]
r fsl.push k 33 r fsl.push k 33
$rd fsl.bpopgt k 35 1 $rd fsl.bpopgt k 35 1
assert_equal {Request timedout} [$rd read] assert_equal {Request timedout} [$rd read]
r client kill id $cid ;# try to smoke-out client-related memory leak
} }
test {Module client blocked on keys (with metadata): Blocked, case 1} { test {Module client blocked on keys (with metadata): Blocked, case 1} {
r del k r del k
set rd [redis_deferring_client] set rd [redis_deferring_client]
$rd client id
set cid [$rd read]
r fsl.push k 33 r fsl.push k 33
$rd fsl.bpopgt k 33 0 $rd fsl.bpopgt k 33 0
r fsl.push k 34 r fsl.push k 34
assert_equal {34} [$rd read] assert_equal {34} [$rd read]
r client kill id $cid ;# try to smoke-out client-related memory leak
} }
test {Module client blocked on keys (with metadata): Blocked, case 2} { test {Module client blocked on keys (with metadata): Blocked, case 2} {
@ -70,6 +76,35 @@ start_server {tags {"modules"}} {
assert_equal {36} [$rd read] assert_equal {36} [$rd read]
} }
test {Module client blocked on keys (with metadata): Blocked, CLIENT KILL} {
r del k
set rd [redis_deferring_client]
$rd client id
set cid [$rd read]
$rd fsl.bpopgt k 35 0
r client kill id $cid ;# try to smoke-out client-related memory leak
}
test {Module client blocked on keys (with metadata): Blocked, CLIENT UNBLOCK TIMEOUT} {
r del k
set rd [redis_deferring_client]
$rd client id
set cid [$rd read]
$rd fsl.bpopgt k 35 0
r client unblock $cid timeout ;# try to smoke-out client-related memory leak
assert_equal {Request timedout} [$rd read]
}
test {Module client blocked on keys (with metadata): Blocked, CLIENT UNBLOCK ERROR} {
r del k
set rd [redis_deferring_client]
$rd client id
set cid [$rd read]
$rd fsl.bpopgt k 35 0
r client unblock $cid error ;# try to smoke-out client-related memory leak
assert_error "*unblocked*" {$rd read}
}
test {Module client blocked on keys does not wake up on wrong type} { test {Module client blocked on keys does not wake up on wrong type} {
r del k r del k
set rd [redis_deferring_client] set rd [redis_deferring_client]

View File

@ -20,9 +20,8 @@ start_server {tags {"modules"}} {
test {Module fork kill} { test {Module fork kill} {
r fork.create 3 r fork.create 3
after 20 after 250
r fork.kill r fork.kill
after 100
assert {[count_log_message "fork child started"] eq "2"} assert {[count_log_message "fork child started"] eq "2"}
assert {[count_log_message "Received SIGUSR1 in child"] eq "1"} assert {[count_log_message "Received SIGUSR1 in child"] eq "1"}

View File

@ -20,11 +20,44 @@ tags "modules" {
wait_for_condition 5000 10 { wait_for_condition 5000 10 {
([$replica get timer] eq "10") && \ ([$replica get timer] eq "10") && \
([$replica get thread] eq "10") ([$replica get a-from-thread] eq "10")
} else { } else {
fail "The two counters don't match the expected value." fail "The two counters don't match the expected value."
} }
$master propagate-test-2
$master propagate-test-3
$master multi
$master propagate-test-2
$master propagate-test-3
$master exec
wait_for_ofs_sync $master $replica
assert_equal [s -1 unexpected_error_replies] 0
} }
} }
} }
} }
tags "modules aof" {
test {Modules RM_Replicate replicates MULTI/EXEC correctly} {
start_server [list overrides [list loadmodule "$testmodule"]] {
# Enable the AOF
r config set appendonly yes
r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
waitForBgrewriteaof r
r propagate-test-2
r propagate-test-3
r multi
r propagate-test-2
r propagate-test-3
r exec
# Load the AOF
r debug loadaof
assert_equal [s 0 unexpected_error_replies] 0
}
}
}

View File

@ -320,4 +320,76 @@ start_server {tags {"multi"}} {
$rd close $rd close
r ping r ping
} {PONG} } {PONG}
test {MULTI and script timeout} {
# check that if MULTI arrives during timeout, it is either refused, or
# allowed to pass, and we don't end up executing half of the transaction
set rd1 [redis_deferring_client]
set rd2 [redis_deferring_client]
r config set lua-time-limit 10
r set xx 1
$rd1 eval {while true do end} 0
after 200
catch { $rd2 multi; $rd2 read } e
catch { $rd2 incr xx; $rd2 read } e
r script kill
after 200 ; # Give some time to Lua to call the hook again...
catch { $rd2 incr xx; $rd2 read } e
catch { $rd2 exec; $rd2 read } e
set xx [r get xx]
# make sure that either the whole transcation passed or none of it (we actually expect none)
assert { $xx == 1 || $xx == 3}
# check that the connection is no longer in multi state
$rd2 ping asdf
set pong [$rd2 read]
assert_equal $pong "asdf"
}
test {EXEC and script timeout} {
# check that if EXEC arrives during timeout, we don't end up executing
# half of the transaction, and also that we exit the multi state
set rd1 [redis_deferring_client]
set rd2 [redis_deferring_client]
r config set lua-time-limit 10
r set xx 1
catch { $rd2 multi; $rd2 read } e
catch { $rd2 incr xx; $rd2 read } e
$rd1 eval {while true do end} 0
after 200
catch { $rd2 incr xx; $rd2 read } e
catch { $rd2 exec; $rd2 read } e
r script kill
after 200 ; # Give some time to Lua to call the hook again...
set xx [r get xx]
# make sure that either the whole transcation passed or none of it (we actually expect none)
assert { $xx == 1 || $xx == 3}
# check that the connection is no longer in multi state
$rd2 ping asdf
set pong [$rd2 read]
assert_equal $pong "asdf"
}
test {MULTI-EXEC body and script timeout} {
# check that we don't run an imcomplete transaction due to some commands
# arriving during busy script
set rd1 [redis_deferring_client]
set rd2 [redis_deferring_client]
r config set lua-time-limit 10
r set xx 1
catch { $rd2 multi; $rd2 read } e
catch { $rd2 incr xx; $rd2 read } e
$rd1 eval {while true do end} 0
after 200
catch { $rd2 incr xx; $rd2 read } e
r script kill
after 200 ; # Give some time to Lua to call the hook again...
catch { $rd2 exec; $rd2 read } e
set xx [r get xx]
# make sure that either the whole transcation passed or none of it (we actually expect none)
assert { $xx == 1 || $xx == 3}
# check that the connection is no longer in multi state
$rd2 ping asdf
set pong [$rd2 read]
assert_equal $pong "asdf"
}
} }

View File

@ -741,3 +741,8 @@ start_server {tags {"scripting repl"}} {
} }
} }
start_server {tags {"scripting"}} {
r script debug sync
r eval {return 'hello'} 0
r eval {return 'hello'} 0
}

66
tests/unit/tracking.tcl Normal file
View File

@ -0,0 +1,66 @@
start_server {tags {"tracking"}} {
# Create a deferred client we'll use to redirect invalidation
# messages to.
set rd1 [redis_deferring_client]
$rd1 client id
set redir [$rd1 read]
$rd1 subscribe __redis__:invalidate
$rd1 read ; # Consume the SUBSCRIBE reply.
test {Clients are able to enable tracking and redirect it} {
r CLIENT TRACKING on REDIRECT $redir
} {*OK}
test {The other connection is able to get invalidations} {
r SET a 1
r GET a
r INCR a
r INCR b ; # This key should not be notified, since it wasn't fetched.
set keys [lindex [$rd1 read] 2]
assert {[llength $keys] == 1}
assert {[lindex $keys 0] eq {a}}
}
test {The client is now able to disable tracking} {
# Make sure to add a few more keys in the tracking list
# so that we can check for leaks, as a side effect.
r MGET a b c d e f g
r CLIENT TRACKING off
}
test {Clients can enable the BCAST mode with the empty prefix} {
r CLIENT TRACKING on BCAST REDIRECT $redir
} {*OK*}
test {The connection gets invalidation messages about all the keys} {
r MSET a 1 b 2 c 3
set keys [lsort [lindex [$rd1 read] 2]]
assert {$keys eq {a b c}}
}
test {Clients can enable the BCAST mode with prefixes} {
r CLIENT TRACKING off
r CLIENT TRACKING on BCAST REDIRECT $redir PREFIX a: PREFIX b:
r MULTI
r INCR a:1
r INCR a:2
r INCR b:1
r INCR b:2
r EXEC
# Because of the internals, we know we are going to receive
# two separated notifications for the two different prefixes.
set keys1 [lsort [lindex [$rd1 read] 2]]
set keys2 [lsort [lindex [$rd1 read] 2]]
set keys [lsort [list {*}$keys1 {*}$keys2]]
assert {$keys eq {a:1 a:2 b:1 b:2}}
}
test {Adding prefixes to BCAST mode works} {
r CLIENT TRACKING on BCAST REDIRECT $redir PREFIX c:
r INCR c:1234
set keys [lsort [lindex [$rd1 read] 2]]
assert {$keys eq {c:1234}}
}
$rd1 close
}

View File

@ -390,6 +390,13 @@ start_server {tags {"hash"}} {
lappend rv [string match "ERR*not*float*" $bigerr] lappend rv [string match "ERR*not*float*" $bigerr]
} {1 1} } {1 1}
test {HINCRBYFLOAT fails against hash value that contains a null-terminator in the middle} {
r hset h f "1\x002"
catch {r hincrbyfloat h f 1} err
set rv {}
lappend rv [string match "ERR*not*float*" $err]
} {1}
test {HSTRLEN against the small hash} { test {HSTRLEN against the small hash} {
set err {} set err {}
foreach k [array names smallhash *] { foreach k [array names smallhash *] {

View File

@ -161,6 +161,36 @@ start_server {
assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {mystream {}} assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {mystream {}}
} }
test {XGROUP DESTROY should unblock XREADGROUP with -NOGROUP} {
r del mystream
r XGROUP CREATE mystream mygroup $ MKSTREAM
set rd [redis_deferring_client]
$rd XREADGROUP GROUP mygroup Alice BLOCK 100 STREAMS mystream ">"
r XGROUP DESTROY mystream mygroup
assert_error "*NOGROUP*" {$rd read}
}
test {RENAME can unblock XREADGROUP with data} {
r del mystream
r XGROUP CREATE mystream mygroup $ MKSTREAM
set rd [redis_deferring_client]
$rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
r XGROUP CREATE mystream2 mygroup $ MKSTREAM
r XADD mystream2 100 f1 v1
r RENAME mystream2 mystream
assert_equal "{mystream {{100-0 {f1 v1}}}}" [$rd read] ;# mystream2 had mygroup before RENAME
}
test {RENAME can unblock XREADGROUP with -NOGROUP} {
r del mystream
r XGROUP CREATE mystream mygroup $ MKSTREAM
set rd [redis_deferring_client]
$rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
r XADD mystream2 100 f1 v1
r RENAME mystream2 mystream
assert_error "*NOGROUP*" {$rd read} ;# mystream2 didn't have mygroup before RENAME
}
test {XCLAIM can claim PEL items from another consumer} { test {XCLAIM can claim PEL items from another consumer} {
# Add 3 items into the stream, and create a consumer group # Add 3 items into the stream, and create a consumer group
r del mystream r del mystream
@ -302,4 +332,17 @@ start_server {
} }
} }
} }
start_server {tags {"stream"} overrides {appendonly yes aof-use-rdb-preamble no}} {
test {Empty stream with no lastid can be rewrite into AOF correctly} {
r XGROUP CREATE mystream group-name $ MKSTREAM
assert {[dict get [r xinfo stream mystream] length] == 0}
set grpinfo [r xinfo groups mystream]
r bgrewriteaof
waitForBgrewriteaof r
r debug loadaof
assert {[dict get [r xinfo stream mystream] length] == 0}
assert {[r xinfo groups mystream] == $grpinfo}
}
}
} }