Merge branch 'unstable' into keydbpro
Former-commit-id: a830cf85df236885558c5571c0bf23cfb23e3655
This commit is contained in:
commit
cece963cf3
54
.github/workflows/daily.yml
vendored
Normal file
54
.github/workflows/daily.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
name: Daily
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 7 * * *'
|
||||
|
||||
jobs:
|
||||
test-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1200
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
|
||||
test-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1200
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make MALLOC=libc
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
|
||||
test-valgrind:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make valgrind
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5 valgrind -y
|
||||
./runtest --valgrind --verbose --clients 1
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --valgrind --verbose --clients 1
|
747
00-RELEASENOTES
747
00-RELEASENOTES
@ -11,6 +11,753 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
================================================================================
|
||||
Redis 6.0.3 Released Sat May 16 18:10:21 CEST 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency CRITICAL: a crash introduced in 6.0.2 is now fixed.
|
||||
|
||||
1eab62f7e Remove the client from CLOSE_ASAP list before caching the master.
|
||||
|
||||
================================================================================
|
||||
Redis 6.0.2 Released Fri May 15 22:24:36 CEST 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency MODERATE: many not critical bugfixes in different areas.
|
||||
Critical fix to client side caching when
|
||||
keys are evicted from the tracking table but
|
||||
no notifications are sent.
|
||||
|
||||
The following are the most serious fix:
|
||||
|
||||
* XPENDING should not update consumer's seen-time
|
||||
* optimize memory usage of deferred replies - fixed
|
||||
* Fix CRC64 initialization outside the Redis server itself.
|
||||
* stringmatchlen() should not expect null terminated strings.
|
||||
* Cluster nodes availability checks improved when there is
|
||||
high Pub/Sub load on the cluster bus.
|
||||
* Redis Benchmark: Fix coredump because of double free
|
||||
* Tracking: send eviction messages when evicting entries.
|
||||
* rax.c updated from upstream antirez/rax.
|
||||
* fix redis 6.0 not freeing closed connections during loading.
|
||||
|
||||
New features:
|
||||
|
||||
* Support setcpuaffinity on linux/bsd
|
||||
* Client Side Caching: Add Tracking Prefix Number Stats in Server Info
|
||||
* Add --user argument to redis-benchmark.c (ACL)
|
||||
|
||||
Full list of commits:
|
||||
|
||||
Yossi Gottlieb in commit 16ba33c05:
|
||||
TLS: Fix test failures on recent Debian/Ubuntu.
|
||||
1 file changed, 20 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 77ae66930:
|
||||
TLS: Add crypto locks for older OpenSSL support.
|
||||
1 file changed, 45 insertions(+)
|
||||
|
||||
David Carlier in commit 389697988:
|
||||
NetBSD build update.
|
||||
3 files changed, 30 insertions(+), 1 deletion(-)
|
||||
|
||||
Madelyn Olson in commit 2435341d7:
|
||||
Added a refcount on timer events to prevent deletion of recursive timer calls
|
||||
2 files changed, 12 insertions(+)
|
||||
|
||||
antirez in commit 80c906bd3:
|
||||
Cache master without checking of deferred close flags.
|
||||
3 files changed, 11 insertions(+), 8 deletions(-)
|
||||
|
||||
antirez in commit 74249be4a:
|
||||
Track events processed while blocked globally.
|
||||
5 files changed, 32 insertions(+), 17 deletions(-)
|
||||
|
||||
antirez in commit 8bf660af9:
|
||||
Some rework of #7234.
|
||||
4 files changed, 77 insertions(+), 65 deletions(-)
|
||||
|
||||
Oran Agra in commit 9da134cd8:
|
||||
fix redis 6.0 not freeing closed connections during loading.
|
||||
3 files changed, 133 insertions(+), 58 deletions(-)
|
||||
|
||||
antirez in commit f7f219a13:
|
||||
Regression test for #7249.
|
||||
1 file changed, 22 insertions(+)
|
||||
|
||||
antirez in commit 693629585:
|
||||
rax.c updated from upstream antirez/rax.
|
||||
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit e3b5648df:
|
||||
Tracking: send eviction messages when evicting entries.
|
||||
2 files changed, 29 insertions(+), 12 deletions(-)
|
||||
|
||||
Oran Agra in commit 5c41802d5:
|
||||
fix unstable replication test
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
ShooterIT in commit a23cdbb94:
|
||||
Redis Benchmark: Fix coredump because of double free
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 1276058ea:
|
||||
Cluster: clarify we always resolve the sender.
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 002fcde3d:
|
||||
Cluster: refactor ping/data delay handling.
|
||||
1 file changed, 13 insertions(+), 11 deletions(-)
|
||||
|
||||
antirez in commit 960186a71:
|
||||
Cluster: introduce data_received field.
|
||||
2 files changed, 27 insertions(+), 10 deletions(-)
|
||||
|
||||
antirez in commit 3672875b4:
|
||||
stringmatchlen() should not expect null terminated strings.
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
Brad Dunbar in commit 24e12641d:
|
||||
Remove unreachable branch.
|
||||
1 file changed, 2 deletions(-)
|
||||
|
||||
hwware in commit c7edffbd5:
|
||||
add jemalloc-bg-thread config in redis conf
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
hwware in commit 8a9c84f4a:
|
||||
add include guard for lolwut.h
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
antirez in commit cb683a84f:
|
||||
Don't propagate spurious MULTI on DEBUG LOADAOF.
|
||||
2 files changed, 6 insertions(+), 3 deletions(-)
|
||||
|
||||
antirez in commit 84d9766d6:
|
||||
Dump recent backlog on master query generating errors.
|
||||
1 file changed, 29 insertions(+)
|
||||
|
||||
Titouan Christophe in commit ec1e106ec:
|
||||
make struct user anonymous (only typedefed)
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit e48c37316:
|
||||
Test: --dont-clean should do first cleanup.
|
||||
1 file changed, 2 insertions(+), 5 deletions(-)
|
||||
|
||||
Benjamin Sergeant in commit 1e561cfaa:
|
||||
Add --user argument to redis-benchmark.c (ACL)
|
||||
1 file changed, 15 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit d1af82a88:
|
||||
Drop not needed part from #7194.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Muhammad Zahalqa in commit 897a360d0:
|
||||
Fix compiler warnings on function rev(unsigned long)
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
antirez in commit ac316d8cc:
|
||||
Move CRC64 initialization in main().
|
||||
2 files changed, 1 insertion(+), 4 deletions(-)
|
||||
|
||||
antirez in commit fc7bc3204:
|
||||
Fix CRC64 initialization outside the Redis server itself.
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
hwware in commit a6e55c096:
|
||||
Client Side Caching: Add Tracking Prefix Number Stats in Server Info
|
||||
3 files changed, 8 insertions(+)
|
||||
|
||||
antirez in commit b062fd523:
|
||||
Fix NetBSD build by fixing redis_set_thread_title() support.
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 4efb25d9c:
|
||||
Rework a bit the documentation for CPU pinning.
|
||||
2 files changed, 18 insertions(+), 8 deletions(-)
|
||||
|
||||
zhenwei pi in commit d6436eb7c:
|
||||
Support setcpuaffinity on linux/bsd
|
||||
12 files changed, 180 insertions(+), 1 deletion(-)
|
||||
|
||||
Guy Benoish in commit 3a441c7d9:
|
||||
XPENDING should not update consumer's seen-time
|
||||
4 files changed, 33 insertions(+), 20 deletions(-)
|
||||
|
||||
Oran Agra in commit 75addb4fe:
|
||||
optimize memory usage of deferred replies - fixed
|
||||
1 file changed, 29 insertions(+)
|
||||
|
||||
Deliang Yang in commit c57d9146f:
|
||||
reformat code
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Oran Agra in commit 3d3861dd8:
|
||||
add daily github actions with libc malloc and valgrind
|
||||
5 files changed, 106 insertions(+), 18 deletions(-)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.0.1 Released Sat May 02 00:06:07 CEST 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency HIGH: This release fixes a crash when builiding against
|
||||
Libc malloc.
|
||||
|
||||
Here we revert 8110ba888, an optimization that causes a crash due to a
|
||||
bug in the code. It does not happen with the default allocator because of
|
||||
differences between Jemalloc and libc malloc, so this escaped all our
|
||||
testing but was reported by a user. We'll add back the original optimization
|
||||
that was reverted here later, after checking what happens: it is not a
|
||||
critical optimization.
|
||||
|
||||
The other commits are minor stuff:
|
||||
|
||||
antirez in commit db73d0998:
|
||||
Cast printf() argument to the format specifier.
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 7c0fe7271:
|
||||
Revert "optimize memory usage of deferred replies"
|
||||
1 file changed, 31 deletions(-)
|
||||
|
||||
antirez in commit 8fe25edc7:
|
||||
Save a call to stopThreadedIOIfNeeded() for the base case.
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
================================================================================
|
||||
Redis 6.0.0 GA Released Thu Apr 30 14:55:02 CEST 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency CRITICAL: many bugs fixed compared to the last release
|
||||
candidate. Better to upgrade if you see things
|
||||
affecting your environment in the changelog.
|
||||
|
||||
Hi all, finally we have Redis 6.0.0 GA! Enjoy this new Redis release.
|
||||
Most of the documentation was updated today so that you can likely
|
||||
find what you are looking for about the new features at redis.io.
|
||||
This is the list of what changed compared to the previoius release candidate:
|
||||
|
||||
* XCLAIM AOF/replicas propagation fixed.
|
||||
* Client side caching: new NOLOOP option to avoid getting notified about
|
||||
changes performed by ourselves.
|
||||
* ACL GENPASS now uses HMAC-SHA256 and have an optional "bits" argument.
|
||||
It means you can use it as a general purpose "secure random strings"
|
||||
primitive!
|
||||
* Cluster "SLOTS" subcommand memory optimization.
|
||||
* The LCS command is now a subcommand of STRALGO.
|
||||
* Meaningful offset for replicas as well. More successful partial
|
||||
resynchronizations.
|
||||
* Optimize memory usage of deferred replies.
|
||||
* Faster CRC64 algorithm for faster RDB loading.
|
||||
* XINFO STREAM FULL, a new subcommand to get the whole stream state.
|
||||
* CLIENT KILL USER <username>.
|
||||
* MIGRATE AUTH2 option, for ACL style authentication support.
|
||||
* Other random bugfixes.
|
||||
|
||||
Enjoy Redis 6! :-)
|
||||
Goodbye antirez
|
||||
|
||||
List of commits in this release:
|
||||
|
||||
antirez in commit 1f9b82bd5:
|
||||
Update help.h again before Redis 6 GA.
|
||||
1 file changed, 17 insertions(+), 12 deletions(-)
|
||||
|
||||
antirez in commit 3fcffe7d0:
|
||||
redis-cli: fix hints with subcommands.
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 455d8a05c:
|
||||
redis-cli command help updated.
|
||||
1 file changed, 165 insertions(+), 25 deletions(-)
|
||||
|
||||
zhaozhao.zz in commit 70287bbc9:
|
||||
lazyfree & eviction: record latency generated by lazyfree eviction
|
||||
1 file changed, 18 insertions(+), 13 deletions(-)
|
||||
|
||||
antirez in commit 7be21139a:
|
||||
MIGRATE AUTH2 for ACL support.
|
||||
1 file changed, 19 insertions(+), 5 deletions(-)
|
||||
|
||||
antirez in commit e1ee1a49d:
|
||||
CLIENT KILL USER <username>.
|
||||
1 file changed, 11 insertions(+)
|
||||
|
||||
antirez in commit d56f058c0:
|
||||
Fix tracking table max keys option in redis.conf.
|
||||
1 file changed, 12 insertions(+), 9 deletions(-)
|
||||
|
||||
antirez in commit 96dd5fc93:
|
||||
redis-cli: safer cluster fix with unreachalbe masters.
|
||||
1 file changed, 26 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 5b59d9c5d:
|
||||
redis-cli: simplify cluster nodes coverage display.
|
||||
1 file changed, 10 insertions(+), 17 deletions(-)
|
||||
|
||||
antirez in commit c163d4add:
|
||||
redis-cli: try to make clusterManagerFixOpenSlot() more readable.
|
||||
1 file changed, 25 insertions(+), 6 deletions(-)
|
||||
|
||||
Guy Benoish in commit aab74b715:
|
||||
XINFO STREAM FULL should have a default COUNT of 10
|
||||
1 file changed, 8 insertions(+), 4 deletions(-)
|
||||
|
||||
antirez in commit 606134f9d:
|
||||
Comment clearly why we moved some code in #6623.
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
srzhao in commit ee627bb66:
|
||||
fix pipelined WAIT performance issue.
|
||||
1 file changed, 13 insertions(+), 13 deletions(-)
|
||||
|
||||
antirez in commit 47b8a7f9b:
|
||||
Fix create-cluster BIN_PATH.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Guy Benoish in commit 6c0bc608a:
|
||||
Extend XINFO STREAM output
|
||||
2 files changed, 226 insertions(+), 34 deletions(-)
|
||||
|
||||
hwware in commit 5bfc18950:
|
||||
Fix not used marco in cluster.c
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Itamar Haber in commit 56d628f85:
|
||||
Update create-cluster
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Itamar Haber in commit cac9d7cf7:
|
||||
Adds `BIN_PATH` to create-cluster
|
||||
1 file changed, 8 insertions(+), 6 deletions(-)
|
||||
|
||||
Oran Agra in commit b712fba17:
|
||||
hickup, re-fix dictEncObjKeyCompare
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
Oran Agra in commit ea63aea72:
|
||||
fix loading race in psync2 tests
|
||||
3 files changed, 15 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 64e588bfa:
|
||||
Rework comment in dictEncObjKeyCompare().
|
||||
1 file changed, 8 insertions(+), 9 deletions(-)
|
||||
|
||||
Oran Agra in commit 0d1e8c93b:
|
||||
allow dictFind using static robj
|
||||
1 file changed, 9 insertions(+), 4 deletions(-)
|
||||
|
||||
Madelyn Olson in commit a1bed447b:
|
||||
Added crcspeed library
|
||||
2 files changed, 341 insertions(+)
|
||||
|
||||
Madelyn Olson in commit a75fa3aad:
|
||||
Made crc64 test consistent
|
||||
1 file changed, 3 insertions(+), 2 deletions(-)
|
||||
|
||||
Madelyn Olson in commit 52c75e9db:
|
||||
Implemented CRC64 based on slice by 4
|
||||
5 files changed, 124 insertions(+), 157 deletions(-)
|
||||
|
||||
Oran Agra in commit 8110ba888:
|
||||
optimize memory usage of deferred replies
|
||||
1 file changed, 31 insertions(+)
|
||||
|
||||
Oran Agra in commit e4d2bb62b:
|
||||
Keep track of meaningful replication offset in replicas too
|
||||
5 files changed, 212 insertions(+), 92 deletions(-)
|
||||
|
||||
antirez in commit fea9788cc:
|
||||
Fix STRALGO command flags.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Dave-in-lafayette in commit 2144047e1:
|
||||
fix for unintended crash during panic response
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Guy Benoish in commit 43329c9b6:
|
||||
Add the stream tag to XSETID tests
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Dave-in-lafayette in commit 1e17d3de7:
|
||||
fix for crash during panic before all threads are up
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 3722f89f4:
|
||||
LCS -> STRALGO LCS.
|
||||
4 files changed, 28 insertions(+), 15 deletions(-)
|
||||
|
||||
antirez in commit 373ae6061:
|
||||
Also use propagate() in streamPropagateGroupID().
|
||||
1 file changed, 11 insertions(+), 1 deletion(-)
|
||||
|
||||
yanhui13 in commit f03f1fad6:
|
||||
add tcl test for cluster slots
|
||||
1 file changed, 44 insertions(+)
|
||||
|
||||
yanhui13 in commit 374ffdf1c:
|
||||
optimize the output of cluster slots
|
||||
1 file changed, 7 insertions(+), 4 deletions(-)
|
||||
|
||||
antirez in commit 4db38d2ef:
|
||||
Minor aesthetic changes to #7135.
|
||||
1 file changed, 5 insertions(+), 7 deletions(-)
|
||||
|
||||
Valentino Geron in commit f0a261448:
|
||||
XREADGROUP with NOACK should propagate only one XGROUP SETID command
|
||||
1 file changed, 13 insertions(+), 7 deletions(-)
|
||||
|
||||
antirez in commit fbdef6a9b:
|
||||
ACL: re-enable command execution of disabled users.
|
||||
1 file changed, 4 deletions(-)
|
||||
|
||||
antirez in commit 05a41da75:
|
||||
getRandomBytes(): use HMAC-SHA256.
|
||||
1 file changed, 30 insertions(+), 10 deletions(-)
|
||||
|
||||
antirez in commit 345c3768d:
|
||||
ACL GENPASS: take number of bits as argument.
|
||||
1 file changed, 21 insertions(+), 6 deletions(-)
|
||||
|
||||
antirez in commit 639c8a1d9:
|
||||
ACL GENPASS: emit 256 bits instead of 128.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 321acea03:
|
||||
ACL: deny commands execution of disabled users.
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
Theo Buehler in commit b0920e6e8:
|
||||
TLS: Fix build with SSL_OP_NO_CLIENT_RENEGOTIATION
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Yossi Gottlieb in commit 149b658b5:
|
||||
TLS: Fix build on older verisons of OpenSSL.
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
antirez in commit 06917e581:
|
||||
Tracking: test expired keys notifications.
|
||||
1 file changed, 13 insertions(+)
|
||||
|
||||
antirez in commit e434b2ce4:
|
||||
Tracking: NOLOOP tests.
|
||||
1 file changed, 32 insertions(+)
|
||||
|
||||
antirez in commit f3a172887:
|
||||
Tracking: signal key as modified when evicting.
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
antirez in commit e63bb7ec8:
|
||||
Tracking: NOLOOP further implementation and fixes.
|
||||
2 files changed, 21 insertions(+), 6 deletions(-)
|
||||
|
||||
antirez in commit 6791ff052:
|
||||
Tracking: NOLOOP internals implementation.
|
||||
17 files changed, 174 insertions(+), 112 deletions(-)
|
||||
|
||||
antirez in commit 725b8cc68:
|
||||
Implement redis_set_thread_title for MacOS.
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
zhenwei pi in commit 3575b8706:
|
||||
Threaded IO: set thread name for redis-server
|
||||
3 files changed, 28 insertions(+)
|
||||
|
||||
antirez in commit a76c67578:
|
||||
Sentinel: small refactoring of sentinelCollectTerminatedScripts().
|
||||
1 file changed, 1 insertion(+), 2 deletions(-)
|
||||
|
||||
omg-by in commit 3a27064c4:
|
||||
fix(sentinel): sentinel.running_scripts will always increase more times and not reset
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
antirez in commit 5c4c73e2c:
|
||||
A few comments and name changes for #7103.
|
||||
1 file changed, 13 insertions(+), 4 deletions(-)
|
||||
|
||||
Oran Agra in commit 6148f9493:
|
||||
testsuite run the defrag latency test solo
|
||||
3 files changed, 42 insertions(+), 2 deletions(-)
|
||||
|
||||
Jamie Scott in commit 51d3012d4:
|
||||
Adding acllog-max-len to Redis.conf
|
||||
1 file changed, 9 insertions(+)
|
||||
|
||||
antirez in commit c39f16c42:
|
||||
Fix XCLAIM propagation in AOF/replicas for blocking XREADGROUP.
|
||||
2 files changed, 8 insertions(+), 3 deletions(-)
|
||||
|
||||
================================================================================
|
||||
Redis 6.0-rc4 Released Thu Apr 16 16:10:35 CEST 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: If you are using RC3 without issues, don't rush.
|
||||
|
||||
Hi all, this the latest release candidate of Redis 6. This is likely to
|
||||
be very similar to what you'll see in Redis 6 GA. Please test it and
|
||||
report any issue :-)
|
||||
|
||||
Main changes in this release:
|
||||
|
||||
* Big INFO speedup when using a lot of of clients.
|
||||
* Big speedup on all the blocking commands: now blocking
|
||||
on the same key is O(1) instead of being O(N).
|
||||
* Stale replicas now allow MULTI/EXEC.
|
||||
* New command: LCS (Longest Common Subsequence).
|
||||
* Add a new configuration to make DEL like UNLINK.
|
||||
* RDB loading speedup.
|
||||
* Many bugs fixed (see the commit messages at the end of this node)
|
||||
|
||||
See you in 14 days for Redis 6 GA.
|
||||
|
||||
List of commits:
|
||||
|
||||
antirez in commit 9f594e243:
|
||||
Update SDS to latest version.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 48781dd95:
|
||||
RESP3: fix HELLO map len in Sentinel mode.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 371ab0cff:
|
||||
Don't allow empty spaces in ACL usernames.
|
||||
1 file changed, 36 insertions(+), 8 deletions(-)
|
||||
|
||||
antirez in commit b86140ac5:
|
||||
Don't allow empty spaces in ACL key patterns.
|
||||
1 file changed, 12 insertions(+), 1 deletion(-)
|
||||
|
||||
liumiuyong in commit a7ee3c3e7:
|
||||
FIX: truncate max/min longitude,latitude related geo_point (ex: {180, 85.05112878} )
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
Guy Benoish in commit e5b9eb817:
|
||||
Typo in getTimeoutFromObjectOrReply's error reply
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 0f31bb5c1:
|
||||
Fix HELLO reply in Sentinel mode, see #6160.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
hwware in commit b92d9a895:
|
||||
fix spelling in acl.c
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit 8f896e57a:
|
||||
Fix zsetAdd() top comment spelling.
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
hayleeliu in commit 8f5157058:
|
||||
fix spelling mistake in bitops.c
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit ddeda9ceb:
|
||||
Fix function names in zslDeleteNode() top comment.
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit bde1f0a8e:
|
||||
RESP3: change streams items from maps to arrays.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit bec68bff2:
|
||||
Use the special static refcount for stack objects.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 0f239e51b:
|
||||
RDB: refactor some RDB loading code into dbAddRDBLoad().
|
||||
3 files changed, 22 insertions(+), 4 deletions(-)
|
||||
|
||||
antirez in commit f855db61b:
|
||||
incrRefCount(): abort on statically allocated object.
|
||||
2 files changed, 12 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit 23094ba01:
|
||||
More powerful DEBUG RELOAD.
|
||||
3 files changed, 55 insertions(+), 16 deletions(-)
|
||||
|
||||
antirez in commit 8161a7a3e:
|
||||
RDB: clarify a condition in rdbLoadRio().
|
||||
2 files changed, 9 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit 61b153073:
|
||||
RDB: load files faster avoiding useless free+realloc.
|
||||
7 files changed, 40 insertions(+), 28 deletions(-)
|
||||
|
||||
antirez in commit 414debfd0:
|
||||
Speedup: unblock clients on keys in O(1).
|
||||
4 files changed, 50 insertions(+), 23 deletions(-)
|
||||
|
||||
antirez in commit cbcd07777:
|
||||
Fix ACL HELP table missing comma.
|
||||
1 file changed, 12 insertions(+), 12 deletions(-)
|
||||
|
||||
mymilkbottles in commit 2437455f2:
|
||||
Judge the log level in advance
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
antirez in commit 35c64b898:
|
||||
Speedup INFO by counting client memory incrementally.
|
||||
4 files changed, 52 insertions(+), 26 deletions(-)
|
||||
|
||||
qetu3790 in commit c3ac71748:
|
||||
fix comments about RESIZE DB opcode in rdb.c
|
||||
1 file changed, 1 insertion(+), 4 deletions(-)
|
||||
|
||||
antirez in commit c8dbcff9d:
|
||||
Clarify redis.conf comment about lazyfree-lazy-user-del.
|
||||
1 file changed, 9 insertions(+), 5 deletions(-)
|
||||
|
||||
zhaozhao.zz in commit abd5156f2:
|
||||
lazyfree: add a new configuration lazyfree-lazy-user-del
|
||||
4 files changed, 7 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit 5719b3054:
|
||||
LCS: more tests.
|
||||
1 file changed, 8 insertions(+)
|
||||
|
||||
antirez in commit c89e1f293:
|
||||
LCS: allow KEYS / STRINGS to be anywhere.
|
||||
1 file changed, 6 deletions(-)
|
||||
|
||||
antirez in commit 0b16f8d44:
|
||||
LCS tests.
|
||||
1 file changed, 22 insertions(+)
|
||||
|
||||
antirez in commit 9254a805d:
|
||||
LCS: get rid of STOREIDX option. Fix get keys helper.
|
||||
2 files changed, 20 insertions(+), 21 deletions(-)
|
||||
|
||||
antirez in commit a4c490703:
|
||||
LCS: fix stale comment.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit cb92c23de:
|
||||
LCS: output LCS len as well in IDX mode.
|
||||
1 file changed, 6 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 56a52e804:
|
||||
LCS: MINMATCHLEN and WITHMATCHLEN options.
|
||||
1 file changed, 24 insertions(+), 11 deletions(-)
|
||||
|
||||
antirez in commit ebb09a5c3:
|
||||
LCS: 7x speedup by accessing the array with better locality.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit a9f8a8cba:
|
||||
LCS: implement KEYS option.
|
||||
1 file changed, 18 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit 4aa24e62a:
|
||||
LCS: other fixes to range emission.
|
||||
1 file changed, 20 insertions(+), 16 deletions(-)
|
||||
|
||||
antirez in commit 2b67b6b87:
|
||||
LCS: fix emission of last range starting at index 0.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 420aac727:
|
||||
LCS: implement range indexes option.
|
||||
1 file changed, 59 insertions(+), 9 deletions(-)
|
||||
|
||||
antirez in commit a518a9a76:
|
||||
LCS: initial functionality implemented.
|
||||
4 files changed, 156 insertions(+), 1 deletion(-)
|
||||
|
||||
srzhao in commit 026cc11b0:
|
||||
Check OOM at script start to get stable lua OOM state.
|
||||
3 files changed, 11 insertions(+), 4 deletions(-)
|
||||
|
||||
Oran Agra in commit 02b594f6a:
|
||||
diffrent fix for runtest --host --port
|
||||
2 files changed, 13 insertions(+), 13 deletions(-)
|
||||
|
||||
Guy Benoish in commit f695d1830:
|
||||
Try to fix time-sensitive tests in blockonkey.tcl
|
||||
1 file changed, 54 insertions(+), 1 deletion(-)
|
||||
|
||||
Guy Benoish in commit 0e42cfc36:
|
||||
Use __attribute__ only if __GNUC__ is defined
|
||||
1 file changed, 12 insertions(+), 3 deletions(-)
|
||||
|
||||
Guy Benoish in commit 91ed9b3c4:
|
||||
Modules: Perform printf-like format checks in variadic API
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
Valentino Geron in commit 3e0d20962:
|
||||
XREAD and XREADGROUP should not be allowed from scripts when BLOCK option is being used
|
||||
3 files changed, 18 insertions(+), 2 deletions(-)
|
||||
|
||||
Guy Benoish in commit 240094c9b:
|
||||
Stale replica should allow MULTI/EXEC
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
Xudong Zhang in commit 209f3a1eb:
|
||||
fix integer overflow
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
Guy Benoish in commit 024c380b9:
|
||||
Fix no-negative-zero test
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
Oran Agra in commit a38ff404b:
|
||||
modules don't signalModifiedKey in setKey() since that's done (optionally) in RM_CloseKey
|
||||
4 files changed, 8 insertions(+), 8 deletions(-)
|
||||
|
||||
Oran Agra in commit 814874d68:
|
||||
change CI to build and run the module api tests
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
Oran Agra in commit 061616c1b:
|
||||
fix possible warning on incomplete struct init
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Guy Benoish in commit 7764996be:
|
||||
Make sure Redis does not reply with negative zero
|
||||
2 files changed, 10 insertions(+)
|
||||
|
||||
Guy Benoish in commit eba28e2ce:
|
||||
DEBUG OBJECT should pass keyname to module when loading
|
||||
3 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
David Carlier in commit 15c9e79a7:
|
||||
debug, dump registers on arm too.
|
||||
1 file changed, 55 insertions(+), 27 deletions(-)
|
||||
|
||||
hwware in commit cd2b5df97:
|
||||
fix spelling in cluster.c
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Valentino Geron in commit 8cdc153f5:
|
||||
XACK should be executed in a "all or nothing" fashion.
|
||||
2 files changed, 23 insertions(+), 1 deletion(-)
|
||||
|
||||
hwware in commit b35407fa7:
|
||||
add check for not switching between optin optout mode directly
|
||||
1 file changed, 12 insertions(+), 1 deletion(-)
|
||||
|
||||
hwware in commit 4395889c9:
|
||||
add check for not providing both optin optout flag
|
||||
1 file changed, 8 insertions(+)
|
||||
|
||||
Guy Benoish in commit 1907e0f18:
|
||||
PERSIST should notify a keyspace event
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
Guy Benoish in commit c35a53169:
|
||||
streamReplyWithRange: Redundant XSETIDs to replica
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 6fe66e096:
|
||||
Simplify comment in moduleTryServeClientBlockedOnKey().
|
||||
1 file changed, 3 insertions(+), 12 deletions(-)
|
||||
|
||||
Guy Benoish in commit 193fc241c:
|
||||
Fix memory corruption in moduleHandleBlockedClients
|
||||
3 files changed, 149 insertions(+), 46 deletions(-)
|
||||
|
||||
================================================================================
|
||||
Redis 6.0-rc3 Released Tue Mar 31 17:42:39 CEST 2020
|
||||
================================================================================
|
||||
|
@ -3,6 +3,8 @@
|
||||
[](https://gitter.im/KeyDB/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://stackshare.io/eq-alpha-technology-inc/eq-alpha-technology-inc)
|
||||
|
||||
##### New! Want to extend KeyDB with Javascript? Try [ModJS](https://github.com/JohnSully/ModJS)
|
||||
|
||||
##### Need Help? Check out our extensive [documentation](https://docs.keydb.dev).
|
||||
|
||||
##### Have feedback? Take our quick survey: https://www.surveymonkey.com/r/Y9XNS93
|
||||
|
68
keydb.conf
68
keydb.conf
@ -626,20 +626,23 @@ replica-priority 100
|
||||
# to track the keys fetched by many clients.
|
||||
#
|
||||
# For this reason it is possible to configure a maximum fill value for the
|
||||
# invalidation table. By default it is set to 10%, and once this limit is
|
||||
# reached, Redis will start to evict caching slots in the invalidation table
|
||||
# even if keys are not modified, just to reclaim memory: this will in turn
|
||||
# invalidation table. By default it is set to 1M of keys, and once this limit
|
||||
# is reached, Redis will start to evict keys in the invalidation table
|
||||
# even if they were not modified, just to reclaim memory: this will in turn
|
||||
# force the clients to invalidate the cached values. Basically the table
|
||||
# maximum fill rate is a trade off between the memory you want to spend server
|
||||
# maximum size is a trade off between the memory you want to spend server
|
||||
# side to track information about who cached what, and the ability of clients
|
||||
# to retain cached objects in memory.
|
||||
#
|
||||
# If you set the value to 0, it means there are no limits, and all the 16
|
||||
# millions of caching slots can be used at the same time. In the "stats"
|
||||
# INFO section, you can find information about the amount of caching slots
|
||||
# used at every given moment.
|
||||
# If you set the value to 0, it means there are no limits, and Redis will
|
||||
# retain as many keys as needed in the invalidation table.
|
||||
# In the "stats" INFO section, you can find information about the number of
|
||||
# keys in the invalidation table at every given moment.
|
||||
#
|
||||
# tracking-table-max-fill 10
|
||||
# Note: when key tracking is used in broadcasting mode, no memory is used
|
||||
# in the server side so this setting is useless.
|
||||
#
|
||||
# tracking-table-max-keys 1000000
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
@ -737,6 +740,15 @@ replica-priority 100
|
||||
# For more information about ACL configuration please refer to
|
||||
# the Redis web site at https://redis.io/topics/acl
|
||||
|
||||
# ACL LOG
|
||||
#
|
||||
# The ACL Log tracks failed commands and authentication events associated
|
||||
# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
|
||||
# by ACLs. The ACL Log is stored in and consumes memory. There is no limit
|
||||
# to its length.You can reclaim memory with ACL LOG RESET or set a maximum
|
||||
# length below.
|
||||
acllog-max-len 128
|
||||
|
||||
# Using an external ACL file
|
||||
#
|
||||
# Instead of configuring users here in this file, it is possible to use
|
||||
@ -943,13 +955,20 @@ replica-priority 100
|
||||
# In all the above cases the default is to delete objects in a blocking way,
|
||||
# like if DEL was called. However you can configure each case specifically
|
||||
# in order to instead release memory in a non-blocking way like if UNLINK
|
||||
# was called, using the following configuration directives:
|
||||
# was called, using the following configuration directives.
|
||||
|
||||
lazyfree-lazy-eviction no
|
||||
lazyfree-lazy-expire no
|
||||
lazyfree-lazy-server-del no
|
||||
replica-lazy-flush no
|
||||
|
||||
# It is also possible, for the case when to replace the user code DEL calls
|
||||
# with UNLINK calls is not easy, to modify the default behavior of the DEL
|
||||
# command to act exactly like UNLINK, using the following configuration
|
||||
# directive:
|
||||
|
||||
lazyfree-lazy-user-del no
|
||||
|
||||
################################ THREADED I/O #################################
|
||||
|
||||
# Redis is mostly single threaded, however there are certain threaded
|
||||
@ -1763,6 +1782,35 @@ rdb-save-incremental-fsync yes
|
||||
# the main dictionary scan
|
||||
# active-defrag-max-scan-fields 1000
|
||||
|
||||
# Jemalloc background thread for purging will be enabled by default
|
||||
jemalloc-bg-thread yes
|
||||
|
||||
# It is possible to pin different threads and processes of Redis to specific
|
||||
# CPUs in your system, in order to maximize the performances of the server.
|
||||
# This is useful both in order to pin different Redis threads in different
|
||||
# CPUs, but also in order to make sure that multiple Redis instances running
|
||||
# in the same host will be pinned to different CPUs.
|
||||
#
|
||||
# Normally you can do this using the "taskset" command, however it is also
|
||||
# possible to this via Redis configuration directly, both in Linux and FreeBSD.
|
||||
#
|
||||
# You can pin the server/IO threads, bio threads, aof rewrite child process, and
|
||||
# the bgsave child process. The syntax to specify the cpu list is the same as
|
||||
# the taskset command:
|
||||
#
|
||||
# Set redis server/io threads to cpu affinity 0,2,4,6:
|
||||
# server_cpulist 0-7:2
|
||||
#
|
||||
# Set bio threads to cpu affinity 1,3:
|
||||
# bio_cpulist 1,3
|
||||
#
|
||||
# Set aof rewrite child process to cpu affinity 8,9,10,11:
|
||||
# aof_rewrite_cpulist 8-11
|
||||
#
|
||||
# Set bgsave child process to cpu affinity 1,10,11
|
||||
# bgsave_cpulist 1,10-11
|
||||
|
||||
|
||||
# Path to directory for file backed scratchpad. The file backed scratchpad
|
||||
# reduces memory requirements by storing rarely accessed data on disk
|
||||
# instead of RAM. A temporary file will be created in this directory.
|
||||
|
14
src/Makefile
14
src/Makefile
@ -172,6 +172,14 @@ else
|
||||
ifeq ($(uname_S),DragonFly)
|
||||
# FreeBSD
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),OpenBSD)
|
||||
# OpenBSD
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),NetBSD)
|
||||
# NetBSD
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
@ -184,6 +192,8 @@ endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/license/
|
||||
FINAL_CXXFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/rocksdb/include/ -I../deps/license
|
||||
@ -267,9 +277,9 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=keydb-pro-server
|
||||
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 motd.o timeout.o AsyncWorkQueue.o snapshot.o storage/rocksdb.o storage/rocksdbfactory.o storage/teststorageprovider.o keydbutils.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 crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o 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 setcpuaffinity.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_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 crcspeed.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_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o $(ASM_OBJ)
|
||||
REDIS_CHECK_RDB_NAME=keydb-check-rdb
|
||||
|
94
src/acl.cpp
94
src/acl.cpp
@ -32,6 +32,7 @@ extern "C" {
|
||||
#include "sha256.h"
|
||||
}
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* =============================================================================
|
||||
* Global state for ACLs
|
||||
@ -171,6 +172,18 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
||||
* Low level ACL API
|
||||
* ==========================================================================*/
|
||||
|
||||
/* Return 1 if the specified string contains spaces or null characters.
|
||||
* We do this for usernames and key patterns for simpler rewriting of
|
||||
* ACL rules, presentation on ACL list, and to avoid subtle security bugs
|
||||
* that may arise from parsing the rules in presence of escapes.
|
||||
* The function returns 0 if the string has no spaces. */
|
||||
int ACLStringHasSpaces(const char *s, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (isspace(s[i]) || s[i] == 0) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Given the category name the command returns the corresponding flag, or
|
||||
* zero if there is no match. */
|
||||
uint64_t ACLGetCommandCategoryFlagByName(const char *name) {
|
||||
@ -692,7 +705,8 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
||||
*
|
||||
* When an error is returned, errno is set to the following values:
|
||||
*
|
||||
* EINVAL: The specified opcode is not understood.
|
||||
* EINVAL: The specified opcode is not understood or the key pattern is
|
||||
* invalid (contains non allowed characters).
|
||||
* ENOENT: The command name or command category provided with + or - is not
|
||||
* known.
|
||||
* EBUSY: The subcommand you want to add is about a command that is currently
|
||||
@ -791,6 +805,10 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
errno = EEXIST;
|
||||
return C_ERR;
|
||||
}
|
||||
if (ACLStringHasSpaces(op+1,oplen-1)) {
|
||||
errno = EINVAL;
|
||||
return C_ERR;
|
||||
}
|
||||
sds newpat = sdsnewlen(op+1,oplen-1);
|
||||
listNode *ln = listSearchKey(u->patterns,newpat);
|
||||
/* Avoid re-adding the same pattern multiple times. */
|
||||
@ -1166,6 +1184,12 @@ int ACLLoadConfiguredUsers(void) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds *aclrules = (sds*)listNodeValue(ln);
|
||||
sds username = aclrules[0];
|
||||
|
||||
if (ACLStringHasSpaces(aclrules[0],sdslen(aclrules[0]))) {
|
||||
serverLog(LL_WARNING,"Spaces not allowed in ACL usernames");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
user *u = ACLCreateUser(username,sdslen(username));
|
||||
if (!u) {
|
||||
u = ACLGetUserByName(username,sdslen(username));
|
||||
@ -1291,6 +1315,14 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Spaces are not allowed in usernames. */
|
||||
if (ACLStringHasSpaces(argv[1],sdslen(argv[1]))) {
|
||||
errors = sdscatprintf(errors,
|
||||
"'%s:%d: username '%s' contains invalid characters. ",
|
||||
g_pserver->acl_filename, linenum, argv[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to process the line using the fake user to validate iif
|
||||
* the rules are able to apply cleanly. */
|
||||
ACLSetUser(fakeuser,"reset",-1);
|
||||
@ -1592,7 +1624,7 @@ void addACLLogEntry(client *c, int reason, int keypos, sds username) {
|
||||
* ACL SETUSER <username> ... acl rules ...
|
||||
* ACL DELUSER <username> [...]
|
||||
* ACL GETUSER <username>
|
||||
* ACL GENPASS
|
||||
* ACL GENPASS [<bits>]
|
||||
* ACL WHOAMI
|
||||
* ACL LOG [<count> | RESET]
|
||||
*/
|
||||
@ -1600,6 +1632,13 @@ void aclCommand(client *c) {
|
||||
char *sub = szFromObj(c->argv[1]);
|
||||
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
|
||||
sds username = szFromObj(c->argv[2]);
|
||||
/* Check username validity. */
|
||||
if (ACLStringHasSpaces(username,sdslen(username))) {
|
||||
addReplyErrorFormat(c,
|
||||
"Usernames can't contain spaces or null characters");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a temporary user to validate and stage all changes against
|
||||
* before applying to an existing user or creating a new user. If all
|
||||
* arguments are valid the user parameters will all be applied together.
|
||||
@ -1777,16 +1816,31 @@ void aclCommand(client *c) {
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c,dl,arraylen);
|
||||
} else if (!strcasecmp(sub,"genpass") && c->argc == 2) {
|
||||
char pass[32]; /* 128 bits of actual pseudo random data. */
|
||||
getRandomHexChars(pass,sizeof(pass));
|
||||
addReplyBulkCBuffer(c,pass,sizeof(pass));
|
||||
} else if (!strcasecmp(sub,"genpass") && (c->argc == 2 || c->argc == 3)) {
|
||||
#define GENPASS_MAX_BITS 4096
|
||||
char pass[GENPASS_MAX_BITS/8*2]; /* Hex representation. */
|
||||
long bits = 256; /* By default generate 256 bits passwords. */
|
||||
|
||||
if (c->argc == 3 && getLongFromObjectOrReply(c,c->argv[2],&bits,NULL)
|
||||
!= C_OK) return;
|
||||
|
||||
if (bits <= 0 || bits > GENPASS_MAX_BITS) {
|
||||
addReplyErrorFormat(c,
|
||||
"ACL GENPASS argument must be the number of "
|
||||
"bits for the output password, a positive number "
|
||||
"up to %d",GENPASS_MAX_BITS);
|
||||
return;
|
||||
}
|
||||
|
||||
long chars = (bits+3)/4; /* Round to number of characters to emit. */
|
||||
getRandomHexChars(pass,chars);
|
||||
addReplyBulkCBuffer(c,pass,chars);
|
||||
} 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. */
|
||||
* the number of entries the user wants to display, or alternatively
|
||||
* the "RESET" command in order to flush the old entries. */
|
||||
if (c->argc == 3) {
|
||||
if (!strcasecmp(szFromObj(c->argv[2]),"reset")) {
|
||||
listSetFreeMethod(ACLLog,(void(*)(const void*))ACLFreeLogEntry);
|
||||
@ -1849,18 +1903,18 @@ void aclCommand(client *c) {
|
||||
}
|
||||
} else if (!strcasecmp(sub,"help")) {
|
||||
const char *help[] = {
|
||||
"LOAD -- Reload users from the ACL file.",
|
||||
"SAVE -- Save the current config to the ACL file."
|
||||
"LIST -- Show user details in config file format.",
|
||||
"USERS -- List all the registered usernames.",
|
||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
||||
"GETUSER <username> -- Get the user details.",
|
||||
"DELUSER <username> [...] -- Delete a list of users.",
|
||||
"CAT -- List available categories.",
|
||||
"CAT <category> -- List commands inside category.",
|
||||
"GENPASS -- Generate a secure user password.",
|
||||
"WHOAMI -- Return the current connection username.",
|
||||
"LOG [<count> | RESET] -- Show the ACL log entries.",
|
||||
"LOAD -- Reload users from the ACL file.",
|
||||
"SAVE -- Save the current config to the ACL file.",
|
||||
"LIST -- Show user details in config file format.",
|
||||
"USERS -- List all the registered usernames.",
|
||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
||||
"GETUSER <username> -- Get the user details.",
|
||||
"DELUSER <username> [...] -- Delete a list of users.",
|
||||
"CAT -- List available categories.",
|
||||
"CAT <category> -- List commands inside category.",
|
||||
"GENPASS [<bits>] -- Generate a secure user password.",
|
||||
"WHOAMI -- Return the current connection username.",
|
||||
"LOG [<count> | RESET] -- Show the ACL log entries.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c,help);
|
||||
|
20
src/adlist.c
20
src/adlist.c
@ -327,12 +327,11 @@ listNode *listIndex(list *list, long index) {
|
||||
}
|
||||
|
||||
/* Rotate the list removing the tail node and inserting it to the head. */
|
||||
void listRotate(list *list) {
|
||||
listNode *tail = list->tail;
|
||||
|
||||
void listRotateTailToHead(list *list) {
|
||||
if (listLength(list) <= 1) return;
|
||||
|
||||
/* Detach current tail */
|
||||
listNode *tail = list->tail;
|
||||
list->tail = tail->prev;
|
||||
list->tail->next = NULL;
|
||||
/* Move it as head */
|
||||
@ -342,6 +341,21 @@ void listRotate(list *list) {
|
||||
list->head = tail;
|
||||
}
|
||||
|
||||
/* Rotate the list removing the head node and inserting it to the tail. */
|
||||
void listRotateHeadToTail(list *list) {
|
||||
if (listLength(list) <= 1) return;
|
||||
|
||||
listNode *head = list->head;
|
||||
/* Detach current head */
|
||||
list->head = head->next;
|
||||
list->head->prev = NULL;
|
||||
/* Move it as tail */
|
||||
list->tail->next = head;
|
||||
head->next = NULL;
|
||||
head->prev = list->tail;
|
||||
list->tail = head;
|
||||
}
|
||||
|
||||
/* Add all the elements of the list 'o' at the end of the
|
||||
* list 'l'. The list 'other' remains empty but otherwise valid. */
|
||||
void listJoin(list *l, list *o) {
|
||||
|
@ -89,7 +89,8 @@ listNode *listSearchKey(list *list, void *key);
|
||||
listNode *listIndex(list *list, long index);
|
||||
void listRewind(list *list, listIter *li);
|
||||
void listRewindTail(list *list, listIter *li);
|
||||
void listRotate(list *list);
|
||||
void listRotateTailToHead(list *list);
|
||||
void listRotateHeadToTail(list *list);
|
||||
void listJoin(list *l, list *o);
|
||||
|
||||
/* Directions for iterators */
|
||||
|
18
src/ae.cpp
18
src/ae.cpp
@ -530,6 +530,7 @@ extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long millise
|
||||
te->clientData = clientData;
|
||||
te->prev = NULL;
|
||||
te->next = eventLoop->timeEventHead;
|
||||
te->refcount = 0;
|
||||
if (te->next)
|
||||
te->next->prev = te;
|
||||
eventLoop->timeEventHead = te;
|
||||
@ -611,6 +612,13 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
/* Remove events scheduled for deletion. */
|
||||
if (te->id == AE_DELETED_EVENT_ID) {
|
||||
aeTimeEvent *next = te->next;
|
||||
/* If a reference exists for this timer event,
|
||||
* don't free it. This is currently incremented
|
||||
* for recursive timerProc calls */
|
||||
if (te->refcount) {
|
||||
te = next;
|
||||
continue;
|
||||
}
|
||||
if (te->prev)
|
||||
te->prev->next = te->next;
|
||||
else
|
||||
@ -640,7 +648,9 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
int retval;
|
||||
|
||||
id = te->id;
|
||||
te->refcount++;
|
||||
retval = te->timeProc(eventLoop, id, te->clientData);
|
||||
te->refcount--;
|
||||
processed++;
|
||||
if (retval != AE_NOMORE) {
|
||||
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
|
||||
@ -722,6 +732,7 @@ extern "C" void ProcessEventCore(aeEventLoop *eventLoop, aeFileEvent *fe, int ma
|
||||
* if flags has AE_DONT_WAIT set the function returns ASAP until all
|
||||
* the events that's possible to process without to wait are processed.
|
||||
* if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
|
||||
* if flags has AE_CALL_BEFORE_SLEEP set, the beforesleep callback is called.
|
||||
*
|
||||
* The function returns the number of events processed. */
|
||||
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
@ -781,6 +792,13 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
tvp = &tv;
|
||||
}
|
||||
|
||||
if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP) {
|
||||
std::unique_lock<decltype(g_lock)> ulock(g_lock, std::defer_lock);
|
||||
if (!(eventLoop->beforesleepFlags & AE_SLEEP_THREADSAFE))
|
||||
ulock.lock();
|
||||
eventLoop->beforesleep(eventLoop);
|
||||
}
|
||||
|
||||
/* Call the multiplexing API, will return only on timeout or when
|
||||
* some event fires. */
|
||||
numevents = aeApiPoll(eventLoop, tvp);
|
||||
|
11
src/ae.h
11
src/ae.h
@ -58,11 +58,12 @@ extern "C" {
|
||||
#define AE_WRITE_THREADSAFE 16
|
||||
#define AE_SLEEP_THREADSAFE 32
|
||||
|
||||
#define AE_FILE_EVENTS 1
|
||||
#define AE_TIME_EVENTS 2
|
||||
#define AE_FILE_EVENTS (1<<0)
|
||||
#define AE_TIME_EVENTS (1<<1)
|
||||
#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
|
||||
#define AE_DONT_WAIT 4
|
||||
#define AE_CALL_AFTER_SLEEP 8
|
||||
#define AE_DONT_WAIT (1<<2)
|
||||
#define AE_CALL_BEFORE_SLEEP (1<<3)
|
||||
#define AE_CALL_AFTER_SLEEP (1<<4)
|
||||
|
||||
#define AE_NOMORE -1
|
||||
#define AE_DELETED_EVENT_ID -1
|
||||
@ -97,6 +98,8 @@ typedef struct aeTimeEvent {
|
||||
void *clientData;
|
||||
struct aeTimeEvent *prev;
|
||||
struct aeTimeEvent *next;
|
||||
int refcount; /* refcount to prevent timer events from being
|
||||
* freed in recursive time event calls. */
|
||||
} aeTimeEvent;
|
||||
|
||||
/* A fired event */
|
||||
|
@ -549,7 +549,10 @@ sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
|
||||
dst = sdscatlen(dst,buf,len);
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
o = getDecodedObject(argv[j]);
|
||||
if (sdsEncodedObject(argv[j]))
|
||||
o = argv[j];
|
||||
else
|
||||
o = getDecodedObject(argv[j]);
|
||||
buf[0] = '$';
|
||||
len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(szFromObj(o)));
|
||||
buf[len++] = '\r';
|
||||
@ -557,7 +560,8 @@ sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
|
||||
dst = sdscatlen(dst,buf,len);
|
||||
dst = sdscatlen(dst,ptrFromObj(o),sdslen(szFromObj(o)));
|
||||
dst = sdscatlen(dst,"\r\n",2);
|
||||
decrRefCount(o);
|
||||
if (o != argv[j])
|
||||
decrRefCount(o);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
@ -1716,6 +1720,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
|
||||
/* Child */
|
||||
redisSetProcTitle("keydb-aof-rewrite");
|
||||
redisSetCpuAffinity(g_pserver->aof_rewrite_cpulist);
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||
sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
|
||||
|
16
src/bio.cpp
16
src/bio.cpp
@ -154,6 +154,20 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case BIO_CLOSE_FILE:
|
||||
redis_set_thread_title("bio_close_file");
|
||||
break;
|
||||
case BIO_AOF_FSYNC:
|
||||
redis_set_thread_title("bio_aof_fsync");
|
||||
break;
|
||||
case BIO_LAZY_FREE:
|
||||
redis_set_thread_title("bio_lazy_free");
|
||||
break;
|
||||
}
|
||||
|
||||
redisSetCpuAffinity(g_pserver->bio_cpulist);
|
||||
|
||||
/* Make the thread killable at any time, so that bioKillThreads()
|
||||
* can work reliably. */
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
@ -254,7 +268,7 @@ void bioKillThreads(void) {
|
||||
int err, j;
|
||||
|
||||
for (j = 0; j < BIO_NUM_OPS; j++) {
|
||||
if (pthread_cancel(bio_threads[j]) == 0) {
|
||||
if (bio_threads[j] && pthread_cancel(bio_threads[j]) == 0) {
|
||||
if ((err = pthread_join(bio_threads[j],NULL)) != 0) {
|
||||
serverLog(LL_WARNING,
|
||||
"Bio thread for job type #%d can be joined: %s",
|
||||
|
@ -269,7 +269,7 @@ int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
|
||||
* then zero is returned, otherwise in case of overflow, 1 is returned,
|
||||
* otherwise in case of underflow, -1 is returned.
|
||||
*
|
||||
* When non-zero is returned (oferflow or underflow), if not NULL, *limit is
|
||||
* When non-zero is returned (overflow or underflow), if not NULL, *limit is
|
||||
* set to the value the operation should result when an overflow happens,
|
||||
* depending on the specified overflow semantics:
|
||||
*
|
||||
@ -556,7 +556,7 @@ void setbitCommand(client *c) {
|
||||
byteval &= ~(1 << bit);
|
||||
byteval |= ((on & 0x1) << bit);
|
||||
((uint8_t*)ptrFromObj(o))[byte] = byteval;
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
@ -828,11 +828,11 @@ void bitopCommand(client *c) {
|
||||
/* Store the computed value into the target key */
|
||||
if (maxlen) {
|
||||
robj *o = createObject(OBJ_STRING,res);
|
||||
setKey(c->db,targetkey,o);
|
||||
setKey(c,c->db,targetkey,o);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
|
||||
decrRefCount(o);
|
||||
} else if (dbDelete(c->db,targetkey)) {
|
||||
signalModifiedKey(c->db,targetkey);
|
||||
signalModifiedKey(c,c->db,targetkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
|
||||
}
|
||||
g_pserver->dirty++;
|
||||
@ -1209,7 +1209,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
}
|
||||
|
||||
if (changes) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
g_pserver->dirty += changes;
|
||||
}
|
||||
|
@ -65,6 +65,21 @@
|
||||
|
||||
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
|
||||
|
||||
/* This structure represents the blocked key information that we store
|
||||
* in the client structure. Each client blocked on keys, has a
|
||||
* client->bpop.keys hash table. The keys of the hash table are Redis
|
||||
* keys pointers to 'robj' structures. The value is this structure.
|
||||
* The structure has two goals: firstly we store the list node that this
|
||||
* client uses to be listed in the database "blocked clients for this key"
|
||||
* list, so we can later unblock in O(1) without a list scan.
|
||||
* Secondly for certain blocking types, we have additional info. Right now
|
||||
* the only use for additional info we have is when clients are blocked
|
||||
* on streams, as we have to remember the ID it blocked for. */
|
||||
typedef struct bkinfo {
|
||||
listNode *listnode; /* List node for db->blocking_keys[key] list. */
|
||||
streamID stream_id; /* Stream ID if we blocked in a stream. */
|
||||
} bkinfo;
|
||||
|
||||
/* Block a client for the specific operation type. Once the CLIENT_BLOCKED
|
||||
* flag is set client query buffer is not longer processed, but accumulated,
|
||||
* and will be processed when the client is unblocked. */
|
||||
@ -106,7 +121,7 @@ void processUnblockedClients(int iel) {
|
||||
* the code is conceptually more correct this way. */
|
||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
||||
processInputBufferAndReplicate(c);
|
||||
processInputBuffer(c, CMD_CALL_FULL);
|
||||
}
|
||||
}
|
||||
fastlock_unlock(&c->lock);
|
||||
@ -235,8 +250,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
if (receiver->btype != BLOCKED_LIST) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
listRotateHeadToTail(clients);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -298,8 +312,7 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
|
||||
if (receiver->btype != BLOCKED_ZSET) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
listRotateHeadToTail(clients);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -346,8 +359,8 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
client *receiver = (client*)listNodeValue(ln);
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
std::unique_lock<decltype(receiver->lock)> lock(receiver->lock);
|
||||
streamID *gt = (streamID*)dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
bkinfo *bki = (bkinfo*)dictFetchValue(receiver->bpop.keys,rl->key);
|
||||
streamID *gt = &bki->stream_id;
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
@ -384,9 +397,10 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
int noack = 0;
|
||||
|
||||
if (group) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
szFromObj(receiver->bpop.xread_consumer),
|
||||
1);
|
||||
consumer =
|
||||
streamLookupConsumer(group,
|
||||
szFromObj(receiver->bpop.xread_consumer),
|
||||
SLC_NONE);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
@ -445,8 +459,7 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||
* ready to be served, so they'll remain in the list
|
||||
* sometimes. We want also be able to skip clients that are
|
||||
* not blocked for the MODULE type safely. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
listRotateHeadToTail(clients);
|
||||
|
||||
if (receiver->btype != BLOCKED_MODULE) continue;
|
||||
|
||||
@ -578,17 +591,15 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
if (target != NULL) incrRefCount(target);
|
||||
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
/* The value associated with the key name in the bpop.keys dictionary
|
||||
* is NULL for lists and sorted sets, or the stream ID for streams. */
|
||||
void *key_data = NULL;
|
||||
if (btype == BLOCKED_STREAM) {
|
||||
key_data = zmalloc(sizeof(streamID), MALLOC_SHARED);
|
||||
memcpy(key_data,ids+j,sizeof(streamID));
|
||||
}
|
||||
/* Allocate our bkinfo structure, associated to each key the client
|
||||
* is blocked for. */
|
||||
bkinfo *bki = (bkinfo*)zmalloc(sizeof(*bki));
|
||||
if (btype == BLOCKED_STREAM)
|
||||
bki->stream_id = ids[j];
|
||||
|
||||
/* If the key already exists in the dictionary ignore it. */
|
||||
if (dictAdd(c->bpop.keys,keys[j],key_data) != DICT_OK) {
|
||||
zfree(key_data);
|
||||
if (dictAdd(c->bpop.keys,keys[j],bki) != DICT_OK) {
|
||||
zfree(bki);
|
||||
continue;
|
||||
}
|
||||
incrRefCount(keys[j]);
|
||||
@ -607,6 +618,7 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
l = (list*)dictGetVal(de);
|
||||
}
|
||||
listAddNodeTail(l,c);
|
||||
bki->listnode = listLast(l);
|
||||
}
|
||||
blockClient(c,btype);
|
||||
}
|
||||
@ -623,11 +635,12 @@ void unblockClientWaitingData(client *c) {
|
||||
/* The client may wait for multiple keys, so unblock it for every key. */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *key = (robj*)dictGetKey(de);
|
||||
bkinfo *bki = (bkinfo*)dictGetVal(de);
|
||||
|
||||
/* Remove this client from the list of clients waiting for this key. */
|
||||
l = (list*)dictFetchValue(c->db->blocking_keys,key);
|
||||
serverAssertWithInfo(c,key,l != NULL);
|
||||
listDelNode(l,listSearchKey(l,c));
|
||||
listDelNode(l,bki->listnode);
|
||||
/* If the list is empty we need to remove it to avoid wasting memory */
|
||||
if (listLength(l) == 0)
|
||||
dictDelete(c->db->blocking_keys,key);
|
||||
|
176
src/cluster.cpp
176
src/cluster.cpp
@ -787,6 +787,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
|
||||
node->slaves = NULL;
|
||||
node->slaveof = NULL;
|
||||
node->ping_sent = node->pong_received = 0;
|
||||
node->data_received = 0;
|
||||
node->fail_time = 0;
|
||||
node->link = NULL;
|
||||
memset(node->ip,0,sizeof(node->ip));
|
||||
@ -971,7 +972,7 @@ int clusterAddNode(clusterNode *node) {
|
||||
return (retval == DICT_OK) ? C_OK : C_ERR;
|
||||
}
|
||||
|
||||
/* Remove a node from the cluster. The functio performs the high level
|
||||
/* Remove a node from the cluster. The function performs the high level
|
||||
* cleanup, calling freeClusterNode() for the low level cleanup.
|
||||
* Here we do the following:
|
||||
*
|
||||
@ -1720,6 +1721,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
clusterMsg *hdr = (clusterMsg*) link->rcvbuf;
|
||||
uint32_t totlen = ntohl(hdr->totlen);
|
||||
uint16_t type = ntohs(hdr->type);
|
||||
mstime_t now = mstime();
|
||||
|
||||
if (type < CLUSTERMSG_TYPE_COUNT)
|
||||
g_pserver->cluster->stats_bus_messages_received[type]++;
|
||||
@ -1781,8 +1783,17 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
if (totlen != explen) return 1;
|
||||
}
|
||||
|
||||
/* Check if the sender is a known node. */
|
||||
/* Check if the sender is a known node. Note that for incoming connections
|
||||
* we don't store link->node information, but resolve the node by the
|
||||
* ID in the header each time in the current implementation. */
|
||||
sender = clusterLookupNode(hdr->sender);
|
||||
|
||||
/* Update the last time we saw any data from this node. We
|
||||
* use this in order to avoid detecting a timeout from a node that
|
||||
* is just sending a lot of data in the cluster bus, for instance
|
||||
* because of Pub/Sub. */
|
||||
if (sender) sender->data_received = now;
|
||||
|
||||
if (sender && !nodeInHandshake(sender)) {
|
||||
/* Update our curretEpoch if we see a newer epoch in the cluster. */
|
||||
senderCurrentEpoch = ntohu64(hdr->currentEpoch);
|
||||
@ -1797,7 +1808,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
}
|
||||
/* Update the replication offset info for this node. */
|
||||
sender->repl_offset = ntohu64(hdr->offset);
|
||||
sender->repl_offset_time = mstime();
|
||||
sender->repl_offset_time = now;
|
||||
/* If we are a slave performing a manual failover and our master
|
||||
* sent its offset while already paused, populate the MF state. */
|
||||
if (g_pserver->cluster->mf_end &&
|
||||
@ -1911,7 +1922,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
* address. */
|
||||
serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d",
|
||||
link->node->name,
|
||||
(int)(mstime()-(link->node->ctime)),
|
||||
(int)(now-(link->node->ctime)),
|
||||
link->node->flags);
|
||||
link->node->flags |= CLUSTER_NODE_NOADDR;
|
||||
link->node->ip[0] = '\0';
|
||||
@ -1946,7 +1957,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
|
||||
/* Update our info about the node */
|
||||
if (link->node && type == CLUSTERMSG_TYPE_PONG) {
|
||||
link->node->pong_received = mstime();
|
||||
link->node->pong_received = now;
|
||||
link->node->ping_sent = 0;
|
||||
|
||||
/* The PFAIL condition can be reversed without external
|
||||
@ -2093,7 +2104,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
"FAIL message received from %.40s about %.40s",
|
||||
hdr->sender, hdr->data.fail.about.nodename);
|
||||
failing->flags |= CLUSTER_NODE_FAIL;
|
||||
failing->fail_time = mstime();
|
||||
failing->fail_time = now;
|
||||
failing->flags &= ~CLUSTER_NODE_PFAIL;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
|
||||
CLUSTER_TODO_UPDATE_STATE);
|
||||
@ -2146,9 +2157,9 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
/* Manual failover requested from slaves. Initialize the state
|
||||
* accordingly. */
|
||||
resetManualFailover();
|
||||
g_pserver->cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
|
||||
g_pserver->cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
|
||||
g_pserver->cluster->mf_slave = sender;
|
||||
pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2));
|
||||
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT));
|
||||
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
|
||||
sender->name);
|
||||
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
|
||||
@ -3577,7 +3588,6 @@ void clusterCron(void) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = (clusterNode*)dictGetVal(de);
|
||||
now = mstime(); /* Use an updated time at every iteration. */
|
||||
mstime_t delay;
|
||||
|
||||
if (node->flags &
|
||||
(CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
|
||||
@ -3601,16 +3611,20 @@ void clusterCron(void) {
|
||||
this_slaves = okslaves;
|
||||
}
|
||||
|
||||
/* If we are waiting for the PONG more than half the cluster
|
||||
/* If we are not receiving any data for more than half the cluster
|
||||
* timeout, reconnect the link: maybe there is a connection
|
||||
* issue even if the node is alive. */
|
||||
mstime_t ping_delay = now - node->ping_sent;
|
||||
mstime_t data_delay = now - node->data_received;
|
||||
if (node->link && /* is connected */
|
||||
now - node->link->ctime >
|
||||
g_pserver->cluster_node_timeout && /* was not already reconnected */
|
||||
node->ping_sent && /* we already sent a ping */
|
||||
node->pong_received < node->ping_sent && /* still waiting pong */
|
||||
/* and we are waiting for the pong more than timeout/2 */
|
||||
now - node->ping_sent > g_pserver->cluster_node_timeout/2)
|
||||
ping_delay > g_pserver->cluster_node_timeout/2 &&
|
||||
/* and in such interval we are not seeing any traffic at all. */
|
||||
data_delay > g_pserver->cluster_node_timeout/2)
|
||||
{
|
||||
/* Disconnect the link, it will be reconnected automatically. */
|
||||
freeClusterLink(node->link);
|
||||
@ -3642,12 +3656,18 @@ void clusterCron(void) {
|
||||
/* Check only if we have an active ping for this instance. */
|
||||
if (node->ping_sent == 0) continue;
|
||||
|
||||
/* Compute the delay of the PONG. Note that if we already received
|
||||
* the PONG, then node->ping_sent is zero, so can't reach this
|
||||
* code at all. */
|
||||
delay = now - node->ping_sent;
|
||||
/* Check if this node looks unreachable.
|
||||
* Note that if we already received the PONG, then node->ping_sent
|
||||
* is zero, so can't reach this code at all, so we don't risk of
|
||||
* checking for a PONG delay if we didn't sent the PING.
|
||||
*
|
||||
* We also consider every incoming data as proof of liveness, since
|
||||
* our cluster bus link is also used for data: under heavy data
|
||||
* load pong delays are possible. */
|
||||
mstime_t node_delay = (ping_delay < data_delay) ? ping_delay :
|
||||
data_delay;
|
||||
|
||||
if (delay > g_pserver->cluster_node_timeout) {
|
||||
if (node_delay > g_pserver->cluster_node_timeout) {
|
||||
/* Timeout reached. Set the node as possibly failing if it is
|
||||
* not already in this state. */
|
||||
if (!(node->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL))) {
|
||||
@ -4238,76 +4258,60 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
dictIterator *di = dictGetSafeIterator(g_pserver->cluster->nodes);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = (clusterNode*)dictGetVal(de);
|
||||
int start = -1;
|
||||
int j = 0, start = -1;
|
||||
int i, nested_elements = 0;
|
||||
|
||||
/* Skip slaves (that are iterated when producing the output of their
|
||||
* master) and masters not serving any slot. */
|
||||
if (!nodeIsMaster(node) || node->numslots == 0) continue;
|
||||
|
||||
static_assert((CLUSTER_SLOTS % (sizeof(uint32_t)*8)) == 0, "code below assumes the bitfield is a multiple of sizeof(unsinged)");
|
||||
|
||||
for (int iw = 0; iw < (CLUSTER_SLOTS/(int)sizeof(uint32_t)/8); ++iw)
|
||||
{
|
||||
uint32_t wordCur = reinterpret_cast<uint32_t*>(node->slots)[iw];
|
||||
if (iw != ((CLUSTER_SLOTS/sizeof(uint32_t)/8)-1))
|
||||
{
|
||||
if (start == -1 && wordCur == 0)
|
||||
continue;
|
||||
if (start != -1 && (wordCur+1)==0)
|
||||
continue;
|
||||
for(i = 0; i < node->numslaves; i++) {
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
nested_elements++;
|
||||
}
|
||||
|
||||
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
||||
int bit, i;
|
||||
|
||||
if ((bit = clusterNodeGetSlotBit(node,j)) != 0) {
|
||||
if (start == -1) start = j;
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
addReplyArrayLen(c, nested_elements + 3); /* slots (2) + master addr (1). */
|
||||
|
||||
unsigned ibitStartLoop = iw*sizeof(uint32_t)*8;
|
||||
|
||||
for (int j = ibitStartLoop; j < (iw+1)*(int)sizeof(uint32_t)*8; j++) {
|
||||
int i;
|
||||
int bit = (int)(wordCur & 1);
|
||||
wordCur >>= 1;
|
||||
if (bit != 0) {
|
||||
if (start == -1) start = j;
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
/* If slot exists in output map, add to it's list.
|
||||
* else, create a new output map for this slot */
|
||||
if (start == j-1) {
|
||||
addReplyLongLong(c, start); /* only one slot; low==high */
|
||||
addReplyLongLong(c, start);
|
||||
} else {
|
||||
addReplyLongLong(c, start); /* low */
|
||||
addReplyLongLong(c, j-1); /* high */
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
int nested_elements = 3; /* slots (2) + master addr (1). */
|
||||
void *nested_replylen = addReplyDeferredLen(c);
|
||||
start = -1;
|
||||
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
/* First node reply position is always the master */
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
addReplyLongLong(c, node->port);
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
|
||||
/* If slot exists in output map, add to it's list.
|
||||
* else, create a new output map for this slot */
|
||||
if (start == (int)j-1) {
|
||||
addReplyLongLong(c, start); /* only one slot; low==high */
|
||||
addReplyLongLong(c, start);
|
||||
} else {
|
||||
addReplyLongLong(c, start); /* low */
|
||||
addReplyLongLong(c, j-1); /* high */
|
||||
}
|
||||
start = -1;
|
||||
|
||||
/* First node reply position is always the master */
|
||||
/* Remaining nodes in reply are replicas for slot range */
|
||||
for (i = 0; i < node->numslaves; i++) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
addReplyLongLong(c, node->port);
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
|
||||
/* Remaining nodes in reply are replicas for slot range */
|
||||
for (i = 0; i < node->numslaves; i++) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
nested_elements++;
|
||||
}
|
||||
setDeferredArrayLen(c, nested_replylen, nested_elements);
|
||||
num_masters++;
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
}
|
||||
num_masters++;
|
||||
}
|
||||
}
|
||||
serverAssert(start == -1);
|
||||
}
|
||||
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c, slot_replylen, num_masters);
|
||||
}
|
||||
@ -5037,7 +5041,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,szFromObj(c->argv[3]));
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1], OBJ_MVCC_INVALID)) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,szFromObj(c->argv[1]), OBJ_MVCC_INVALID)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
@ -5068,7 +5072,7 @@ eoferr:
|
||||
setExpire(c,c->db,c->argv[1],nullptr,ttl);
|
||||
}
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",c->argv[1],c->db->id);
|
||||
addReply(c,shared.ok);
|
||||
g_pserver->dirty++;
|
||||
@ -5183,15 +5187,17 @@ void migrateCloseTimedoutSockets(void) {
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password]
|
||||
/* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password |
|
||||
* AUTH2 username password]
|
||||
*
|
||||
* On in the multiple keys form:
|
||||
*
|
||||
* MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password] KEYS key1
|
||||
* key2 ... keyN */
|
||||
* MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password |
|
||||
* AUTH2 username password] KEYS key1 key2 ... keyN */
|
||||
void migrateCommand(client *c) {
|
||||
migrateCachedSocket *cs;
|
||||
int copy = 0, replace = 0, j;
|
||||
char *username = NULL;
|
||||
char *password = NULL;
|
||||
long timeout;
|
||||
long dbid;
|
||||
@ -5209,7 +5215,7 @@ void migrateCommand(client *c) {
|
||||
|
||||
/* Parse additional options */
|
||||
for (j = 6; j < c->argc; j++) {
|
||||
int moreargs = j < c->argc-1;
|
||||
int moreargs = (c->argc-1) - j;
|
||||
if (!strcasecmp(szFromObj(c->argv[j]),"copy")) {
|
||||
copy = 1;
|
||||
} else if (!strcasecmp(szFromObj(c->argv[j]),"replace")) {
|
||||
@ -5221,6 +5227,13 @@ void migrateCommand(client *c) {
|
||||
}
|
||||
j++;
|
||||
password = szFromObj(c->argv[j]);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[j]),"auth2")) {
|
||||
if (moreargs < 2) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
username = szFromObj(c->argv[++j]);
|
||||
password = szFromObj(c->argv[++j]);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[j]),"keys")) {
|
||||
if (sdslen(szFromObj(c->argv[3])) != 0) {
|
||||
addReplyError(c,
|
||||
@ -5281,8 +5294,13 @@ try_again:
|
||||
|
||||
/* Authentication */
|
||||
if (password) {
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2));
|
||||
int arity = username ? 3 : 2;
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',arity));
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"AUTH",4));
|
||||
if (username) {
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,username,
|
||||
sdslen(username)));
|
||||
}
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,password,
|
||||
sdslen(password)));
|
||||
}
|
||||
@ -5417,7 +5435,7 @@ try_again:
|
||||
if (!copy) {
|
||||
/* No COPY option: remove the local key, signal the change. */
|
||||
dbDelete(c->db,kv[j]);
|
||||
signalModifiedKey(c->db,kv[j]);
|
||||
signalModifiedKey(c,c->db,kv[j]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",kv[j],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
|
||||
|
@ -128,6 +128,7 @@ typedef struct clusterNode {
|
||||
tables. */
|
||||
mstime_t ping_sent; /* Unix time we sent latest ping */
|
||||
mstime_t pong_received; /* Unix time we received the pong */
|
||||
mstime_t data_received; /* Unix time we received any data */
|
||||
mstime_t fail_time; /* Unix time when FAIL flag was set */
|
||||
mstime_t voted_time; /* Last time we voted for a slave of this master */
|
||||
mstime_t repl_offset_time; /* Unix time we received offset for this node */
|
||||
|
@ -2304,6 +2304,10 @@ standardConfig configs[] = {
|
||||
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->syslog_ident, "redis", NULL, NULL),
|
||||
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->rdb_filename, CONFIG_DEFAULT_RDB_FILENAME, isValidDBfilename, NULL),
|
||||
createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->aof_filename, "appendonly.aof", isValidAOFfilename, NULL),
|
||||
createStringConfig("server_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->server_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bio_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->aof_rewrite_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bgsave_cpulist, NULL, NULL, NULL),
|
||||
|
||||
/* Enum Configs */
|
||||
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, cserver.supervised_mode, SUPERVISED_NONE, NULL, NULL),
|
||||
|
33
src/config.h
33
src/config.h
@ -232,4 +232,37 @@ void setproctitle(const char *fmt, ...);
|
||||
#define USE_ALIGNED_ACCESS
|
||||
#endif
|
||||
|
||||
/* Define for redis_set_thread_title */
|
||||
#ifdef __linux__
|
||||
#define redis_set_thread_title(name) pthread_setname_np(pthread_self(), name)
|
||||
#else
|
||||
#if (defined __FreeBSD__ || defined __OpenBSD__)
|
||||
#include <pthread_np.h>
|
||||
#define redis_set_thread_title(name) pthread_set_name_np(pthread_self(), name)
|
||||
#elif defined __NetBSD__
|
||||
#include <pthread.h>
|
||||
#define redis_set_thread_title(name) pthread_setname_np(pthread_self(), name, NULL)
|
||||
#else
|
||||
#if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7))
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
int pthread_setname_np(const char *name);
|
||||
#include <pthread.h>
|
||||
#define redis_set_thread_title(name) pthread_setname_np(name)
|
||||
#else
|
||||
#define redis_set_thread_title(name)
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Check if we can use setcpuaffinity(). */
|
||||
#if (defined __linux || defined __NetBSD__ || defined __FreeBSD__)
|
||||
#define USE_SETCPUAFFINITY
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
void setcpuaffinity(const char *cpulist);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -31,6 +31,8 @@
|
||||
#ifndef __REDIS_CONNECTION_H
|
||||
#define __REDIS_CONNECTION_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#define CONN_INFO_LEN 32
|
||||
|
||||
struct aeEventLoop;
|
||||
@ -70,7 +72,7 @@ typedef struct ConnectionType {
|
||||
|
||||
struct connection {
|
||||
ConnectionType *type;
|
||||
ConnectionState state;
|
||||
std::atomic<ConnectionState> state;
|
||||
short int flags;
|
||||
short int refs;
|
||||
int last_errno;
|
||||
@ -225,6 +227,6 @@ const char *connGetInfo(connection *conn, char *buf, size_t buf_len);
|
||||
|
||||
/* Helpers for tls special considerations */
|
||||
int tlsHasPendingData();
|
||||
void tlsProcessPendingData();
|
||||
int tlsProcessPendingData();
|
||||
|
||||
#endif /* __REDIS_CONNECTION_H */
|
||||
|
269
src/crc64.c
269
src/crc64.c
@ -1,16 +1,5 @@
|
||||
/* Redis uses the CRC64 variant with "Jones" coefficients and init value of 0.
|
||||
*
|
||||
* Specification of this CRC64 variant follows:
|
||||
* Name: crc-64-jones
|
||||
* Width: 64 bites
|
||||
* Poly: 0xad93d23594c935a9
|
||||
* Reflected In: True
|
||||
* Xor_In: 0xffffffffffffffff
|
||||
* Reflected_Out: True
|
||||
* Xor_Out: 0x0
|
||||
* Check("123456789"): 0xe9c6d914c4b8d9ca
|
||||
*
|
||||
* Copyright (c) 2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
/* Copyright (c) 2014, Matt Stancliff <matt@genges.com>
|
||||
* Copyright (c) 2020, Amazon Web Services
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -37,147 +26,100 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE. */
|
||||
|
||||
#include <stdint.h>
|
||||
#include "crc64.h"
|
||||
#include "crcspeed.h"
|
||||
static uint64_t crc64_table[8][256] = {{0}};
|
||||
|
||||
static const uint64_t crc64_tab[256] = {
|
||||
UINT64_C(0x0000000000000000), UINT64_C(0x7ad870c830358979),
|
||||
UINT64_C(0xf5b0e190606b12f2), UINT64_C(0x8f689158505e9b8b),
|
||||
UINT64_C(0xc038e5739841b68f), UINT64_C(0xbae095bba8743ff6),
|
||||
UINT64_C(0x358804e3f82aa47d), UINT64_C(0x4f50742bc81f2d04),
|
||||
UINT64_C(0xab28ecb46814fe75), UINT64_C(0xd1f09c7c5821770c),
|
||||
UINT64_C(0x5e980d24087fec87), UINT64_C(0x24407dec384a65fe),
|
||||
UINT64_C(0x6b1009c7f05548fa), UINT64_C(0x11c8790fc060c183),
|
||||
UINT64_C(0x9ea0e857903e5a08), UINT64_C(0xe478989fa00bd371),
|
||||
UINT64_C(0x7d08ff3b88be6f81), UINT64_C(0x07d08ff3b88be6f8),
|
||||
UINT64_C(0x88b81eabe8d57d73), UINT64_C(0xf2606e63d8e0f40a),
|
||||
UINT64_C(0xbd301a4810ffd90e), UINT64_C(0xc7e86a8020ca5077),
|
||||
UINT64_C(0x4880fbd87094cbfc), UINT64_C(0x32588b1040a14285),
|
||||
UINT64_C(0xd620138fe0aa91f4), UINT64_C(0xacf86347d09f188d),
|
||||
UINT64_C(0x2390f21f80c18306), UINT64_C(0x594882d7b0f40a7f),
|
||||
UINT64_C(0x1618f6fc78eb277b), UINT64_C(0x6cc0863448deae02),
|
||||
UINT64_C(0xe3a8176c18803589), UINT64_C(0x997067a428b5bcf0),
|
||||
UINT64_C(0xfa11fe77117cdf02), UINT64_C(0x80c98ebf2149567b),
|
||||
UINT64_C(0x0fa11fe77117cdf0), UINT64_C(0x75796f2f41224489),
|
||||
UINT64_C(0x3a291b04893d698d), UINT64_C(0x40f16bccb908e0f4),
|
||||
UINT64_C(0xcf99fa94e9567b7f), UINT64_C(0xb5418a5cd963f206),
|
||||
UINT64_C(0x513912c379682177), UINT64_C(0x2be1620b495da80e),
|
||||
UINT64_C(0xa489f35319033385), UINT64_C(0xde51839b2936bafc),
|
||||
UINT64_C(0x9101f7b0e12997f8), UINT64_C(0xebd98778d11c1e81),
|
||||
UINT64_C(0x64b116208142850a), UINT64_C(0x1e6966e8b1770c73),
|
||||
UINT64_C(0x8719014c99c2b083), UINT64_C(0xfdc17184a9f739fa),
|
||||
UINT64_C(0x72a9e0dcf9a9a271), UINT64_C(0x08719014c99c2b08),
|
||||
UINT64_C(0x4721e43f0183060c), UINT64_C(0x3df994f731b68f75),
|
||||
UINT64_C(0xb29105af61e814fe), UINT64_C(0xc849756751dd9d87),
|
||||
UINT64_C(0x2c31edf8f1d64ef6), UINT64_C(0x56e99d30c1e3c78f),
|
||||
UINT64_C(0xd9810c6891bd5c04), UINT64_C(0xa3597ca0a188d57d),
|
||||
UINT64_C(0xec09088b6997f879), UINT64_C(0x96d1784359a27100),
|
||||
UINT64_C(0x19b9e91b09fcea8b), UINT64_C(0x636199d339c963f2),
|
||||
UINT64_C(0xdf7adabd7a6e2d6f), UINT64_C(0xa5a2aa754a5ba416),
|
||||
UINT64_C(0x2aca3b2d1a053f9d), UINT64_C(0x50124be52a30b6e4),
|
||||
UINT64_C(0x1f423fcee22f9be0), UINT64_C(0x659a4f06d21a1299),
|
||||
UINT64_C(0xeaf2de5e82448912), UINT64_C(0x902aae96b271006b),
|
||||
UINT64_C(0x74523609127ad31a), UINT64_C(0x0e8a46c1224f5a63),
|
||||
UINT64_C(0x81e2d7997211c1e8), UINT64_C(0xfb3aa75142244891),
|
||||
UINT64_C(0xb46ad37a8a3b6595), UINT64_C(0xceb2a3b2ba0eecec),
|
||||
UINT64_C(0x41da32eaea507767), UINT64_C(0x3b024222da65fe1e),
|
||||
UINT64_C(0xa2722586f2d042ee), UINT64_C(0xd8aa554ec2e5cb97),
|
||||
UINT64_C(0x57c2c41692bb501c), UINT64_C(0x2d1ab4dea28ed965),
|
||||
UINT64_C(0x624ac0f56a91f461), UINT64_C(0x1892b03d5aa47d18),
|
||||
UINT64_C(0x97fa21650afae693), UINT64_C(0xed2251ad3acf6fea),
|
||||
UINT64_C(0x095ac9329ac4bc9b), UINT64_C(0x7382b9faaaf135e2),
|
||||
UINT64_C(0xfcea28a2faafae69), UINT64_C(0x8632586aca9a2710),
|
||||
UINT64_C(0xc9622c4102850a14), UINT64_C(0xb3ba5c8932b0836d),
|
||||
UINT64_C(0x3cd2cdd162ee18e6), UINT64_C(0x460abd1952db919f),
|
||||
UINT64_C(0x256b24ca6b12f26d), UINT64_C(0x5fb354025b277b14),
|
||||
UINT64_C(0xd0dbc55a0b79e09f), UINT64_C(0xaa03b5923b4c69e6),
|
||||
UINT64_C(0xe553c1b9f35344e2), UINT64_C(0x9f8bb171c366cd9b),
|
||||
UINT64_C(0x10e3202993385610), UINT64_C(0x6a3b50e1a30ddf69),
|
||||
UINT64_C(0x8e43c87e03060c18), UINT64_C(0xf49bb8b633338561),
|
||||
UINT64_C(0x7bf329ee636d1eea), UINT64_C(0x012b592653589793),
|
||||
UINT64_C(0x4e7b2d0d9b47ba97), UINT64_C(0x34a35dc5ab7233ee),
|
||||
UINT64_C(0xbbcbcc9dfb2ca865), UINT64_C(0xc113bc55cb19211c),
|
||||
UINT64_C(0x5863dbf1e3ac9dec), UINT64_C(0x22bbab39d3991495),
|
||||
UINT64_C(0xadd33a6183c78f1e), UINT64_C(0xd70b4aa9b3f20667),
|
||||
UINT64_C(0x985b3e827bed2b63), UINT64_C(0xe2834e4a4bd8a21a),
|
||||
UINT64_C(0x6debdf121b863991), UINT64_C(0x1733afda2bb3b0e8),
|
||||
UINT64_C(0xf34b37458bb86399), UINT64_C(0x8993478dbb8deae0),
|
||||
UINT64_C(0x06fbd6d5ebd3716b), UINT64_C(0x7c23a61ddbe6f812),
|
||||
UINT64_C(0x3373d23613f9d516), UINT64_C(0x49aba2fe23cc5c6f),
|
||||
UINT64_C(0xc6c333a67392c7e4), UINT64_C(0xbc1b436e43a74e9d),
|
||||
UINT64_C(0x95ac9329ac4bc9b5), UINT64_C(0xef74e3e19c7e40cc),
|
||||
UINT64_C(0x601c72b9cc20db47), UINT64_C(0x1ac40271fc15523e),
|
||||
UINT64_C(0x5594765a340a7f3a), UINT64_C(0x2f4c0692043ff643),
|
||||
UINT64_C(0xa02497ca54616dc8), UINT64_C(0xdafce7026454e4b1),
|
||||
UINT64_C(0x3e847f9dc45f37c0), UINT64_C(0x445c0f55f46abeb9),
|
||||
UINT64_C(0xcb349e0da4342532), UINT64_C(0xb1eceec59401ac4b),
|
||||
UINT64_C(0xfebc9aee5c1e814f), UINT64_C(0x8464ea266c2b0836),
|
||||
UINT64_C(0x0b0c7b7e3c7593bd), UINT64_C(0x71d40bb60c401ac4),
|
||||
UINT64_C(0xe8a46c1224f5a634), UINT64_C(0x927c1cda14c02f4d),
|
||||
UINT64_C(0x1d148d82449eb4c6), UINT64_C(0x67ccfd4a74ab3dbf),
|
||||
UINT64_C(0x289c8961bcb410bb), UINT64_C(0x5244f9a98c8199c2),
|
||||
UINT64_C(0xdd2c68f1dcdf0249), UINT64_C(0xa7f41839ecea8b30),
|
||||
UINT64_C(0x438c80a64ce15841), UINT64_C(0x3954f06e7cd4d138),
|
||||
UINT64_C(0xb63c61362c8a4ab3), UINT64_C(0xcce411fe1cbfc3ca),
|
||||
UINT64_C(0x83b465d5d4a0eece), UINT64_C(0xf96c151de49567b7),
|
||||
UINT64_C(0x76048445b4cbfc3c), UINT64_C(0x0cdcf48d84fe7545),
|
||||
UINT64_C(0x6fbd6d5ebd3716b7), UINT64_C(0x15651d968d029fce),
|
||||
UINT64_C(0x9a0d8ccedd5c0445), UINT64_C(0xe0d5fc06ed698d3c),
|
||||
UINT64_C(0xaf85882d2576a038), UINT64_C(0xd55df8e515432941),
|
||||
UINT64_C(0x5a3569bd451db2ca), UINT64_C(0x20ed197575283bb3),
|
||||
UINT64_C(0xc49581ead523e8c2), UINT64_C(0xbe4df122e51661bb),
|
||||
UINT64_C(0x3125607ab548fa30), UINT64_C(0x4bfd10b2857d7349),
|
||||
UINT64_C(0x04ad64994d625e4d), UINT64_C(0x7e7514517d57d734),
|
||||
UINT64_C(0xf11d85092d094cbf), UINT64_C(0x8bc5f5c11d3cc5c6),
|
||||
UINT64_C(0x12b5926535897936), UINT64_C(0x686de2ad05bcf04f),
|
||||
UINT64_C(0xe70573f555e26bc4), UINT64_C(0x9ddd033d65d7e2bd),
|
||||
UINT64_C(0xd28d7716adc8cfb9), UINT64_C(0xa85507de9dfd46c0),
|
||||
UINT64_C(0x273d9686cda3dd4b), UINT64_C(0x5de5e64efd965432),
|
||||
UINT64_C(0xb99d7ed15d9d8743), UINT64_C(0xc3450e196da80e3a),
|
||||
UINT64_C(0x4c2d9f413df695b1), UINT64_C(0x36f5ef890dc31cc8),
|
||||
UINT64_C(0x79a59ba2c5dc31cc), UINT64_C(0x037deb6af5e9b8b5),
|
||||
UINT64_C(0x8c157a32a5b7233e), UINT64_C(0xf6cd0afa9582aa47),
|
||||
UINT64_C(0x4ad64994d625e4da), UINT64_C(0x300e395ce6106da3),
|
||||
UINT64_C(0xbf66a804b64ef628), UINT64_C(0xc5bed8cc867b7f51),
|
||||
UINT64_C(0x8aeeace74e645255), UINT64_C(0xf036dc2f7e51db2c),
|
||||
UINT64_C(0x7f5e4d772e0f40a7), UINT64_C(0x05863dbf1e3ac9de),
|
||||
UINT64_C(0xe1fea520be311aaf), UINT64_C(0x9b26d5e88e0493d6),
|
||||
UINT64_C(0x144e44b0de5a085d), UINT64_C(0x6e963478ee6f8124),
|
||||
UINT64_C(0x21c640532670ac20), UINT64_C(0x5b1e309b16452559),
|
||||
UINT64_C(0xd476a1c3461bbed2), UINT64_C(0xaeaed10b762e37ab),
|
||||
UINT64_C(0x37deb6af5e9b8b5b), UINT64_C(0x4d06c6676eae0222),
|
||||
UINT64_C(0xc26e573f3ef099a9), UINT64_C(0xb8b627f70ec510d0),
|
||||
UINT64_C(0xf7e653dcc6da3dd4), UINT64_C(0x8d3e2314f6efb4ad),
|
||||
UINT64_C(0x0256b24ca6b12f26), UINT64_C(0x788ec2849684a65f),
|
||||
UINT64_C(0x9cf65a1b368f752e), UINT64_C(0xe62e2ad306bafc57),
|
||||
UINT64_C(0x6946bb8b56e467dc), UINT64_C(0x139ecb4366d1eea5),
|
||||
UINT64_C(0x5ccebf68aecec3a1), UINT64_C(0x2616cfa09efb4ad8),
|
||||
UINT64_C(0xa97e5ef8cea5d153), UINT64_C(0xd3a62e30fe90582a),
|
||||
UINT64_C(0xb0c7b7e3c7593bd8), UINT64_C(0xca1fc72bf76cb2a1),
|
||||
UINT64_C(0x45775673a732292a), UINT64_C(0x3faf26bb9707a053),
|
||||
UINT64_C(0x70ff52905f188d57), UINT64_C(0x0a2722586f2d042e),
|
||||
UINT64_C(0x854fb3003f739fa5), UINT64_C(0xff97c3c80f4616dc),
|
||||
UINT64_C(0x1bef5b57af4dc5ad), UINT64_C(0x61372b9f9f784cd4),
|
||||
UINT64_C(0xee5fbac7cf26d75f), UINT64_C(0x9487ca0fff135e26),
|
||||
UINT64_C(0xdbd7be24370c7322), UINT64_C(0xa10fceec0739fa5b),
|
||||
UINT64_C(0x2e675fb4576761d0), UINT64_C(0x54bf2f7c6752e8a9),
|
||||
UINT64_C(0xcdcf48d84fe75459), UINT64_C(0xb71738107fd2dd20),
|
||||
UINT64_C(0x387fa9482f8c46ab), UINT64_C(0x42a7d9801fb9cfd2),
|
||||
UINT64_C(0x0df7adabd7a6e2d6), UINT64_C(0x772fdd63e7936baf),
|
||||
UINT64_C(0xf8474c3bb7cdf024), UINT64_C(0x829f3cf387f8795d),
|
||||
UINT64_C(0x66e7a46c27f3aa2c), UINT64_C(0x1c3fd4a417c62355),
|
||||
UINT64_C(0x935745fc4798b8de), UINT64_C(0xe98f353477ad31a7),
|
||||
UINT64_C(0xa6df411fbfb21ca3), UINT64_C(0xdc0731d78f8795da),
|
||||
UINT64_C(0x536fa08fdfd90e51), UINT64_C(0x29b7d047efec8728),
|
||||
};
|
||||
#define POLY UINT64_C(0xad93d23594c935a9)
|
||||
/******************** BEGIN GENERATED PYCRC FUNCTIONS ********************/
|
||||
/**
|
||||
* Generated on Sun Dec 21 14:14:07 2014,
|
||||
* by pycrc v0.8.2, https://www.tty1.net/pycrc/
|
||||
*
|
||||
* LICENSE ON GENERATED CODE:
|
||||
* ==========================
|
||||
* As of version 0.6, pycrc is released under the terms of the MIT licence.
|
||||
* The code generated by pycrc is not considered a substantial portion of the
|
||||
* software, therefore the author of pycrc will not claim any copyright on
|
||||
* the generated code.
|
||||
* ==========================
|
||||
*
|
||||
* CRC configuration:
|
||||
* Width = 64
|
||||
* Poly = 0xad93d23594c935a9
|
||||
* XorIn = 0xffffffffffffffff
|
||||
* ReflectIn = True
|
||||
* XorOut = 0x0000000000000000
|
||||
* ReflectOut = True
|
||||
* Algorithm = bit-by-bit-fast
|
||||
*
|
||||
* Modifications after generation (by matt):
|
||||
* - included finalize step in-line with update for single-call generation
|
||||
* - re-worked some inner variable architectures
|
||||
* - adjusted function parameters to match expected prototypes.
|
||||
*****************************************************************************/
|
||||
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
|
||||
uint64_t j;
|
||||
/**
|
||||
* Reflect all bits of a \a data word of \a data_len bytes.
|
||||
*
|
||||
* \param data The data word to be reflected.
|
||||
* \param data_len The width of \a data expressed in number of bits.
|
||||
* \return The reflected data.
|
||||
*****************************************************************************/
|
||||
static inline uint_fast64_t crc_reflect(uint_fast64_t data, size_t data_len) {
|
||||
uint_fast64_t ret = data & 0x01;
|
||||
|
||||
for (j = 0; j < l; j++) {
|
||||
uint8_t byte = s[j];
|
||||
crc = crc64_tab[(uint8_t)crc ^ byte] ^ (crc >> 8);
|
||||
for (size_t i = 1; i < data_len; i++) {
|
||||
data >>= 1;
|
||||
ret = (ret << 1) | (data & 0x01);
|
||||
}
|
||||
return crc;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Pointer to a buffer of \a data_len bytes.
|
||||
* \param data_len Number of bytes in the \a data buffer.
|
||||
* \return The updated crc value.
|
||||
******************************************************************************/
|
||||
uint64_t _crc64(uint_fast64_t crc, const void *in_data, const uint64_t len) {
|
||||
const uint8_t *data = in_data;
|
||||
unsigned long long bit;
|
||||
|
||||
for (uint64_t offset = 0; offset < len; offset++) {
|
||||
uint8_t c = data[offset];
|
||||
for (uint_fast8_t i = 0x01; i & 0xff; i <<= 1) {
|
||||
bit = crc & 0x8000000000000000;
|
||||
if (c & i) {
|
||||
bit = !bit;
|
||||
}
|
||||
|
||||
crc <<= 1;
|
||||
if (bit) {
|
||||
crc ^= POLY;
|
||||
}
|
||||
}
|
||||
|
||||
crc &= 0xffffffffffffffff;
|
||||
}
|
||||
|
||||
crc = crc & 0xffffffffffffffff;
|
||||
return crc_reflect(crc, 64) ^ 0x0000000000000000;
|
||||
}
|
||||
|
||||
/******************** END GENERATED PYCRC FUNCTIONS ********************/
|
||||
|
||||
/* Initializes the 16KB lookup tables. */
|
||||
void crc64_init(void) {
|
||||
crcspeed64native_init(_crc64, crc64_table);
|
||||
}
|
||||
|
||||
/* Compute crc64 */
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
|
||||
return crcspeed64native(crc64_table, crc, (void *) s, l);
|
||||
}
|
||||
|
||||
/* Test main */
|
||||
@ -188,8 +130,31 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
|
||||
int crc64Test(int argc, char *argv[]) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
printf("e9c6d914c4b8d9ca == %016llx\n",
|
||||
(unsigned long long) crc64(0,(unsigned char*)"123456789",9));
|
||||
crc64_init();
|
||||
printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
|
||||
(uint64_t)_crc64(0, "123456789", 9));
|
||||
printf("[64speed]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
|
||||
(uint64_t)crc64(0, "123456789", 9));
|
||||
char li[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed "
|
||||
"do eiusmod tempor incididunt ut labore et dolore magna "
|
||||
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
|
||||
"ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis "
|
||||
"aute irure dolor in reprehenderit in voluptate velit esse "
|
||||
"cillum dolore eu fugiat nulla pariatur. Excepteur sint "
|
||||
"occaecat cupidatat non proident, sunt in culpa qui officia "
|
||||
"deserunt mollit anim id est laborum.";
|
||||
printf("[calcula]: c7794709e69683b3 == %016" PRIx64 "\n",
|
||||
(uint64_t)_crc64(0, li, sizeof(li)));
|
||||
printf("[64speed]: c7794709e69683b3 == %016" PRIx64 "\n",
|
||||
(uint64_t)crc64(0, li, sizeof(li)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef REDIS_TEST_MAIN
|
||||
int main(int argc, char *argv[]) {
|
||||
return crc64Test(argc, argv);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -7,6 +7,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void crc64_init(void);
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
|
281
src/crcspeed.c
Normal file
281
src/crcspeed.c
Normal file
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Mark Adler
|
||||
* Originally by: crc64.c Version 1.4 16 Dec 2013 Mark Adler
|
||||
* Modifications by Matt Stancliff <matt@genges.com>:
|
||||
* - removed CRC64-specific behavior
|
||||
* - added generation of lookup tables by parameters
|
||||
* - removed inversion of CRC input/result
|
||||
* - removed automatic initialization in favor of explicit initialization
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Mark Adler
|
||||
madler@alumni.caltech.edu
|
||||
*/
|
||||
|
||||
#include "crcspeed.h"
|
||||
|
||||
/* Fill in a CRC constants table. */
|
||||
void crcspeed64little_init(crcfn64 crcfn, uint64_t table[8][256]) {
|
||||
uint64_t crc;
|
||||
|
||||
/* generate CRCs for all single byte sequences */
|
||||
for (int n = 0; n < 256; n++) {
|
||||
table[0][n] = crcfn(0, &n, 1);
|
||||
}
|
||||
|
||||
/* generate nested CRC table for future slice-by-8 lookup */
|
||||
for (int n = 0; n < 256; n++) {
|
||||
crc = table[0][n];
|
||||
for (int k = 1; k < 8; k++) {
|
||||
crc = table[0][crc & 0xff] ^ (crc >> 8);
|
||||
table[k][n] = crc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crcspeed16little_init(crcfn16 crcfn, uint16_t table[8][256]) {
|
||||
uint16_t crc;
|
||||
|
||||
/* generate CRCs for all single byte sequences */
|
||||
for (int n = 0; n < 256; n++) {
|
||||
table[0][n] = crcfn(0, &n, 1);
|
||||
}
|
||||
|
||||
/* generate nested CRC table for future slice-by-8 lookup */
|
||||
for (int n = 0; n < 256; n++) {
|
||||
crc = table[0][n];
|
||||
for (int k = 1; k < 8; k++) {
|
||||
crc = table[0][(crc >> 8) & 0xff] ^ (crc << 8);
|
||||
table[k][n] = crc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Reverse the bytes in a 64-bit word. */
|
||||
static inline uint64_t rev8(uint64_t a) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_bswap64(a);
|
||||
#else
|
||||
uint64_t m;
|
||||
|
||||
m = UINT64_C(0xff00ff00ff00ff);
|
||||
a = ((a >> 8) & m) | (a & m) << 8;
|
||||
m = UINT64_C(0xffff0000ffff);
|
||||
a = ((a >> 16) & m) | (a & m) << 16;
|
||||
return a >> 32 | a << 32;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* This function is called once to initialize the CRC table for use on a
|
||||
big-endian architecture. */
|
||||
void crcspeed64big_init(crcfn64 fn, uint64_t big_table[8][256]) {
|
||||
/* Create the little endian table then reverse all the entires. */
|
||||
crcspeed64little_init(fn, big_table);
|
||||
for (int k = 0; k < 8; k++) {
|
||||
for (int n = 0; n < 256; n++) {
|
||||
big_table[k][n] = rev8(big_table[k][n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crcspeed16big_init(crcfn16 fn, uint16_t big_table[8][256]) {
|
||||
/* Create the little endian table then reverse all the entires. */
|
||||
crcspeed16little_init(fn, big_table);
|
||||
for (int k = 0; k < 8; k++) {
|
||||
for (int n = 0; n < 256; n++) {
|
||||
big_table[k][n] = rev8(big_table[k][n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate a non-inverted CRC multiple bytes at a time on a little-endian
|
||||
* architecture. If you need inverted CRC, invert *before* calling and invert
|
||||
* *after* calling.
|
||||
* 64 bit crc = process 8 bytes at once;
|
||||
*/
|
||||
uint64_t crcspeed64little(uint64_t little_table[8][256], uint64_t crc,
|
||||
void *buf, size_t len) {
|
||||
unsigned char *next = buf;
|
||||
|
||||
/* process individual bytes until we reach an 8-byte aligned pointer */
|
||||
while (len && ((uintptr_t)next & 7) != 0) {
|
||||
crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
/* fast middle processing, 8 bytes (aligned!) per loop */
|
||||
while (len >= 8) {
|
||||
crc ^= *(uint64_t *)next;
|
||||
crc = little_table[7][crc & 0xff] ^
|
||||
little_table[6][(crc >> 8) & 0xff] ^
|
||||
little_table[5][(crc >> 16) & 0xff] ^
|
||||
little_table[4][(crc >> 24) & 0xff] ^
|
||||
little_table[3][(crc >> 32) & 0xff] ^
|
||||
little_table[2][(crc >> 40) & 0xff] ^
|
||||
little_table[1][(crc >> 48) & 0xff] ^
|
||||
little_table[0][crc >> 56];
|
||||
next += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
/* process remaining bytes (can't be larger than 8) */
|
||||
while (len) {
|
||||
crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t crcspeed16little(uint16_t little_table[8][256], uint16_t crc,
|
||||
void *buf, size_t len) {
|
||||
unsigned char *next = buf;
|
||||
|
||||
/* process individual bytes until we reach an 8-byte aligned pointer */
|
||||
while (len && ((uintptr_t)next & 7) != 0) {
|
||||
crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
/* fast middle processing, 8 bytes (aligned!) per loop */
|
||||
while (len >= 8) {
|
||||
uint64_t n = *(uint64_t *)next;
|
||||
crc = little_table[7][(n & 0xff) ^ ((crc >> 8) & 0xff)] ^
|
||||
little_table[6][((n >> 8) & 0xff) ^ (crc & 0xff)] ^
|
||||
little_table[5][(n >> 16) & 0xff] ^
|
||||
little_table[4][(n >> 24) & 0xff] ^
|
||||
little_table[3][(n >> 32) & 0xff] ^
|
||||
little_table[2][(n >> 40) & 0xff] ^
|
||||
little_table[1][(n >> 48) & 0xff] ^
|
||||
little_table[0][n >> 56];
|
||||
next += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
/* process remaining bytes (can't be larger than 8) */
|
||||
while (len) {
|
||||
crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* Calculate a non-inverted CRC eight bytes at a time on a big-endian
|
||||
* architecture.
|
||||
*/
|
||||
uint64_t crcspeed64big(uint64_t big_table[8][256], uint64_t crc, void *buf,
|
||||
size_t len) {
|
||||
unsigned char *next = buf;
|
||||
|
||||
crc = rev8(crc);
|
||||
while (len && ((uintptr_t)next & 7) != 0) {
|
||||
crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
while (len >= 8) {
|
||||
crc ^= *(uint64_t *)next;
|
||||
crc = big_table[0][crc & 0xff] ^
|
||||
big_table[1][(crc >> 8) & 0xff] ^
|
||||
big_table[2][(crc >> 16) & 0xff] ^
|
||||
big_table[3][(crc >> 24) & 0xff] ^
|
||||
big_table[4][(crc >> 32) & 0xff] ^
|
||||
big_table[5][(crc >> 40) & 0xff] ^
|
||||
big_table[6][(crc >> 48) & 0xff] ^
|
||||
big_table[7][crc >> 56];
|
||||
next += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len) {
|
||||
crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
return rev8(crc);
|
||||
}
|
||||
|
||||
/* WARNING: Completely untested on big endian architecture. Possibly broken. */
|
||||
uint16_t crcspeed16big(uint16_t big_table[8][256], uint16_t crc_in, void *buf,
|
||||
size_t len) {
|
||||
unsigned char *next = buf;
|
||||
uint64_t crc = crc_in;
|
||||
|
||||
crc = rev8(crc);
|
||||
while (len && ((uintptr_t)next & 7) != 0) {
|
||||
crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
while (len >= 8) {
|
||||
uint64_t n = *(uint64_t *)next;
|
||||
crc = big_table[0][(n & 0xff) ^ ((crc >> (56 - 8)) & 0xff)] ^
|
||||
big_table[1][((n >> 8) & 0xff) ^ (crc & 0xff)] ^
|
||||
big_table[2][(n >> 16) & 0xff] ^
|
||||
big_table[3][(n >> 24) & 0xff] ^
|
||||
big_table[4][(n >> 32) & 0xff] ^
|
||||
big_table[5][(n >> 40) & 0xff] ^
|
||||
big_table[6][(n >> 48) & 0xff] ^
|
||||
big_table[7][n >> 56];
|
||||
next += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len) {
|
||||
crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
return rev8(crc);
|
||||
}
|
||||
|
||||
/* Return the CRC of buf[0..len-1] with initial crc, processing eight bytes
|
||||
at a time using passed-in lookup table.
|
||||
This selects one of two routines depending on the endianess of
|
||||
the architecture. */
|
||||
uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf,
|
||||
size_t len) {
|
||||
uint64_t n = 1;
|
||||
|
||||
return *(char *)&n ? crcspeed64little(table, crc, buf, len)
|
||||
: crcspeed64big(table, crc, buf, len);
|
||||
}
|
||||
|
||||
uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf,
|
||||
size_t len) {
|
||||
uint64_t n = 1;
|
||||
|
||||
return *(char *)&n ? crcspeed16little(table, crc, buf, len)
|
||||
: crcspeed16big(table, crc, buf, len);
|
||||
}
|
||||
|
||||
/* Initialize CRC lookup table in architecture-dependent manner. */
|
||||
void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]) {
|
||||
uint64_t n = 1;
|
||||
|
||||
*(char *)&n ? crcspeed64little_init(fn, table)
|
||||
: crcspeed64big_init(fn, table);
|
||||
}
|
||||
|
||||
void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]) {
|
||||
uint64_t n = 1;
|
||||
|
||||
*(char *)&n ? crcspeed16little_init(fn, table)
|
||||
: crcspeed16big_init(fn, table);
|
||||
}
|
60
src/crcspeed.h
Normal file
60
src/crcspeed.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* Copyright (c) 2014, Matt Stancliff <matt@genges.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. */
|
||||
|
||||
#ifndef CRCSPEED_H
|
||||
#define CRCSPEED_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef uint64_t (*crcfn64)(uint64_t, const void *, const uint64_t);
|
||||
typedef uint16_t (*crcfn16)(uint16_t, const void *, const uint64_t);
|
||||
|
||||
/* CRC-64 */
|
||||
void crcspeed64little_init(crcfn64 fn, uint64_t table[8][256]);
|
||||
void crcspeed64big_init(crcfn64 fn, uint64_t table[8][256]);
|
||||
void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]);
|
||||
|
||||
uint64_t crcspeed64little(uint64_t table[8][256], uint64_t crc, void *buf,
|
||||
size_t len);
|
||||
uint64_t crcspeed64big(uint64_t table[8][256], uint64_t crc, void *buf,
|
||||
size_t len);
|
||||
uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf,
|
||||
size_t len);
|
||||
|
||||
/* CRC-16 */
|
||||
void crcspeed16little_init(crcfn16 fn, uint16_t table[8][256]);
|
||||
void crcspeed16big_init(crcfn16 fn, uint16_t table[8][256]);
|
||||
void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]);
|
||||
|
||||
uint16_t crcspeed16little(uint16_t table[8][256], uint16_t crc, void *buf,
|
||||
size_t len);
|
||||
uint16_t crcspeed16big(uint16_t table[8][256], uint16_t crc, void *buf,
|
||||
size_t len);
|
||||
uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf,
|
||||
size_t len);
|
||||
#endif
|
@ -66,7 +66,7 @@ void cronCommand(client *c)
|
||||
spjob->vecargs.emplace_back(sdsdup(szFromObj(c->argv[i])));
|
||||
|
||||
robj *o = createObject(OBJ_CRON, spjob.release());
|
||||
setKey(c->db, c->argv[ARG_NAME], o);
|
||||
setKey(c, c->db, c->argv[ARG_NAME], o);
|
||||
decrRefCount(o);
|
||||
// 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);
|
||||
|
83
src/db.cpp
83
src/db.cpp
@ -41,6 +41,7 @@
|
||||
|
||||
int keyIsExpired(redisDb *db, robj *key);
|
||||
int expireIfNeeded(redisDb *db, robj *key, robj *o);
|
||||
void slotToKeyUpdateKeyCore(const char *key, size_t keylen, int add);
|
||||
|
||||
std::unique_ptr<expireEntry> deserializeExpire(sds key, const char *str, size_t cch, size_t *poffset);
|
||||
sds serializeStoredObjectAndExpire(redisDbPersistentData *db, const char *key, robj_roptr o);
|
||||
@ -223,7 +224,7 @@ bool dbAddCore(redisDb *db, robj *key, robj *val, bool fAssumeNew = false) {
|
||||
val->type == OBJ_ZSET ||
|
||||
val->type == OBJ_STREAM)
|
||||
signalKeyAsReady(db, key);
|
||||
if (g_pserver->cluster_enabled) slotToKeyAdd(key);
|
||||
if (g_pserver->cluster_enabled) slotToKeyAdd(szFromObj(key));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -320,18 +321,20 @@ int dbMerge(redisDb *db, robj *key, robj *val, int fReplace)
|
||||
* 3) The expire time of the key is reset (the key is made persistent),
|
||||
* unless 'keepttl' is true.
|
||||
*
|
||||
* All the new keys in the database should be created via this interface. */
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl) {
|
||||
* All the new keys in the database should be created via this interface.
|
||||
* The client 'c' argument may be set to NULL if the operation is performed
|
||||
* in a context where there is no clear client performing the operation. */
|
||||
void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal) {
|
||||
if (!dbAddCore(db, key, val)) {
|
||||
dbOverwrite(db, key, val, !keepttl);
|
||||
}
|
||||
incrRefCount(val);
|
||||
signalModifiedKey(db,key);
|
||||
if (signal) signalModifiedKey(c,db,key);
|
||||
}
|
||||
|
||||
/* Common case for genericSetKey() where the TTL is not retained. */
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
genericSetKey(db,key,val,0);
|
||||
void setKey(client *c, redisDb *db, robj *key, robj *val) {
|
||||
genericSetKey(c,db,key,val,0,1);
|
||||
}
|
||||
|
||||
/* Return true if the specified key exists in the specified database.
|
||||
@ -418,7 +421,7 @@ bool redisDbPersistentData::syncDelete(robj *key)
|
||||
sdsfree(keyTombstone);
|
||||
}
|
||||
}
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(key);
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(szFromObj(key));
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
@ -578,9 +581,11 @@ long long dbTotalServerKeyCount() {
|
||||
* Every time a DB is flushed the function signalFlushDb() is called.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void signalModifiedKey(redisDb *db, robj *key) {
|
||||
/* Note that the 'c' argument may be NULL if the key was modified out of
|
||||
* a context of a client. */
|
||||
void signalModifiedKey(client *c, redisDb *db, robj *key) {
|
||||
touchWatchedKey(db,key);
|
||||
trackingInvalidateKey(key);
|
||||
trackingInvalidateKey(c,key);
|
||||
}
|
||||
|
||||
void signalFlushedDb(int dbid) {
|
||||
@ -706,7 +711,7 @@ void delGenericCommand(client *c, int lazy) {
|
||||
int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) :
|
||||
dbSyncDelete(c->db,c->argv[j]);
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
signalModifiedKey(c,c->db,c->argv[j]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
"del",c->argv[j],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
@ -717,7 +722,7 @@ void delGenericCommand(client *c, int lazy) {
|
||||
}
|
||||
|
||||
void delCommand(client *c) {
|
||||
delGenericCommand(c,0);
|
||||
delGenericCommand(c,g_pserver->lazyfree_lazy_user_del);
|
||||
}
|
||||
|
||||
void unlinkCommand(client *c) {
|
||||
@ -1255,8 +1260,8 @@ void renameGenericCommand(client *c, int nx) {
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
if (spexpire != nullptr)
|
||||
setExpire(c,c->db,c->argv[2],std::move(*spexpire));
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[2]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_from",
|
||||
c->argv[1],c->db->id);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_to",
|
||||
@ -1333,8 +1338,8 @@ void moveCommand(client *c) {
|
||||
dbAdd(dst,c->argv[1],o);
|
||||
if (spexpire != nullptr) setExpire(c,dst,c->argv[1],std::move(*spexpire));
|
||||
|
||||
signalModifiedKey(src,c->argv[1]);
|
||||
signalModifiedKey(dst,c->argv[1]);
|
||||
signalModifiedKey(c,src,c->argv[1]);
|
||||
signalModifiedKey(c,dst,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
"move_from",c->argv[1],src->id);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
@ -1731,7 +1736,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
"expired",key,db->id);
|
||||
int retval = g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
if (retval) signalModifiedKey(db,key);
|
||||
if (retval) signalModifiedKey(NULL,db,key);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -1986,6 +1991,32 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* LCS ... [KEYS <key1> <key2>] ... */
|
||||
int *lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
{
|
||||
int i;
|
||||
int *keys = getKeysTempBuffer;
|
||||
UNUSED(cmd);
|
||||
|
||||
/* We need to parse the options of the command in order to check for the
|
||||
* "KEYS" argument before the "STRINGS" argument. */
|
||||
for (i = 1; i < argc; i++) {
|
||||
char *arg = szFromObj(argv[i]);
|
||||
int moreargs = (argc-1) - i;
|
||||
|
||||
if (!strcasecmp(arg, "strings")) {
|
||||
break;
|
||||
} else if (!strcasecmp(arg, "keys") && moreargs >= 2) {
|
||||
keys[0] = i+1;
|
||||
keys[1] = i+2;
|
||||
*numkeys = 2;
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
*numkeys = 0;
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from memory command.
|
||||
* MEMORY USAGE <key> */
|
||||
int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
@ -2053,11 +2084,14 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
* a fast way a key that belongs to a specified hash slot. This is useful
|
||||
* while rehashing the cluster and in other conditions when we need to
|
||||
* understand if we have keys for a given hash slot. */
|
||||
void slotToKeyUpdateKeyCore(const char *rgch, size_t keylen, int add)
|
||||
{
|
||||
void slotToKeyUpdateKey(sds key, int add) {
|
||||
slotToKeyUpdateKeyCore(key, sdslen(key), add);
|
||||
}
|
||||
|
||||
void slotToKeyUpdateKeyCore(const char *key, size_t keylen, int add) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
|
||||
unsigned int hashslot = keyHashSlot(rgch,(int)keylen);
|
||||
unsigned int hashslot = keyHashSlot(key,keylen);
|
||||
unsigned char buf[64];
|
||||
unsigned char *indexed = buf;
|
||||
|
||||
@ -2065,7 +2099,7 @@ void slotToKeyUpdateKeyCore(const char *rgch, size_t keylen, int add)
|
||||
if (keylen+2 > 64) indexed = (unsigned char*)zmalloc(keylen+2, MALLOC_SHARED);
|
||||
indexed[0] = (hashslot >> 8) & 0xff;
|
||||
indexed[1] = hashslot & 0xff;
|
||||
memcpy(indexed+2,rgch,keylen);
|
||||
memcpy(indexed+2,key,keylen);
|
||||
int fModified = false;
|
||||
if (add) {
|
||||
fModified = raxInsert(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
|
||||
@ -2076,16 +2110,11 @@ void slotToKeyUpdateKeyCore(const char *rgch, size_t keylen, int add)
|
||||
if (indexed != buf) zfree(indexed);
|
||||
}
|
||||
|
||||
void slotToKeyUpdateKey(robj *key, int add) {
|
||||
size_t keylen = sdslen(szFromObj(key));
|
||||
slotToKeyUpdateKeyCore(szFromObj(key), keylen, add);
|
||||
}
|
||||
|
||||
void slotToKeyAdd(robj *key) {
|
||||
void slotToKeyAdd(sds key) {
|
||||
slotToKeyUpdateKey(key,1);
|
||||
}
|
||||
|
||||
void slotToKeyDel(robj *key) {
|
||||
void slotToKeyDel(sds key) {
|
||||
slotToKeyUpdateKey(key,0);
|
||||
}
|
||||
|
||||
|
133
src/debug.cpp
133
src/debug.cpp
@ -374,7 +374,7 @@ void debugCommand(client *c) {
|
||||
"OOM -- Crash the server simulating an out-of-memory error.",
|
||||
"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.",
|
||||
"RELOAD -- Save the RDB on disk and reload it back in memory.",
|
||||
"RELOAD [MERGE] [NOFLUSH] [NOSAVE] -- Save the RDB on disk and reload it back in memory. By default it will save the RDB file and load it back. With the NOFLUSH option the current database is not removed before loading the new one, but conficts in keys will kill the server with an exception. When MERGE is used, conflicting keys will be loaded (the key in the loaded RDB file will win). When NOSAVE is used, the server will not save the current dataset in the RDB file before loading. Use DEBUG RELOAD NOSAVE when you want just to load the RDB file you placed in the Redis working directory in order to replace the current dataset in memory. Use DEBUG RELOAD NOSAVE NOFLUSH MERGE when you want to add what is in the current RDB file placed in the Redis current directory, with the current memory content. Use DEBUG RELOAD when you want to verify Redis is able to persist the current dataset in the RDB file, flush the memory content, and load it back.",
|
||||
"RESTART -- Graceful restart: save config, db, restart.",
|
||||
"SDSLEN <key> -- Show low level SDS string info representing key and value.",
|
||||
"SEGFAULT -- Crash the server with sigsegv.",
|
||||
@ -419,15 +419,44 @@ NULL
|
||||
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)ptrFromObj(c->argv[2]));
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(nullptr, rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
int flush = 1, save = 1;
|
||||
int flags = RDBFLAGS_NONE;
|
||||
|
||||
/* Parse the additional options that modify the RELOAD
|
||||
* behavior. */
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
char *opt = szFromObj(c->argv[j]);
|
||||
if (!strcasecmp(opt,"MERGE")) {
|
||||
flags |= RDBFLAGS_ALLOW_DUP;
|
||||
} else if (!strcasecmp(opt,"NOFLUSH")) {
|
||||
flush = 0;
|
||||
} else if (!strcasecmp(opt,"NOSAVE")) {
|
||||
save = 0;
|
||||
} else {
|
||||
addReplyError(c,"DEBUG RELOAD only supports the "
|
||||
"MERGE, NOFLUSH and NOSAVE options.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
|
||||
/* The default beahvior is to save the RDB file before loading
|
||||
* it back. */
|
||||
if (save) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(nullptr, rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* The default behavior is to remove the current dataset from
|
||||
* memory before loading the RDB file, however when MERGE is
|
||||
* used together with NOFLUSH, we are able to merge two datasets. */
|
||||
if (flush) emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
|
||||
protectClient(c);
|
||||
int ret = rdbLoadFile(g_pserver->rdb_filename,NULL,RDBFLAGS_NONE);
|
||||
int ret = rdbLoadFile(g_pserver->rdb_filename,NULL,flags);
|
||||
unprotectClient(c);
|
||||
if (ret != C_OK) {
|
||||
addReplyError(c,"Error trying to load the RDB dump");
|
||||
@ -497,7 +526,7 @@ NULL
|
||||
"encoding:%s serializedlength:%zu "
|
||||
"lru:%d lru_seconds_idle:%llu%s",
|
||||
(void*)val, static_cast<int>(val->getrefcount(std::memory_order_relaxed)),
|
||||
strenc, rdbSavedObjectLen(val),
|
||||
strenc, rdbSavedObjectLen(val, c->argv[2]),
|
||||
val->lru, estimateObjectIdleTime(val)/1000, extra);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"sdslen") && c->argc == 3) {
|
||||
auto itr = c->db->find(c->argv[2]);
|
||||
@ -564,7 +593,7 @@ NULL
|
||||
memcpy(ptrFromObj(val), buf, valsize<=buflen? valsize: buflen);
|
||||
}
|
||||
dbAdd(c->db,key,val);
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
decrRefCount(key);
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
@ -1072,6 +1101,61 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext.gregs[18]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[15]);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
|
||||
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
|
||||
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
|
||||
"X30:%016lx\n"
|
||||
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.regs[18],
|
||||
(unsigned long) uc->uc_mcontext.regs[19],
|
||||
(unsigned long) uc->uc_mcontext.regs[20],
|
||||
(unsigned long) uc->uc_mcontext.regs[21],
|
||||
(unsigned long) uc->uc_mcontext.regs[22],
|
||||
(unsigned long) uc->uc_mcontext.regs[23],
|
||||
(unsigned long) uc->uc_mcontext.regs[24],
|
||||
(unsigned long) uc->uc_mcontext.regs[25],
|
||||
(unsigned long) uc->uc_mcontext.regs[26],
|
||||
(unsigned long) uc->uc_mcontext.regs[27],
|
||||
(unsigned long) uc->uc_mcontext.regs[28],
|
||||
(unsigned long) uc->uc_mcontext.regs[29],
|
||||
(unsigned long) uc->uc_mcontext.regs[30],
|
||||
(unsigned long) uc->uc_mcontext.pc,
|
||||
(unsigned long) uc->uc_mcontext.sp,
|
||||
(unsigned long) uc->uc_mcontext.pstate,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.sp);
|
||||
#elif defined(__arm__) /* Linux ARM */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"R10:%016lx R9 :%016lx\nR8 :%016lx R7 :%016lx\n"
|
||||
"R6 :%016lx R5 :%016lx\nR4 :%016lx R3 :%016lx\n"
|
||||
"R2 :%016lx R1 :%016lx\nR0 :%016lx EC :%016lx\n"
|
||||
"fp: %016lx ip:%016lx\n",
|
||||
"pc:%016lx sp:%016lx\ncpsr:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.arm_r10,
|
||||
(unsigned long) uc->uc_mcontext.arm_r9,
|
||||
(unsigned long) uc->uc_mcontext.arm_r8,
|
||||
(unsigned long) uc->uc_mcontext.arm_r7,
|
||||
(unsigned long) uc->uc_mcontext.arm_r6,
|
||||
(unsigned long) uc->uc_mcontext.arm_r5,
|
||||
(unsigned long) uc->uc_mcontext.arm_r4,
|
||||
(unsigned long) uc->uc_mcontext.arm_r3,
|
||||
(unsigned long) uc->uc_mcontext.arm_r2,
|
||||
(unsigned long) uc->uc_mcontext.arm_r1,
|
||||
(unsigned long) uc->uc_mcontext.arm_r0,
|
||||
(unsigned long) uc->uc_mcontext.error_code,
|
||||
(unsigned long) uc->uc_mcontext.arm_fp,
|
||||
(unsigned long) uc->uc_mcontext.arm_ip,
|
||||
(unsigned long) uc->uc_mcontext.arm_pc,
|
||||
(unsigned long) uc->uc_mcontext.arm_sp,
|
||||
(unsigned long) uc->uc_mcontext.arm_cpsr,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.arm_sp);
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
#if defined(__x86_64__)
|
||||
@ -1212,33 +1296,6 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext.mc_cs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
|
||||
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
|
||||
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
|
||||
"X30:%016lx\n"
|
||||
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.regs[18],
|
||||
(unsigned long) uc->uc_mcontext.regs[19],
|
||||
(unsigned long) uc->uc_mcontext.regs[20],
|
||||
(unsigned long) uc->uc_mcontext.regs[21],
|
||||
(unsigned long) uc->uc_mcontext.regs[22],
|
||||
(unsigned long) uc->uc_mcontext.regs[23],
|
||||
(unsigned long) uc->uc_mcontext.regs[24],
|
||||
(unsigned long) uc->uc_mcontext.regs[25],
|
||||
(unsigned long) uc->uc_mcontext.regs[26],
|
||||
(unsigned long) uc->uc_mcontext.regs[27],
|
||||
(unsigned long) uc->uc_mcontext.regs[28],
|
||||
(unsigned long) uc->uc_mcontext.regs[29],
|
||||
(unsigned long) uc->uc_mcontext.regs[30],
|
||||
(unsigned long) uc->uc_mcontext.pc,
|
||||
(unsigned long) uc->uc_mcontext.sp,
|
||||
(unsigned long) uc->uc_mcontext.pstate,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.sp);
|
||||
#else
|
||||
serverLog(LL_WARNING,
|
||||
" Dumping of registers not supported for this OS/arch");
|
||||
@ -1658,7 +1715,7 @@ void enableWatchdog(int period) {
|
||||
/* Watchdog was actually disabled, so we have to setup the signal
|
||||
* handler. */
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
act.sa_sigaction = watchdogSignalHandler;
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
}
|
||||
|
@ -824,8 +824,8 @@ dictEntry *dictGetFairRandomKey(dict *d) {
|
||||
/* Function to reverse bits. Algorithm from:
|
||||
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
|
||||
static unsigned long rev(unsigned long v) {
|
||||
unsigned long s = 8 * sizeof(v); // bit size; must be power of 2
|
||||
unsigned long mask = ~0;
|
||||
unsigned long s = CHAR_BIT * sizeof(v); // bit size; must be power of 2
|
||||
unsigned long mask = ~0UL;
|
||||
while ((s >>= 1) > 0) {
|
||||
mask ^= (mask << s);
|
||||
v = ((v >> s) & mask) | ((v << s) & ~mask);
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "server.h"
|
||||
#include "bio.h"
|
||||
#include "atomicvar.h"
|
||||
#include <mutex>
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Data structures
|
||||
@ -389,6 +390,7 @@ size_t freeMemoryGetNotCountedMemory(void) {
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)listNodeValue(ln);
|
||||
std::unique_lock<fastlock>(replica->lock);
|
||||
overhead += getClientOutputBufferMemoryUsage(replica);
|
||||
}
|
||||
}
|
||||
@ -489,11 +491,11 @@ int freeMemoryIfNeeded(bool fPreSnapshot) {
|
||||
if (g_pserver->m_pstorageFactory == nullptr && listLength(g_pserver->masters) && g_pserver->repl_slave_ignore_maxmemory && !g_pserver->fActiveReplica) return C_OK;
|
||||
|
||||
size_t mem_reported, mem_tofree, mem_freed;
|
||||
mstime_t latency, eviction_latency;
|
||||
mstime_t latency, eviction_latency, lazyfree_latency;
|
||||
long long delta;
|
||||
int slaves = listLength(g_pserver->slaves);
|
||||
const bool fEvictToStorage = !cserver.delete_on_evict && g_pserver->db[0]->FStorageProvider();
|
||||
|
||||
int result = C_ERR;
|
||||
|
||||
/* When clients are paused the dataset should be static not just from the
|
||||
* POV of clients not being able to write, but also from the POV of
|
||||
@ -504,10 +506,10 @@ int freeMemoryIfNeeded(bool fPreSnapshot) {
|
||||
|
||||
mem_freed = 0;
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
if (g_pserver->maxmemory_policy == MAXMEMORY_NO_EVICTION)
|
||||
goto cant_free; /* We need to free memory, but policy forbids. */
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
while (mem_freed < mem_tofree) {
|
||||
int j, k, i, keys_freed = 0;
|
||||
static unsigned int next_db = 0;
|
||||
@ -638,9 +640,9 @@ int freeMemoryIfNeeded(bool fPreSnapshot) {
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
signalModifiedKey(NULL,db,keyobj);
|
||||
latencyEndMonitor(eviction_latency);
|
||||
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
|
||||
latencyRemoveNestedEvent(latency,eviction_latency);
|
||||
delta -= (long long) zmalloc_used_memory();
|
||||
mem_freed += delta;
|
||||
g_pserver->stat_evictedkeys++;
|
||||
@ -648,7 +650,6 @@ int freeMemoryIfNeeded(bool fPreSnapshot) {
|
||||
keyobj, db->id);
|
||||
decrRefCount(keyobj);
|
||||
}
|
||||
|
||||
keys_freed++;
|
||||
|
||||
/* When the memory to free starts to be big enough, we may
|
||||
@ -670,17 +671,11 @@ int freeMemoryIfNeeded(bool fPreSnapshot) {
|
||||
mem_freed = mem_tofree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (keys_freed <= 0) {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
} else {
|
||||
goto cant_free; /* nothing to free... */
|
||||
}
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
return C_OK;
|
||||
result = C_OK;
|
||||
|
||||
cant_free:
|
||||
if (!cserver.delete_on_evict)
|
||||
@ -701,12 +696,21 @@ cant_free:
|
||||
/* We are here if we are not able to reclaim memory. There is only one
|
||||
* last thing we can try: check if the lazyfree thread has jobs in queue
|
||||
* and wait... */
|
||||
while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
|
||||
if (((mem_reported - zmalloc_used_memory()) + mem_freed) >= mem_tofree)
|
||||
break;
|
||||
usleep(1000);
|
||||
if (result != C_OK) {
|
||||
latencyStartMonitor(lazyfree_latency);
|
||||
while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
|
||||
result = C_OK;
|
||||
break;
|
||||
}
|
||||
usleep(1000);
|
||||
}
|
||||
latencyEndMonitor(lazyfree_latency);
|
||||
latencyAddSampleIfNeeded("eviction-lazyfree",lazyfree_latency);
|
||||
}
|
||||
return C_ERR;
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
|
||||
|
@ -33,6 +33,17 @@
|
||||
#include "server.h"
|
||||
#include "cron.h"
|
||||
|
||||
/* Helper function for the activeExpireCycle() function.
|
||||
* This function will try to expire the key that is stored in the hash table
|
||||
* entry 'de' of the 'expires' hash table of a Redis database.
|
||||
*
|
||||
* If the key is found to be expired, it is removed from the database and
|
||||
* 1 is returned. Otherwise no operation is performed and 0 is returned.
|
||||
*
|
||||
* When a key is expired, g_pserver->stat_expiredkeys is incremented.
|
||||
*
|
||||
* The parameter 'now' is the current time in milliseconds as is passed
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
@ -43,7 +54,7 @@ void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
|
||||
dbSyncDelete(db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
if (g_pserver->tracking_clients) trackingInvalidateKey(keyobj);
|
||||
trackingInvalidateKey(NULL, keyobj);
|
||||
decrRefCount(keyobj);
|
||||
g_pserver->stat_expiredkeys++;
|
||||
}
|
||||
@ -56,17 +67,7 @@ void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
|
||||
* if no access is performed on them.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Helper function for the activeExpireCycle() function.
|
||||
* This function will try to expire the key that is stored in the hash table
|
||||
* entry 'de' of the 'expires' hash table of a Redis database.
|
||||
*
|
||||
* If the key is found to be expired, it is removed from the database and
|
||||
* 1 is returned. Otherwise no operation is performed and 0 is returned.
|
||||
*
|
||||
* When a key is expired, g_pserver->stat_expiredkeys is incremented.
|
||||
*
|
||||
* The parameter 'now' is the current time in milliseconds as is passed
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
|
||||
void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
if (!e.FFat())
|
||||
{
|
||||
@ -152,7 +153,7 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
signalModifiedKey(db,&objKey);
|
||||
signalModifiedKey(nullptr, db,&objKey);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"srem",&objKey,db->id);
|
||||
break;
|
||||
}
|
||||
@ -225,7 +226,7 @@ void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, lo
|
||||
}
|
||||
|
||||
setExpire(c, c->db, key, subkey, when);
|
||||
signalModifiedKey(c->db, key);
|
||||
signalModifiedKey(c, c->db, key);
|
||||
g_pserver->dirty++;
|
||||
addReply(c, shared.cone);
|
||||
}
|
||||
@ -604,14 +605,14 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
/* Replicate/AOF this as an explicit DEL or UNLINK. */
|
||||
aux = g_pserver->lazyfree_lazy_expire ? shared.unlink : shared.del;
|
||||
rewriteClientCommandVector(c,2,aux,key);
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
addReply(c, shared.cone);
|
||||
return;
|
||||
} else {
|
||||
setExpire(c,c->db,key,nullptr,when);
|
||||
addReply(c,shared.cone);
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
|
||||
g_pserver->dirty++;
|
||||
return;
|
||||
@ -700,6 +701,7 @@ void persistCommand(client *c) {
|
||||
if (lookupKeyWrite(c->db,c->argv[1])) {
|
||||
if (c->argc == 2) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
@ -707,6 +709,7 @@ void persistCommand(client *c) {
|
||||
}
|
||||
} else if (c->argc == 3) {
|
||||
if (c->db->removeSubkeyExpire(c->argv[1], c->argv[2])) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
|
@ -194,8 +194,9 @@ static int futex(volatile unsigned *uaddr, int futex_op, int val,
|
||||
|
||||
class DeadlockDetector
|
||||
{
|
||||
fastlock m_lock { "deadlock detector" }; // destruct this first
|
||||
std::map<pid_t, fastlock *> m_mapwait;
|
||||
fastlock m_lock { "deadlock detector" };
|
||||
|
||||
public:
|
||||
void registerwait(fastlock *lock, pid_t thispid)
|
||||
{
|
||||
|
@ -657,13 +657,13 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
if (returned_items) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
setKey(c->db,storekey,zobj);
|
||||
setKey(c,c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
c->db->id);
|
||||
g_pserver->dirty += returned_items;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
signalModifiedKey(c->db,storekey);
|
||||
signalModifiedKey(c,c->db,storekey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
|
@ -206,7 +206,11 @@ int geohashDecodeWGS84(const GeoHashBits hash, GeoHashArea *area) {
|
||||
int geohashDecodeAreaToLongLat(const GeoHashArea *area, double *xy) {
|
||||
if (!xy) return 0;
|
||||
xy[0] = (area->longitude.min + area->longitude.max) / 2;
|
||||
if (xy[0] > GEO_LONG_MAX) xy[0] = GEO_LONG_MAX;
|
||||
if (xy[0] < GEO_LONG_MIN) xy[0] = GEO_LONG_MIN;
|
||||
xy[1] = (area->latitude.min + area->latitude.max) / 2;
|
||||
if (xy[1] > GEO_LAT_MAX) xy[1] = GEO_LAT_MAX;
|
||||
if (xy[1] < GEO_LAT_MIN) xy[1] = GEO_LAT_MIN;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
213
src/help.h
213
src/help.h
@ -28,6 +28,56 @@ struct commandHelp {
|
||||
int group;
|
||||
char *since;
|
||||
} commandHelp[] = {
|
||||
{ "ACL CAT",
|
||||
"[categoryname]",
|
||||
"List the ACL categories or the commands inside a category",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL DELUSER",
|
||||
"username [username ...]",
|
||||
"Remove the specified ACL users and the associated rules",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL GENPASS",
|
||||
"[bits]",
|
||||
"Generate a pseudorandom secure password to use for ACL users",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL LIST",
|
||||
"-",
|
||||
"List the current ACL rules in ACL config file format",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL LOAD",
|
||||
"-",
|
||||
"Reload the ACLs from the configured ACL file",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL LOG",
|
||||
"[count or RESET]",
|
||||
"List latest events denied because of ACLs in place",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL SAVE",
|
||||
"-",
|
||||
"Save the current ACL rules in the configured ACL file",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL SETUSER",
|
||||
"rule [rule ...]",
|
||||
"Modify or create the rules for a specific ACL user",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL USERS",
|
||||
"-",
|
||||
"List the username of all the configured ACL rules",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL WHOAMI",
|
||||
"-",
|
||||
"Return the name of the user associated to the current connection",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "APPEND",
|
||||
"key value",
|
||||
"Append a value to a key",
|
||||
@ -44,7 +94,7 @@ struct commandHelp {
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "BGSAVE",
|
||||
"-",
|
||||
"[SCHEDULE]",
|
||||
"Asynchronously save the dataset to disk",
|
||||
9,
|
||||
"1.0.0" },
|
||||
@ -80,7 +130,7 @@ struct commandHelp {
|
||||
"2.0.0" },
|
||||
{ "BRPOPLPUSH",
|
||||
"source destination timeout",
|
||||
"Pop a value from a list, push it to another list and return it; or block until one is available",
|
||||
"Pop an element from a list, push it to another list and return it; or block until one is available",
|
||||
2,
|
||||
"2.2.0" },
|
||||
{ "BZPOPMAX",
|
||||
@ -93,51 +143,71 @@ struct commandHelp {
|
||||
"Remove and return the member with the lowest score from one or more sorted sets, or block until one is available",
|
||||
4,
|
||||
"5.0.0" },
|
||||
{ "CLIENT CACHING",
|
||||
"YES|NO",
|
||||
"Instruct the server about tracking or not keys in the next request",
|
||||
8,
|
||||
"6.0.0" },
|
||||
{ "CLIENT GETNAME",
|
||||
"-",
|
||||
"Get the current connection name",
|
||||
9,
|
||||
8,
|
||||
"2.6.9" },
|
||||
{ "CLIENT GETREDIR",
|
||||
"-",
|
||||
"Get tracking notifications redirection client ID if any",
|
||||
8,
|
||||
"6.0.0" },
|
||||
{ "CLIENT ID",
|
||||
"-",
|
||||
"Returns the client ID for the current connection",
|
||||
9,
|
||||
8,
|
||||
"5.0.0" },
|
||||
{ "CLIENT KILL",
|
||||
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]",
|
||||
"Kill the connection of a client",
|
||||
9,
|
||||
8,
|
||||
"2.4.0" },
|
||||
{ "CLIENT LIST",
|
||||
"-",
|
||||
"[TYPE normal|master|replica|pubsub]",
|
||||
"Get the list of client connections",
|
||||
9,
|
||||
8,
|
||||
"2.4.0" },
|
||||
{ "CLIENT PAUSE",
|
||||
"timeout",
|
||||
"Stop processing commands from clients for some time",
|
||||
9,
|
||||
8,
|
||||
"2.9.50" },
|
||||
{ "CLIENT REPLY",
|
||||
"ON|OFF|SKIP",
|
||||
"Instruct the server whether to reply to commands",
|
||||
9,
|
||||
8,
|
||||
"3.2" },
|
||||
{ "CLIENT SETNAME",
|
||||
"connection-name",
|
||||
"Set the current connection name",
|
||||
9,
|
||||
8,
|
||||
"2.6.9" },
|
||||
{ "CLIENT TRACKING",
|
||||
"ON|OFF [REDIRECT client-id] [PREFIX prefix] [BCAST] [OPTIN] [OPTOUT] [NOLOOP]",
|
||||
"Enable or disable server assisted client side caching support",
|
||||
8,
|
||||
"6.0.0" },
|
||||
{ "CLIENT UNBLOCK",
|
||||
"client-id [TIMEOUT|ERROR]",
|
||||
"Unblock a client blocked in a blocking command from a different connection",
|
||||
9,
|
||||
8,
|
||||
"5.0.0" },
|
||||
{ "CLUSTER ADDSLOTS",
|
||||
"slot [slot ...]",
|
||||
"Assign new hash slots to receiving node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER BUMPEPOCH",
|
||||
"-",
|
||||
"Advance the cluster config epoch",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER COUNT-FAILURE-REPORTS",
|
||||
"node-id",
|
||||
"Return the number of failure reports active for a given node",
|
||||
@ -158,6 +228,11 @@ struct commandHelp {
|
||||
"Forces a replica to perform a manual failover of its master.",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FLUSHSLOTS",
|
||||
"-",
|
||||
"Delete a node's own slots information",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FORGET",
|
||||
"node-id",
|
||||
"Remove a node from the nodes table",
|
||||
@ -183,6 +258,11 @@ struct commandHelp {
|
||||
"Force a node cluster to handshake with another node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER MYID",
|
||||
"-",
|
||||
"Return the node id",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER NODES",
|
||||
"-",
|
||||
"Get Cluster config for the node",
|
||||
@ -365,7 +445,7 @@ struct commandHelp {
|
||||
13,
|
||||
"3.2.0" },
|
||||
{ "GEODIST",
|
||||
"key member1 member2 [unit]",
|
||||
"key member1 member2 [m|km|ft|mi]",
|
||||
"Returns the distance between two members of a geospatial index",
|
||||
13,
|
||||
"3.2.0" },
|
||||
@ -414,6 +494,11 @@ struct commandHelp {
|
||||
"Delete one or more hash fields",
|
||||
5,
|
||||
"2.0.0" },
|
||||
{ "HELLO",
|
||||
"protover [AUTH username password] [SETNAME clientname]",
|
||||
"switch Redis protocol",
|
||||
8,
|
||||
"6.0.0" },
|
||||
{ "HEXISTS",
|
||||
"key field",
|
||||
"Determine if a hash field exists",
|
||||
@ -465,7 +550,7 @@ struct commandHelp {
|
||||
5,
|
||||
"2.8.0" },
|
||||
{ "HSET",
|
||||
"key field value",
|
||||
"key field value [field value ...]",
|
||||
"Set the string value of a hash field",
|
||||
5,
|
||||
"2.0.0" },
|
||||
@ -514,13 +599,43 @@ struct commandHelp {
|
||||
"Get the UNIX time stamp of the last successful save to disk",
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "LATENCY DOCTOR",
|
||||
"-",
|
||||
"Return a human readable latency analysis report.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY GRAPH",
|
||||
"event",
|
||||
"Return a latency graph for the event.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY HELP",
|
||||
"-",
|
||||
"Show helpful text about the different subcommands.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY HISTORY",
|
||||
"event",
|
||||
"Return timestamp-latency samples for the event.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY LATEST",
|
||||
"-",
|
||||
"Return the latest latency samples for all events.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY RESET",
|
||||
"[event]",
|
||||
"Reset latency data for one or more events.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LINDEX",
|
||||
"key index",
|
||||
"Get an element from a list by its index",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LINSERT",
|
||||
"key BEFORE|AFTER pivot value",
|
||||
"key BEFORE|AFTER pivot element",
|
||||
"Insert an element before or after another element in a list",
|
||||
2,
|
||||
"2.2.0" },
|
||||
@ -529,19 +644,24 @@ struct commandHelp {
|
||||
"Get the length of a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LOLWUT",
|
||||
"[VERSION version]",
|
||||
"Display some computer art and the Redis version",
|
||||
9,
|
||||
"5.0.0" },
|
||||
{ "LPOP",
|
||||
"key",
|
||||
"Remove and get the first element in a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LPUSH",
|
||||
"key value [value ...]",
|
||||
"Prepend one or multiple values to a list",
|
||||
"key element [element ...]",
|
||||
"Prepend one or multiple elements to a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LPUSHX",
|
||||
"key value",
|
||||
"Prepend a value to a list, only if the list exists",
|
||||
"key element [element ...]",
|
||||
"Prepend an element to a list, only if the list exists",
|
||||
2,
|
||||
"2.2.0" },
|
||||
{ "LRANGE",
|
||||
@ -550,12 +670,12 @@ struct commandHelp {
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LREM",
|
||||
"key count value",
|
||||
"key count element",
|
||||
"Remove elements from a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LSET",
|
||||
"key index value",
|
||||
"key index element",
|
||||
"Set the value of an element in a list by its index",
|
||||
2,
|
||||
"1.0.0" },
|
||||
@ -600,10 +720,25 @@ struct commandHelp {
|
||||
1,
|
||||
"1.0.0" },
|
||||
{ "MIGRATE",
|
||||
"host port key|"" destination-db timeout [COPY] [REPLACE] [KEYS key]",
|
||||
"host port key|"" destination-db timeout [COPY] [REPLACE] [AUTH password] [KEYS key]",
|
||||
"Atomically transfer a key from a Redis instance to another one.",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "MODULE LIST",
|
||||
"-",
|
||||
"List all modules loaded by the server",
|
||||
9,
|
||||
"4.0.0" },
|
||||
{ "MODULE LOAD",
|
||||
"path [arg]",
|
||||
"Load a module",
|
||||
9,
|
||||
"4.0.0" },
|
||||
{ "MODULE UNLOAD",
|
||||
"name",
|
||||
"Unload a module",
|
||||
9,
|
||||
"4.0.0" },
|
||||
{ "MONITOR",
|
||||
"-",
|
||||
"Listen for all requests received by the server in real time",
|
||||
@ -679,6 +814,11 @@ struct commandHelp {
|
||||
"Listen for messages published to channels matching the given patterns",
|
||||
6,
|
||||
"2.0.0" },
|
||||
{ "PSYNC",
|
||||
"replicationid offset",
|
||||
"Internal command used for replication",
|
||||
9,
|
||||
"2.8.0" },
|
||||
{ "PTTL",
|
||||
"key [subkey]",
|
||||
"Get the time to live for a key or subkey in milliseconds",
|
||||
@ -735,7 +875,7 @@ struct commandHelp {
|
||||
9,
|
||||
"5.0.0" },
|
||||
{ "RESTORE",
|
||||
"key ttl serialized-value [REPLACE]",
|
||||
"key ttl serialized-value [REPLACE] [ABSTTL] [IDLETIME seconds] [FREQ frequency]",
|
||||
"Create a key using the provided serialized value, previously obtained using DUMP.",
|
||||
0,
|
||||
"2.6.0" },
|
||||
@ -755,13 +895,13 @@ struct commandHelp {
|
||||
2,
|
||||
"1.2.0" },
|
||||
{ "RPUSH",
|
||||
"key value [value ...]",
|
||||
"Append one or multiple values to a list",
|
||||
"key element [element ...]",
|
||||
"Append one or multiple elements to a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "RPUSHX",
|
||||
"key value",
|
||||
"Append a value to a list, only if the list exists",
|
||||
"key element [element ...]",
|
||||
"Append an element to a list, only if the list exists",
|
||||
2,
|
||||
"2.2.0" },
|
||||
{ "SADD",
|
||||
@ -775,7 +915,7 @@ struct commandHelp {
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "SCAN",
|
||||
"cursor [MATCH pattern] [COUNT count]",
|
||||
"cursor [MATCH pattern] [COUNT count] [TYPE type]",
|
||||
"Incrementally iterate the keys space",
|
||||
0,
|
||||
"2.8.0" },
|
||||
@ -825,7 +965,7 @@ struct commandHelp {
|
||||
8,
|
||||
"1.0.0" },
|
||||
{ "SET",
|
||||
"key value [expiration EX seconds|PX milliseconds] [NX|XX]",
|
||||
"key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]",
|
||||
"Set the string value of a key",
|
||||
1,
|
||||
"1.0.0" },
|
||||
@ -914,6 +1054,11 @@ struct commandHelp {
|
||||
"Incrementally iterate Set elements",
|
||||
3,
|
||||
"2.8.0" },
|
||||
{ "STRALGO",
|
||||
"LCS algo-specific-argument [algo-specific-argument ...]",
|
||||
"Run algorithms (currently LCS) against strings",
|
||||
1,
|
||||
"6.0.0" },
|
||||
{ "STRLEN",
|
||||
"key",
|
||||
"Get the length of the value stored in a key",
|
||||
@ -935,9 +1080,9 @@ struct commandHelp {
|
||||
3,
|
||||
"1.0.0" },
|
||||
{ "SWAPDB",
|
||||
"index index",
|
||||
"index1 index2",
|
||||
"Swaps two Redis databases",
|
||||
8,
|
||||
9,
|
||||
"4.0.0" },
|
||||
{ "SYNC",
|
||||
"-",
|
||||
@ -995,7 +1140,7 @@ struct commandHelp {
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XADD",
|
||||
"key ID field string [field string ...]",
|
||||
"key ID field value [field value ...]",
|
||||
"Appends a new entry to a stream",
|
||||
14,
|
||||
"5.0.0" },
|
||||
@ -1010,7 +1155,7 @@ struct commandHelp {
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XGROUP",
|
||||
"[CREATE key groupname id-or-$] [SETID key id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]",
|
||||
"[CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]",
|
||||
"Create, destroy, and manage consumer groups.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
@ -1035,12 +1180,12 @@ struct commandHelp {
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XREAD",
|
||||
"[COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
|
||||
"[COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]",
|
||||
"Return never seen elements in multiple streams, with IDs greater than the ones reported by the caller for each stream. Can block.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XREADGROUP",
|
||||
"GROUP group consumer [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
|
||||
"GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]",
|
||||
"Return new entries from a stream using a consumer group, or access the history of the pending entries for a given consumer. Can block.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
|
@ -1219,7 +1219,7 @@ void pfaddCommand(client *c) {
|
||||
}
|
||||
hdr = (hllhdr*)ptrFromObj(o);
|
||||
if (updated) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
HLL_INVALIDATE_CACHE(hdr);
|
||||
@ -1310,7 +1310,7 @@ void pfcountCommand(client *c) {
|
||||
* data structure is not modified, since the cached value
|
||||
* may be modified and given that the HLL is a Redis string
|
||||
* we need to propagate the change. */
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
addReplyLongLong(c,card);
|
||||
@ -1383,7 +1383,7 @@ void pfmergeCommand(client *c) {
|
||||
last hllSparseSet() call. */
|
||||
HLL_INVALIDATE_CACHE(hdr);
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
/* We generate a PFADD event for PFMERGE for semantical simplicity
|
||||
* since in theory this is a mass-add of elements. */
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
|
@ -92,7 +92,7 @@ bool redisDbPersistentData::asyncDelete(robj *key) {
|
||||
* field to NULL in order to lazy free it later. */
|
||||
if (de) {
|
||||
dictFreeUnlinkedEntry(m_pdict,de);
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(key);
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(szFromObj(key));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -34,6 +34,10 @@
|
||||
/* This represents a very simple generic canvas in order to draw stuff.
|
||||
* It's up to each LOLWUT versions to translate what they draw to the
|
||||
* screen, depending on the result to accomplish. */
|
||||
|
||||
#ifndef __LOLWUT_H
|
||||
#define __LOLWUT_H
|
||||
|
||||
typedef struct lwCanvas {
|
||||
int width;
|
||||
int height;
|
||||
@ -47,3 +51,5 @@ void lwDrawPixel(lwCanvas *canvas, int x, int y, int color);
|
||||
int lwGetPixel(lwCanvas *canvas, int x, int y);
|
||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color);
|
||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color);
|
||||
|
||||
#endif
|
||||
|
@ -910,7 +910,7 @@ void RM_SetModuleOptions(RedisModuleCtx *ctx, int options) {
|
||||
/* 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) {
|
||||
signalModifiedKey(ctx->client->db,keyname);
|
||||
signalModifiedKey(ctx->client,ctx->client->db,keyname);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@ -2086,7 +2086,7 @@ void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
|
||||
static void moduleCloseKey(RedisModuleKey *key) {
|
||||
int signal = SHOULD_SIGNAL_MODIFIED_KEYS(key->ctx);
|
||||
if ((key->mode & REDISMODULE_WRITE) && signal)
|
||||
signalModifiedKey(key->db,key->key);
|
||||
signalModifiedKey(key->ctx->client,key->db,key->key);
|
||||
/* TODO: if (key->iter) RM_KeyIteratorStop(kp); */
|
||||
RM_ZsetRangeStop(key);
|
||||
decrRefCount(key->key);
|
||||
@ -2231,7 +2231,7 @@ RedisModuleString *RM_RandomKey(RedisModuleCtx *ctx) {
|
||||
int RM_StringSet(RedisModuleKey *key, RedisModuleString *str) {
|
||||
if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR;
|
||||
RM_DeleteKey(key);
|
||||
setKey(key->db,key->key,str);
|
||||
genericSetKey(key->ctx->client,key->db,key->key,str,0,0);
|
||||
key->value = str;
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@ -2311,7 +2311,7 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
|
||||
if (key->value == NULL) {
|
||||
/* Empty key: create it with the new size. */
|
||||
robj *o = createObject(OBJ_STRING,sdsnewlen(NULL, newlen));
|
||||
setKey(key->db,key->key,o);
|
||||
genericSetKey(key->ctx->client,key->db,key->key,o,0,0);
|
||||
key->value = o;
|
||||
decrRefCount(o);
|
||||
} else {
|
||||
@ -3701,7 +3701,7 @@ int RM_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value) {
|
||||
if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR;
|
||||
RM_DeleteKey(key);
|
||||
robj *o = createModuleObject(mt,value);
|
||||
setKey(key->db,key->key,o);
|
||||
genericSetKey(key->ctx->client,key->db,key->key,o,0,0);
|
||||
decrRefCount(o);
|
||||
key->value = o;
|
||||
return REDISMODULE_OK;
|
||||
@ -4473,14 +4473,17 @@ RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdF
|
||||
* can really be unblocked, since the module was able to serve the client.
|
||||
* If the callback returns REDISMODULE_OK, then the client can be unblocked,
|
||||
* otherwise the client remains blocked and we'll retry again when one of
|
||||
* the keys it blocked for becomes "ready" again. */
|
||||
* the keys it blocked for becomes "ready" again.
|
||||
* This function returns 1 if client was served (and should be unblocked) */
|
||||
int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
|
||||
int served = 0;
|
||||
RedisModuleBlockedClient *bc = (RedisModuleBlockedClient*)c->bpop.module_blocked_handle;
|
||||
|
||||
/* Protect against re-processing: don't serve clients that are already
|
||||
* in the unblocking list for any reason (including RM_UnblockClient()
|
||||
* explicit call). */
|
||||
if (bc->unblocked) return REDISMODULE_ERR;
|
||||
* explicit call). See #6798. */
|
||||
if (bc->unblocked) return 0;
|
||||
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
ctx.flags |= REDISMODULE_CTX_BLOCKED_REPLY;
|
||||
ctx.blocked_ready_key = key;
|
||||
@ -6104,7 +6107,7 @@ sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int
|
||||
struct RedisModule *module = (RedisModule*)dictGetVal(de);
|
||||
if (!module->info_cb)
|
||||
continue;
|
||||
RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0};
|
||||
RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0, 0};
|
||||
module->info_cb(&info_ctx, for_crash_report);
|
||||
/* Implicitly end dicts (no way to handle errors, and we must add the newline). */
|
||||
if (info_ctx.in_dict_field)
|
||||
@ -7769,11 +7772,9 @@ int RM_GetLFU(RedisModuleKey *key, long long *lfu_freq) {
|
||||
*lfu_freq = -1;
|
||||
if (!key->value)
|
||||
return REDISMODULE_ERR;
|
||||
serverLog(LL_WARNING, "MAXMEMORY_POLICY: %X", g_pserver->maxmemory_policy);
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU)
|
||||
{
|
||||
*lfu_freq = LFUDecrAndReturn(key->value);
|
||||
serverLog(LL_WARNING, "lfu_freq: %lld", lfu_freq);
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
@ -175,7 +175,11 @@ void execCommand(client *c) {
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN)) && !(FInReplicaReplay())) {
|
||||
if (!must_propagate &&
|
||||
!g_pserver->loading &&
|
||||
!(c->cmd->flags & (CMD_READONLY|CMD_ADMIN)) &&
|
||||
!(FInReplicaReplay()))
|
||||
{
|
||||
execCommandPropagateMulti(c);
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ int listMatchObjects(void *a, void *b) {
|
||||
/* This function links the client to the global linked list of clients.
|
||||
* unlinkClient() does the opposite, among other things. */
|
||||
void linkClient(client *c) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
listAddNodeTail(g_pserver->clients,c);
|
||||
/* Note that we remember the linked list node where the client is stored,
|
||||
* this way removing the client in unlinkClient() will not require
|
||||
@ -179,6 +180,8 @@ client *createClient(connection *conn, int iel) {
|
||||
memset(c->uuid, 0, UUID_BINARY_LEN);
|
||||
|
||||
c->client_tracking_prefixes = NULL;
|
||||
c->client_cron_last_memory_usage = 0;
|
||||
c->client_cron_last_memory_type = CLIENT_TYPE_NORMAL;
|
||||
c->auth_callback = NULL;
|
||||
c->auth_callback_privdata = NULL;
|
||||
c->auth_module = NULL;
|
||||
@ -507,12 +510,35 @@ void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync)
|
||||
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
|
||||
"to its %s: '%s' after processing the command "
|
||||
"'%s'", from, to, s, cmdname);
|
||||
if (ctype == CLIENT_TYPE_MASTER && g_pserver->repl_backlog &&
|
||||
g_pserver->repl_backlog_histlen > 0)
|
||||
{
|
||||
long long dumplen = 256;
|
||||
if (g_pserver->repl_backlog_histlen < dumplen)
|
||||
dumplen = g_pserver->repl_backlog_histlen;
|
||||
|
||||
if (c->querybuf && sdslen(c->querybuf)) {
|
||||
std::string str = escapeString(c->querybuf);
|
||||
printf("\tquerybuf: %s\n", str.c_str());
|
||||
/* Identify the first byte to dump. */
|
||||
long long idx =
|
||||
(g_pserver->repl_backlog_idx + (g_pserver->repl_backlog_size - dumplen)) %
|
||||
g_pserver->repl_backlog_size;
|
||||
|
||||
/* Scan the circular buffer to collect 'dumplen' bytes. */
|
||||
sds dump = sdsempty();
|
||||
while(dumplen) {
|
||||
long long thislen =
|
||||
((g_pserver->repl_backlog_size - idx) < dumplen) ?
|
||||
(g_pserver->repl_backlog_size - idx) : dumplen;
|
||||
|
||||
dump = sdscatrepr(dump,g_pserver->repl_backlog+idx,thislen);
|
||||
dumplen -= thislen;
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
/* Finally log such bytes: this is vital debugging info to
|
||||
* understand what happened. */
|
||||
serverLog(LL_WARNING,"Latest backlog is: '%s'", dump);
|
||||
sdsfree(dump);
|
||||
}
|
||||
|
||||
g_pserver->stat_unexpected_error_replies++;
|
||||
}
|
||||
}
|
||||
@ -565,6 +591,34 @@ void addReplyStatusFormat(client *c, const char *fmt, ...) {
|
||||
sdsfree(s);
|
||||
}
|
||||
|
||||
/* Sometimes we are forced to create a new reply node, and we can't append to
|
||||
* the previous one, when that happens, we wanna try to trim the unused space
|
||||
* at the end of the last reply node which we won't use anymore. */
|
||||
void trimReplyUnusedTailSpace(client *c) {
|
||||
listNode *ln = listLast(c->reply);
|
||||
clientReplyBlock *tail = ln? (clientReplyBlock*)listNodeValue(ln): NULL;
|
||||
|
||||
/* Note that 'tail' may be NULL even if we have a tail node, becuase when
|
||||
* addDeferredMultiBulkLength() is used */
|
||||
if (!tail) return;
|
||||
|
||||
/* We only try to trim the space is relatively high (more than a 1/4 of the
|
||||
* allocation), otherwise there's a high chance realloc will NOP.
|
||||
* Also, to avoid large memmove which happens as part of realloc, we only do
|
||||
* that if the used part is small. */
|
||||
if (tail->size - tail->used > tail->size / 4 &&
|
||||
tail->used < PROTO_REPLY_CHUNK_BYTES)
|
||||
{
|
||||
size_t old_size = tail->size;
|
||||
tail = (clientReplyBlock*)zrealloc(tail, tail->used + sizeof(clientReplyBlock));
|
||||
/* take over the allocation's internal fragmentation (at least for
|
||||
* memory usage tracking) */
|
||||
tail->size = zmalloc_usable(tail) - sizeof(clientReplyBlock);
|
||||
c->reply_bytes = c->reply_bytes + tail->size - old_size;
|
||||
listNodeValue(ln) = tail;
|
||||
}
|
||||
}
|
||||
|
||||
/* Adds an empty object to the reply list that will contain the multi bulk
|
||||
* length, which is not known when this function is called. */
|
||||
void *addReplyDeferredLen(client *c) {
|
||||
@ -572,6 +626,7 @@ void *addReplyDeferredLen(client *c) {
|
||||
* ready to be sent, since we are sure that before returning to the
|
||||
* event loop setDeferredAggregateLen() will be called. */
|
||||
if (prepareClientToWrite(c, false) != C_OK) return NULL;
|
||||
trimReplyUnusedTailSpace(c);
|
||||
listAddNodeTail(c->reply,NULL); /* NULL is our placeholder. */
|
||||
return listLast(c->reply);
|
||||
}
|
||||
@ -1231,11 +1286,60 @@ static void acceptCommonHandler(connection *conn, int flags, char *ip, int iel)
|
||||
}
|
||||
}
|
||||
|
||||
void acceptOnThread(connection *conn, int flags, char *cip)
|
||||
{
|
||||
int ielCur = ielFromEventLoop(serverTL->el);
|
||||
|
||||
int ielTarget = 0;
|
||||
if (g_pserver->loading)
|
||||
{
|
||||
ielTarget = IDX_EVENT_LOOP_MAIN; // During load only the main thread is active
|
||||
}
|
||||
else if (g_fTestMode)
|
||||
{
|
||||
// On test mode we don't want any bunching of clients
|
||||
while (cserver.cthreads > 1 && ielTarget == IDX_EVENT_LOOP_MAIN)
|
||||
ielTarget = rand() % cserver.cthreads;
|
||||
}
|
||||
else
|
||||
{
|
||||
ielTarget = chooseBestThreadForAccept();
|
||||
}
|
||||
|
||||
rgacceptsInFlight[ielTarget].fetch_add(1, std::memory_order_relaxed);
|
||||
if (ielTarget != ielCur)
|
||||
{
|
||||
char *szT = nullptr;
|
||||
if (cip != nullptr)
|
||||
{
|
||||
szT = (char*)zmalloc(NET_IP_STR_LEN, MALLOC_LOCAL);
|
||||
memcpy(szT, cip, NET_IP_STR_LEN);
|
||||
}
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[ielTarget].el, [conn, flags, ielTarget, szT]{
|
||||
acceptCommonHandler(conn,flags,szT,ielTarget);
|
||||
if (!g_fTestMode && !g_pserver->loading)
|
||||
rgacceptsInFlight[ielTarget].fetch_sub(1, std::memory_order_relaxed);
|
||||
zfree(szT);
|
||||
});
|
||||
|
||||
if (res == AE_OK)
|
||||
return;
|
||||
// If res != AE_OK we can still try to accept on the local thread
|
||||
}
|
||||
if (!g_fTestMode && !g_pserver->loading)
|
||||
rgacceptsInFlight[ielTarget].fetch_sub(1, std::memory_order_relaxed);
|
||||
|
||||
aeAcquireLock();
|
||||
acceptCommonHandler(conn,flags,cip,ielCur);
|
||||
aeReleaseLock();
|
||||
}
|
||||
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
|
||||
char cip[NET_IP_STR_LEN];
|
||||
UNUSED(mask);
|
||||
UNUSED(privdata);
|
||||
UNUSED(el);
|
||||
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(serverTL->neterr, fd, cip, sizeof(cip), &cport);
|
||||
@ -1246,55 +1350,8 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
int ielCur = ielFromEventLoop(el);
|
||||
|
||||
if (!g_fTestMode)
|
||||
{
|
||||
{
|
||||
int ielTarget = chooseBestThreadForAccept();
|
||||
rgacceptsInFlight[ielTarget].fetch_add(1, std::memory_order_relaxed);
|
||||
if (ielTarget != ielCur)
|
||||
{
|
||||
char *szT = (char*)zmalloc(NET_IP_STR_LEN, MALLOC_LOCAL);
|
||||
memcpy(szT, cip, NET_IP_STR_LEN);
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[ielTarget].el, [cfd, ielTarget, szT]{
|
||||
acceptCommonHandler(connCreateAcceptedSocket(cfd),0,szT,ielTarget);
|
||||
rgacceptsInFlight[ielTarget].fetch_sub(1, std::memory_order_relaxed);
|
||||
zfree(szT);
|
||||
});
|
||||
|
||||
if (res == AE_OK)
|
||||
continue;
|
||||
}
|
||||
rgacceptsInFlight[ielTarget].fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
LLocalThread:
|
||||
aeAcquireLock();
|
||||
acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip,ielCur);
|
||||
aeReleaseLock();
|
||||
}
|
||||
else
|
||||
{
|
||||
// In test mode we want a good distribution among threads and avoid the main thread
|
||||
// since the main thread is most likely to work
|
||||
int iel = IDX_EVENT_LOOP_MAIN;
|
||||
while (cserver.cthreads > 1 && iel == IDX_EVENT_LOOP_MAIN)
|
||||
iel = rand() % cserver.cthreads;
|
||||
if (iel == ielFromEventLoop(el))
|
||||
goto LLocalThread;
|
||||
char *szT = (char*)zmalloc(NET_IP_STR_LEN, MALLOC_LOCAL);
|
||||
memcpy(szT, cip, NET_IP_STR_LEN);
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[iel].el, [cfd, iel, szT]{
|
||||
acceptCommonHandler(connCreateAcceptedSocket(cfd),0,szT,iel);
|
||||
zfree(szT);
|
||||
});
|
||||
if (res != AE_OK)
|
||||
{
|
||||
zfree(szT);
|
||||
goto LLocalThread;
|
||||
}
|
||||
}
|
||||
acceptOnThread(connCreateAcceptedSocket(cfd), 0, cip);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1305,7 +1362,6 @@ void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
UNUSED(mask);
|
||||
UNUSED(privdata);
|
||||
|
||||
int ielCur = ielFromEventLoop(el);
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(serverTL->neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
@ -1315,9 +1371,8 @@ void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
aeAcquireLock();
|
||||
acceptCommonHandler(connCreateAcceptedTLS(cfd, g_pserver->tls_auth_clients),0,cip,ielCur);
|
||||
aeReleaseLock();
|
||||
|
||||
acceptOnThread(connCreateAcceptedTLS(cfd, g_pserver->tls_auth_clients), 0, cip);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1327,7 +1382,6 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
UNUSED(mask);
|
||||
UNUSED(privdata);
|
||||
|
||||
int iel = ielFromEventLoop(el);
|
||||
while(max--) {
|
||||
cfd = anetUnixAccept(serverTL->neterr, fd);
|
||||
if (cfd == ANET_ERR) {
|
||||
@ -1338,23 +1392,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
serverLog(LL_VERBOSE,"Accepted connection to %s", g_pserver->unixsocket);
|
||||
|
||||
aeAcquireLock();
|
||||
int ielTarget = rand() % cserver.cthreads;
|
||||
if (ielTarget == iel)
|
||||
{
|
||||
LLocalThread:
|
||||
acceptCommonHandler(connCreateAcceptedSocket(cfd),CLIENT_UNIX_SOCKET,NULL,iel);
|
||||
}
|
||||
else
|
||||
{
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[ielTarget].el, [cfd, ielTarget]{
|
||||
acceptCommonHandler(connCreateAcceptedSocket(cfd),CLIENT_UNIX_SOCKET,NULL,ielTarget);
|
||||
});
|
||||
if (res != AE_OK)
|
||||
goto LLocalThread;
|
||||
}
|
||||
aeReleaseLock();
|
||||
|
||||
acceptOnThread(connCreateAcceptedSocket(cfd),CLIENT_UNIX_SOCKET,NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1494,6 +1532,16 @@ bool freeClient(client *c) {
|
||||
/* Notify module system that this client auth status changed. */
|
||||
moduleNotifyUserChanged(c);
|
||||
|
||||
/* If this client was scheduled for async freeing we need to remove it
|
||||
* from the queue. Note that we need to do this here, because later
|
||||
* we may call replicationCacheMaster() and the client should already
|
||||
* be removed from the list of clients to free. */
|
||||
if (c->flags & CLIENT_CLOSE_ASAP) {
|
||||
ln = listSearchKey(g_pserver->clients_to_close,c);
|
||||
serverAssert(ln != NULL);
|
||||
listDelNode(g_pserver->clients_to_close,ln);
|
||||
}
|
||||
|
||||
/* If it is our master that's beging disconnected we should make sure
|
||||
* to cache the state to try a partial resynchronization later.
|
||||
*
|
||||
@ -1501,10 +1549,8 @@ bool freeClient(client *c) {
|
||||
* some unexpected state, by checking its flags. */
|
||||
if (FActiveMaster(c)) {
|
||||
serverLog(LL_WARNING,"Connection with master lost.");
|
||||
if (!(c->flags & (CLIENT_CLOSE_AFTER_REPLY|
|
||||
CLIENT_CLOSE_ASAP|
|
||||
CLIENT_BLOCKED)))
|
||||
{
|
||||
if (!(c->flags & (CLIENT_PROTOCOL_ERROR|CLIENT_BLOCKED))) {
|
||||
c->flags &= ~(CLIENT_CLOSE_ASAP|CLIENT_CLOSE_AFTER_REPLY);
|
||||
replicationCacheMaster(MasterInfoFromClient(c), c);
|
||||
return false;
|
||||
}
|
||||
@ -1576,13 +1622,10 @@ bool freeClient(client *c) {
|
||||
* we lost the connection with the master. */
|
||||
if (c->flags & CLIENT_MASTER) replicationHandleMasterDisconnection(MasterInfoFromClient(c));
|
||||
|
||||
/* If this client was scheduled for async freeing we need to remove it
|
||||
* from the queue. */
|
||||
if (c->flags & CLIENT_CLOSE_ASAP) {
|
||||
ln = listSearchKey(g_pserver->clients_to_close,c);
|
||||
serverAssert(ln != NULL);
|
||||
listDelNode(g_pserver->clients_to_close,ln);
|
||||
}
|
||||
/* Remove the contribution that this client gave to our
|
||||
* incrementally computed memory usage. */
|
||||
g_pserver->stat_clients_type_memory[c->client_cron_last_memory_type] -=
|
||||
c->client_cron_last_memory_usage;
|
||||
|
||||
/* Release other dynamically allocated client structure fields,
|
||||
* and finally release the client structure itself. */
|
||||
@ -1604,7 +1647,7 @@ fastlock lockasyncfree {"async free lock"};
|
||||
* a context where calling freeClient() is not possible, because the client
|
||||
* should be valid for the continuation of the flow of the program. */
|
||||
void freeClientAsync(client *c) {
|
||||
/* We need to handle concurrent access to the server.clients_to_close list
|
||||
/* We need to handle concurrent access to the g_pserver->clients_to_close list
|
||||
* only in the freeClientAsync() function, since it's the only function that
|
||||
* may access the list while Redis uses I/O threads. All the other accesses
|
||||
* are in the context of the main thread while the other threads are
|
||||
@ -1619,7 +1662,7 @@ void freeClientAsync(client *c) {
|
||||
listAddNodeTail(g_pserver->clients_to_close,c);
|
||||
}
|
||||
|
||||
void freeClientsInAsyncFreeQueue(int iel) {
|
||||
int freeClientsInAsyncFreeQueue(int iel) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
@ -1644,6 +1687,7 @@ void freeClientsInAsyncFreeQueue(int iel) {
|
||||
c->flags &= ~CLIENT_CLOSE_ASAP;
|
||||
freeClient(c);
|
||||
}
|
||||
return (int)vecclientsFree.size();
|
||||
}
|
||||
|
||||
/* Return a client by ID, or NULL if the client ID is not in the set
|
||||
@ -1921,12 +1965,9 @@ int handleClientsWithPendingWrites(int iel, int aof_state) {
|
||||
}
|
||||
}
|
||||
|
||||
if (listLength(serverTL->clients_pending_asyncwrite))
|
||||
{
|
||||
AeLocker locker;
|
||||
locker.arm(nullptr);
|
||||
ProcessPendingAsyncWrites();
|
||||
}
|
||||
AeLocker locker;
|
||||
locker.arm(nullptr);
|
||||
ProcessPendingAsyncWrites();
|
||||
|
||||
return processed;
|
||||
}
|
||||
@ -2056,7 +2097,8 @@ int processInlineBuffer(client *c) {
|
||||
}
|
||||
|
||||
/* Helper function. Record protocol erro details in server log,
|
||||
* and set the client as CLIENT_CLOSE_AFTER_REPLY. */
|
||||
* and set the client as CLIENT_CLOSE_AFTER_REPLY and
|
||||
* CLIENT_PROTOCOL_ERROR. */
|
||||
#define PROTO_DUMP_LEN 128
|
||||
static void setProtocolError(const char *errstr, client *c) {
|
||||
if (cserver.verbosity <= LL_VERBOSE) {
|
||||
@ -2082,7 +2124,7 @@ static void setProtocolError(const char *errstr, client *c) {
|
||||
"Protocol error (%s) from client: %s. %s", errstr, client, buf);
|
||||
sdsfree(client);
|
||||
}
|
||||
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||
c->flags |= (CLIENT_CLOSE_AFTER_REPLY|CLIENT_PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
/* Process the query buffer for client 'c', setting up the client argument
|
||||
@ -2231,33 +2273,69 @@ int processMultibulkBuffer(client *c) {
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Perform necessary tasks after a command was executed:
|
||||
*
|
||||
* 1. The client is reset unless there are reasons to avoid doing it.
|
||||
* 2. In the case of master clients, the replication offset is updated.
|
||||
* 3. Propagate commands we got from our master to replicas down the line. */
|
||||
void commandProcessed(client *c) {
|
||||
int cmd_is_ping = c->cmd && c->cmd->proc == pingCommand;
|
||||
long long prev_offset = c->reploff;
|
||||
if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
|
||||
/* Update the applied replication offset of our master. */
|
||||
c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos;
|
||||
}
|
||||
|
||||
/* Don't reset the client structure for clients blocked in a
|
||||
* module blocking command, so that the reply callback will
|
||||
* still be able to access the client argv and argc field.
|
||||
* The client will be reset in unblockClientFromModule(). */
|
||||
if (!(c->flags & CLIENT_BLOCKED) ||
|
||||
c->btype != BLOCKED_MODULE)
|
||||
{
|
||||
resetClient(c);
|
||||
}
|
||||
|
||||
/* If the client is a master we need to compute the difference
|
||||
* between the applied offset before and after processing the buffer,
|
||||
* to understand how much of the replication stream was actually
|
||||
* applied to the master state: this quantity, and its corresponding
|
||||
* part of the replication stream, will be propagated to the
|
||||
* sub-replicas and to the replication backlog. */
|
||||
if (c->flags & CLIENT_MASTER) {
|
||||
AeLocker ae;
|
||||
ae.arm(c);
|
||||
long long applied = c->reploff - prev_offset;
|
||||
long long prev_master_repl_meaningful_offset = g_pserver->master_repl_meaningful_offset;
|
||||
if (applied) {
|
||||
if (!g_pserver->fActiveReplica)
|
||||
{
|
||||
replicationFeedSlavesFromMasterStream(g_pserver->slaves,
|
||||
c->pending_querybuf, applied);
|
||||
}
|
||||
sdsrange(c->pending_querybuf,applied,-1);
|
||||
}
|
||||
/* The g_pserver->master_repl_meaningful_offset variable represents
|
||||
* the offset of the replication stream without the pending PINGs. */
|
||||
if (cmd_is_ping)
|
||||
g_pserver->master_repl_meaningful_offset = prev_master_repl_meaningful_offset;
|
||||
}
|
||||
}
|
||||
|
||||
/* This function calls processCommand(), but also performs a few sub tasks
|
||||
* that are useful in that context:
|
||||
* for the client that are useful in that context:
|
||||
*
|
||||
* 1. It sets the current client to the client 'c'.
|
||||
* 2. In the case of master clients, the replication offset is updated.
|
||||
* 3. The client is reset unless there are reasons to avoid doing it.
|
||||
* 2. calls commandProcessed() if the command was handled.
|
||||
*
|
||||
* The function returns C_ERR in case the client was freed as a side effect
|
||||
* of processing the command, otherwise C_OK is returned. */
|
||||
int processCommandAndResetClient(client *c, int flags) {
|
||||
int deadclient = 0;
|
||||
serverTL->current_client = c;
|
||||
if (processCommand(c, flags) == C_OK) {
|
||||
if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
|
||||
/* Update the applied replication offset of our master. */
|
||||
c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos;
|
||||
}
|
||||
|
||||
/* Don't reset the client structure for clients blocked in a
|
||||
* module blocking command, so that the reply callback will
|
||||
* still be able to access the client argv and argc field.
|
||||
* The client will be reset in unblockClientFromModule(). */
|
||||
if (!(c->flags & CLIENT_BLOCKED) ||
|
||||
c->btype != BLOCKED_MODULE)
|
||||
{
|
||||
resetClient(c);
|
||||
}
|
||||
AeLocker locker;
|
||||
if (processCommand(c, flags, locker) == C_OK) {
|
||||
commandProcessed(c);
|
||||
}
|
||||
if (serverTL->current_client == NULL) deadclient = 1;
|
||||
serverTL->current_client = NULL;
|
||||
@ -2333,36 +2411,6 @@ void processInputBuffer(client *c, int callFlags) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a wrapper for processInputBuffer that also cares about handling
|
||||
* the replication forwarding to the sub-replicas, in case the client 'c'
|
||||
* is flagged as master. Usually you want to call this instead of the
|
||||
* raw processInputBuffer(). */
|
||||
void processInputBufferAndReplicate(client *c) {
|
||||
if (!(c->flags & CLIENT_MASTER)) {
|
||||
processInputBuffer(c, CMD_CALL_FULL);
|
||||
} else {
|
||||
/* If the client is a master we need to compute the difference
|
||||
* between the applied offset before and after processing the buffer,
|
||||
* to understand how much of the replication stream was actually
|
||||
* applied to the master state: this quantity, and its corresponding
|
||||
* part of the replication stream, will be propagated to the
|
||||
* sub-replicas and to the replication backlog. */
|
||||
size_t prev_offset = c->reploff;
|
||||
processInputBuffer(c, CMD_CALL_FULL);
|
||||
size_t applied = c->reploff - prev_offset;
|
||||
if (applied) {
|
||||
if (!g_pserver->fActiveReplica)
|
||||
{
|
||||
AeLocker ae;
|
||||
ae.arm(c);
|
||||
replicationFeedSlavesFromMasterStream(g_pserver->slaves,
|
||||
c->pending_querybuf, applied);
|
||||
}
|
||||
sdsrange(c->pending_querybuf,applied,-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void readQueryFromClient(connection *conn) {
|
||||
client *c = (client*)connGetPrivateData(conn);
|
||||
serverAssert(conn == c->conn);
|
||||
@ -2438,13 +2486,9 @@ void readQueryFromClient(connection *conn) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Time to process the buffer. If the client is a master we need to
|
||||
* compute the difference between the applied offset before and after
|
||||
* processing the buffer, to understand how much of the replication stream
|
||||
* was actually applied to the master state: this quantity, and its
|
||||
* corresponding part of the replication stream, will be propagated to
|
||||
* the sub-slaves and to the replication backlog. */
|
||||
processInputBufferAndReplicate(c);
|
||||
/* There is more data in the client input buffer, continue parsing it
|
||||
* in case to check if there is a full command to execute. */
|
||||
processInputBuffer(c, CMD_CALL_FULL);
|
||||
if (listLength(serverTL->clients_pending_asyncwrite))
|
||||
{
|
||||
aelock.arm(c);
|
||||
@ -2629,6 +2673,7 @@ void clientCommand(client *c) {
|
||||
"KILL <option> <value> [option value ...] -- Kill connections. Options are:",
|
||||
" ADDR <ip:port> -- Kill connection made from <ip:port>",
|
||||
" TYPE (normal|master|replica|pubsub) -- Kill connections by type.",
|
||||
" USER <username> -- Kill connections authenticated with such user.",
|
||||
" SKIPME (yes|no) -- Skip killing current connection (default: yes).",
|
||||
"LIST [options ...] -- Return information about client connections. Options:",
|
||||
" TYPE (normal|master|replica|pubsub) -- Return clients of specified type.",
|
||||
@ -2679,6 +2724,7 @@ NULL
|
||||
/* CLIENT KILL <ip:port>
|
||||
* CLIENT KILL <option> [value] ... <option> [value] */
|
||||
char *addr = NULL;
|
||||
user *user = NULL;
|
||||
int type = -1;
|
||||
uint64_t id = 0;
|
||||
int skipme = 1;
|
||||
@ -2708,10 +2754,18 @@ NULL
|
||||
(char*) ptrFromObj(c->argv[i+1]));
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp((const char*)ptrFromObj(c->argv[i]),"addr") && moreargs) {
|
||||
addr = (char*)ptrFromObj(c->argv[i+1]);
|
||||
} else if (!strcasecmp((const char*)ptrFromObj(c->argv[i]),"skipme") && moreargs) {
|
||||
if (!strcasecmp((const char*)ptrFromObj(c->argv[i+1]),"yes")) {
|
||||
} else if (!strcasecmp(szFromObj(c->argv[i]),"addr") && moreargs) {
|
||||
addr = szFromObj(c->argv[i+1]);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[i]),"user") && moreargs) {
|
||||
user = ACLGetUserByName(szFromObj(c->argv[i+1]),
|
||||
sdslen(szFromObj(c->argv[i+1])));
|
||||
if (user == NULL) {
|
||||
addReplyErrorFormat(c,"No such user '%s'",
|
||||
szFromObj(c->argv[i+1]));
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(szFromObj(c->argv[i]),"skipme") && moreargs) {
|
||||
if (!strcasecmp(szFromObj(c->argv[i+1]),"yes")) {
|
||||
skipme = 1;
|
||||
} else if (!strcasecmp((const char*)ptrFromObj(c->argv[i+1]),"no")) {
|
||||
skipme = 0;
|
||||
@ -2737,6 +2791,7 @@ NULL
|
||||
if (addr && strcmp(getClientPeerId(client),addr) != 0) continue;
|
||||
if (type != -1 && getClientType(client) != type) continue;
|
||||
if (id != 0 && client->id != id) continue;
|
||||
if (user && client->puser != user) continue;
|
||||
if (c == client && skipme) continue;
|
||||
|
||||
/* Kill it. */
|
||||
@ -2865,6 +2920,8 @@ NULL
|
||||
options |= CLIENT_TRACKING_OPTIN;
|
||||
} else if (!strcasecmp(szFromObj(c->argv[j]),"optout")) {
|
||||
options |= CLIENT_TRACKING_OPTOUT;
|
||||
} else if (!strcasecmp(szFromObj(c->argv[j]),"noloop")) {
|
||||
options |= CLIENT_TRACKING_NOLOOP;
|
||||
} else if (!strcasecmp(szFromObj(c->argv[j]),"prefix") && moreargs) {
|
||||
j++;
|
||||
prefix = (robj**)zrealloc(prefix,sizeof(robj*)*(numprefix+1), MALLOC_LOCAL);
|
||||
@ -2909,6 +2966,25 @@ NULL
|
||||
return;
|
||||
}
|
||||
|
||||
if (options & CLIENT_TRACKING_OPTIN && options & CLIENT_TRACKING_OPTOUT)
|
||||
{
|
||||
addReplyError(c,
|
||||
"You can't specify both OPTIN mode and OPTOUT mode");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((options & CLIENT_TRACKING_OPTIN && c->flags & CLIENT_TRACKING_OPTOUT) ||
|
||||
(options & CLIENT_TRACKING_OPTOUT && c->flags & CLIENT_TRACKING_OPTIN))
|
||||
{
|
||||
addReplyError(c,
|
||||
"You can't switch OPTIN/OPTOUT mode before disabling "
|
||||
"tracking for this client, and then re-enabling it with "
|
||||
"a different mode.");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
enableTracking(c,redir,options,prefix,numprefix);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[2]),"off")) {
|
||||
disableTracking(c);
|
||||
@ -3001,7 +3077,7 @@ void helloCommand(client *c) {
|
||||
|
||||
/* Let's switch to the specified RESP mode. */
|
||||
c->resp = ver;
|
||||
addReplyMapLen(c,7);
|
||||
addReplyMapLen(c,6 + !g_pserver->sentinel_mode);
|
||||
|
||||
addReplyBulkCString(c,"server");
|
||||
addReplyBulkCString(c,"redis");
|
||||
@ -3017,7 +3093,7 @@ void helloCommand(client *c) {
|
||||
|
||||
addReplyBulkCString(c,"mode");
|
||||
if (g_pserver->sentinel_mode) addReplyBulkCString(c,"sentinel");
|
||||
if (g_pserver->cluster_enabled) addReplyBulkCString(c,"cluster");
|
||||
else if (g_pserver->cluster_enabled) addReplyBulkCString(c,"cluster");
|
||||
else addReplyBulkCString(c,"standalone");
|
||||
|
||||
if (!g_pserver->sentinel_mode) {
|
||||
@ -3352,10 +3428,9 @@ void unpauseClientsIfNecessary()
|
||||
* write, close sequence needed to serve a client.
|
||||
*
|
||||
* The function returns the total number of events processed. */
|
||||
int processEventsWhileBlocked(int iel) {
|
||||
void processEventsWhileBlocked(int iel) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
int iterations = 4; /* See the function top-comment. */
|
||||
int count = 0;
|
||||
|
||||
std::vector<client*> vecclients;
|
||||
listIter li;
|
||||
@ -3375,17 +3450,20 @@ int processEventsWhileBlocked(int iel) {
|
||||
}
|
||||
|
||||
|
||||
int aof_state = g_pserver->aof_state;
|
||||
aeReleaseLock();
|
||||
serverAssertDebug(!GlobalLocksAcquired());
|
||||
try
|
||||
{
|
||||
while (iterations--) {
|
||||
int events = 0;
|
||||
events += aeProcessEvents(g_pserver->rgthreadvar[iel].el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
events += handleClientsWithPendingWrites(iel, aof_state);
|
||||
long long startval = g_pserver->events_processed_while_blocked;
|
||||
long long ae_events = aeProcessEvents(g_pserver->rgthreadvar[iel].el,
|
||||
AE_FILE_EVENTS|AE_DONT_WAIT|
|
||||
AE_CALL_BEFORE_SLEEP|AE_CALL_AFTER_SLEEP);
|
||||
/* Note that server.events_processed_while_blocked will also get
|
||||
* incremeted by callbacks called by the event loop handlers. */
|
||||
g_pserver->events_processed_while_blocked += ae_events;
|
||||
long long events = g_pserver->events_processed_while_blocked - startval;
|
||||
if (!events) break;
|
||||
count += events;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
@ -3402,8 +3480,12 @@ int processEventsWhileBlocked(int iel) {
|
||||
AeLocker locker;
|
||||
locker.arm(nullptr);
|
||||
locker.release();
|
||||
|
||||
for (client *c : vecclients)
|
||||
c->lock.lock();
|
||||
return count;
|
||||
|
||||
// If a different thread processed the shutdown we need to abort the lua command or we will hang
|
||||
if (serverTL->el->stop)
|
||||
throw ShutdownException();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <cstddef> // std::size_t
|
||||
#include "server.h"
|
||||
#include "new.h"
|
||||
#include <new>
|
||||
|
||||
#ifdef SANITIZE
|
||||
void *operator new(size_t size, enum MALLOC_CLASS mclass)
|
||||
@ -21,6 +22,11 @@ void *operator new(size_t size, enum MALLOC_CLASS mclass)
|
||||
return zmalloc(size, mclass);
|
||||
}
|
||||
|
||||
void *operator new(std::size_t size, const std::nothrow_t &) noexcept
|
||||
{
|
||||
return zmalloc(size, MALLOC_LOCAL);
|
||||
}
|
||||
|
||||
void operator delete(void * p) noexcept
|
||||
{
|
||||
zfree(p);
|
||||
@ -38,4 +44,4 @@ extern "C" size_t malloc_usable_size(void *ptr)
|
||||
{
|
||||
return zmalloc_usable(ptr);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@ -362,7 +362,16 @@ void freeStreamObject(robj_roptr o) {
|
||||
}
|
||||
|
||||
void incrRefCount(robj_roptr o) {
|
||||
if (o->getrefcount(std::memory_order_relaxed) != OBJ_SHARED_REFCOUNT) o->addref();
|
||||
auto refcount = o->getrefcount(std::memory_order_relaxed);
|
||||
if (refcount < OBJ_FIRST_SPECIAL_REFCOUNT) {
|
||||
o->addref();
|
||||
} else {
|
||||
if (refcount == OBJ_SHARED_REFCOUNT) {
|
||||
/* Nothing to do: this refcount is immutable. */
|
||||
} else if (refcount == OBJ_STATIC_REFCOUNT) {
|
||||
serverPanic("You tried to retain an object allocated in the stack");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decrRefCount(robj_roptr o) {
|
||||
@ -1025,32 +1034,15 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
mh->repl_backlog = mem;
|
||||
mem_total += mem;
|
||||
|
||||
mem = 0;
|
||||
if (listLength(g_pserver->clients)) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
size_t mem_normal = 0, mem_slaves = 0;
|
||||
|
||||
listRewind(g_pserver->clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
size_t mem_curr = 0;
|
||||
client *c = (client*)listNodeValue(ln);
|
||||
std::unique_lock<fastlock> ul(c->lock);
|
||||
|
||||
int type = getClientType(c);
|
||||
mem_curr += getClientOutputBufferMemoryUsage(c);
|
||||
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;
|
||||
}
|
||||
mem_total+=mem;
|
||||
/* Computing the memory used by the clients would be O(N) if done
|
||||
* here online. We use our values computed incrementally by
|
||||
* clientsCronTrackClientsMemUsage(). */
|
||||
mh->clients_slaves = g_pserver->stat_clients_type_memory[CLIENT_TYPE_SLAVE];
|
||||
mh->clients_normal = g_pserver->stat_clients_type_memory[CLIENT_TYPE_MASTER]+
|
||||
g_pserver->stat_clients_type_memory[CLIENT_TYPE_PUBSUB]+
|
||||
g_pserver->stat_clients_type_memory[CLIENT_TYPE_NORMAL];
|
||||
mem_total += mh->clients_slaves;
|
||||
mem_total += mh->clients_normal;
|
||||
|
||||
mem = 0;
|
||||
if (g_pserver->aof_state != AOF_OFF) {
|
||||
|
@ -110,7 +110,7 @@ quicklist *quicklistCreate(void) {
|
||||
return quicklist;
|
||||
}
|
||||
|
||||
#define COMPRESS_MAX (1 << QL_COMP_BITS)
|
||||
#define COMPRESS_MAX ((1 << QL_COMP_BITS)-1)
|
||||
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
if (compress > COMPRESS_MAX) {
|
||||
compress = COMPRESS_MAX;
|
||||
@ -120,7 +120,7 @@ void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
quicklist->compress = compress;
|
||||
}
|
||||
|
||||
#define FILL_MAX (1 << (QL_FILL_BITS-1))
|
||||
#define FILL_MAX ((1 << (QL_FILL_BITS-1))-1)
|
||||
void quicklistSetFill(quicklist *quicklist, int fill) {
|
||||
if (fill > FILL_MAX) {
|
||||
fill = FILL_MAX;
|
||||
|
@ -1,6 +1,8 @@
|
||||
/* Rax -- A radix tree implementation.
|
||||
*
|
||||
* Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Version 1.2 -- 7 February 2019
|
||||
*
|
||||
* Copyright (c) 2017-2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -1737,7 +1739,7 @@ int raxRandomWalk(raxIterator *it, size_t steps) {
|
||||
}
|
||||
|
||||
if (steps == 0) {
|
||||
size_t fle = floor(log(it->rt->numele));
|
||||
size_t fle = 1+floor(log(it->rt->numele));
|
||||
fle *= 2;
|
||||
steps = 1 + rand() % fle;
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
extern "C" {
|
||||
#include "rio.h"
|
||||
}
|
||||
#include "server.h"
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
104
src/rdb.cpp
104
src/rdb.cpp
@ -1050,8 +1050,8 @@ ssize_t rdbSaveAuxFieldStrInt(rio *rdb, const char *key, long long val) {
|
||||
* the rdbSaveObject() function. Currently we use a trick to get
|
||||
* this length with very little changes to the code. In the future
|
||||
* we could switch to a faster solution. */
|
||||
size_t rdbSavedObjectLen(robj *o) {
|
||||
ssize_t len = rdbSaveObject(NULL,o,NULL);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key) {
|
||||
ssize_t len = rdbSaveObject(NULL,o,key);
|
||||
serverAssertWithInfo(NULL,o,len != -1);
|
||||
return len;
|
||||
}
|
||||
@ -1238,10 +1238,7 @@ int rdbSaveRio(rio *rdb, const redisDbPersistentDataSnapshot **rgpdb, int *error
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
|
||||
if (rdbSaveLen(rdb,j) == -1) goto werr;
|
||||
|
||||
/* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
|
||||
* is currently the largest type we are able to represent in RDB sizes.
|
||||
* However this does not limit the actual size of the DB to load since
|
||||
* these sizes are just hints to resize the hash tables. */
|
||||
/* Write the RESIZE DB opcode. */
|
||||
uint64_t db_size, expires_size;
|
||||
db_size = db->size();
|
||||
expires_size = db->expireSize();
|
||||
@ -1587,7 +1584,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, uint64_t mvcc_tstamp) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
@ -1606,7 +1603,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
|
||||
/* Load every single element of the list */
|
||||
while(len--) {
|
||||
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
|
||||
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
dec = getDecodedObject(ele);
|
||||
size_t len = sdslen(szFromObj(dec));
|
||||
quicklistPushTail((quicklist*)ptrFromObj(o), ptrFromObj(dec), len);
|
||||
@ -1633,8 +1633,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
long long llval;
|
||||
sds sdsele;
|
||||
|
||||
if ((sdsele = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL) return NULL;
|
||||
if ((sdsele = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_INTSET) {
|
||||
/* Fetch integer value from element. */
|
||||
@ -1673,16 +1675,20 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
double score;
|
||||
zskiplistNode *znode;
|
||||
|
||||
if ((sdsele = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL) return NULL;
|
||||
if ((sdsele = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (rdbtype == RDB_TYPE_ZSET_2) {
|
||||
if (rdbLoadBinaryDoubleValue(rdb,&score) == -1) {
|
||||
decrRefCount(o);
|
||||
sdsfree(sdsele);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
if (rdbLoadDoubleValue(rdb,&score) == -1) {
|
||||
decrRefCount(o);
|
||||
sdsfree(sdsele);
|
||||
return NULL;
|
||||
}
|
||||
@ -1717,15 +1723,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
while (o->encoding == OBJ_ENCODING_ZIPLIST && len > 0) {
|
||||
len--;
|
||||
/* Load raw strings */
|
||||
if ((field = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL)
|
||||
{
|
||||
if ((field = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
if ((value = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL)
|
||||
{
|
||||
if ((value = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
sdsfree(field);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
@ -1756,15 +1759,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
while (o->encoding == OBJ_ENCODING_HT && len > 0) {
|
||||
len--;
|
||||
/* Load encoded strings */
|
||||
if ((field = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL)
|
||||
{
|
||||
if ((field = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
if ((value = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL)
|
||||
{
|
||||
if ((value = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
sdsfree(field);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
@ -1787,7 +1787,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
while (len--) {
|
||||
unsigned char *zl = (unsigned char*)
|
||||
rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN,NULL);
|
||||
if (zl == NULL) return NULL;
|
||||
if (zl == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
quicklistAppendZiplist((quicklist*)ptrFromObj(o), zl);
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_HASH_ZIPMAP ||
|
||||
@ -2010,8 +2013,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
|
||||
1);
|
||||
streamConsumer *consumer =
|
||||
streamLookupConsumer(cgroup,cname,SLC_NONE);
|
||||
sdsfree(cname);
|
||||
consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
|
||||
if (rioGetReadError(rdb)) {
|
||||
@ -2073,7 +2076,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
exit(1);
|
||||
}
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
moduleInitIOContext(io,mt,rdb,&keyobj);
|
||||
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
@ -2237,7 +2242,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
uint64_t mvcc_tstamp = OBJ_MVCC_INVALID;
|
||||
size_t ckeysLoaded = 0;
|
||||
robj *subexpireKey = nullptr;
|
||||
robj *key = nullptr;
|
||||
sds key = nullptr;
|
||||
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
{
|
||||
@ -2379,10 +2384,12 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
incrRefCount(subexpireKey);
|
||||
} else if (!strcasecmp(szFromObj(auxkey), "keydb-subexpire-when")) {
|
||||
if (key == nullptr || subexpireKey == nullptr) {
|
||||
serverLog(LL_WARNING, "Corrupt subexpire entry in RDB skipping. key: %s subkey: %s", key != nullptr ? szFromObj(key) : "(null)", subexpireKey != nullptr ? szFromObj(subexpireKey) : "(null)");
|
||||
serverLog(LL_WARNING, "Corrupt subexpire entry in RDB skipping. key: %s subkey: %s", key != nullptr ? key : "(null)", subexpireKey != nullptr ? szFromObj(subexpireKey) : "(null)");
|
||||
}
|
||||
else {
|
||||
setExpire(NULL, db, key, subexpireKey, strtoll(szFromObj(auxval), nullptr, 10));
|
||||
redisObject keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
setExpire(NULL, db, &keyobj, subexpireKey, strtoll(szFromObj(auxval), nullptr, 10));
|
||||
decrRefCount(subexpireKey);
|
||||
subexpireKey = nullptr;
|
||||
}
|
||||
@ -2452,14 +2459,15 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
/* Read key */
|
||||
if (key != nullptr)
|
||||
{
|
||||
decrRefCount(key);
|
||||
sdsfree(key);
|
||||
key = nullptr;
|
||||
}
|
||||
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
if ((key = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL)
|
||||
goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb,key, mvcc_tstamp)) == NULL) {
|
||||
decrRefCount(key);
|
||||
if ((val = rdbLoadObject(type,rdb,key,mvcc_tstamp)) == NULL) {
|
||||
sdsfree(key);
|
||||
key = nullptr;
|
||||
goto eoferr;
|
||||
}
|
||||
@ -2470,14 +2478,18 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the replica. */
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
bool fExpiredKey = iAmMaster() && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now;
|
||||
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, &keyobj) == nullptr) {
|
||||
// We have a key that we've already deleted and is not back in our database.
|
||||
// We'll need to inform the sending master of the delete if it is also a replica of us
|
||||
rsi->mi->staleKeyMap->operator[](dbid).push_back(key);
|
||||
robj *objKeyDup = createStringObject(key, sdslen(key));
|
||||
rsi->mi->staleKeyMap->operator[](dbid).push_back(objKeyDup);
|
||||
decrRefCount(objKeyDup);
|
||||
}
|
||||
decrRefCount(key);
|
||||
sdsfree(key);
|
||||
key = nullptr;
|
||||
decrRefCount(val);
|
||||
val = nullptr;
|
||||
@ -2499,7 +2511,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
}
|
||||
|
||||
/* Add the new object in the hash table */
|
||||
int fInserted = dbMerge(db, key, val, rsi && rsi->fForceSetKey); // Note: dbMerge will incrRef
|
||||
int fInserted = dbMerge(db, &keyobj, val, (rsi && rsi->fForceSetKey) || (rdbflags & RDBFLAGS_ALLOW_DUP)); // Note: dbMerge will incrRef
|
||||
|
||||
if (fInserted)
|
||||
{
|
||||
@ -2507,7 +2519,9 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1)
|
||||
setExpire(NULL,db,key,nullptr,expiretime);
|
||||
{
|
||||
setExpire(NULL,db,&keyobj,nullptr,expiretime);
|
||||
}
|
||||
|
||||
/* Set usage information (for eviction). */
|
||||
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock,1000);
|
||||
@ -2518,6 +2532,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
val = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_pserver->key_load_delay)
|
||||
usleep(g_pserver->key_load_delay);
|
||||
|
||||
@ -2530,7 +2545,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
|
||||
if (key != nullptr)
|
||||
{
|
||||
decrRefCount(key);
|
||||
sdsfree(key);
|
||||
key = nullptr;
|
||||
}
|
||||
|
||||
@ -2551,7 +2566,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
if (cksum == 0) {
|
||||
serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
|
||||
} else if (cksum != expected) {
|
||||
serverLog(LL_WARNING,"Wrong RDB checksum. Aborting now.");
|
||||
serverLog(LL_WARNING,"Wrong RDB checksum expected: (%llx) but "
|
||||
"got (%llx). Aborting now.",
|
||||
(unsigned long long)expected,
|
||||
(unsigned long long)cksum);
|
||||
rdbExitReportCorruptRDB("RDB CRC error");
|
||||
}
|
||||
}
|
||||
@ -2571,7 +2589,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
eoferr:
|
||||
if (key != nullptr)
|
||||
{
|
||||
decrRefCount(key);
|
||||
sdsfree(key);
|
||||
key = nullptr;
|
||||
}
|
||||
if (subexpireKey != nullptr)
|
||||
|
11
src/rdb.h
11
src/rdb.h
@ -125,9 +125,10 @@
|
||||
#define RDB_LOAD_SDS (1<<2)
|
||||
|
||||
/* flags on the purpose of rdb save or load */
|
||||
#define RDBFLAGS_NONE 0
|
||||
#define RDBFLAGS_AOF_PREAMBLE (1<<0)
|
||||
#define RDBFLAGS_REPLICATION (1<<1)
|
||||
#define RDBFLAGS_NONE 0 /* No special RDB loading. */
|
||||
#define RDBFLAGS_AOF_PREAMBLE (1<<0) /* Load/save the RDB as AOF preamble. */
|
||||
#define RDBFLAGS_REPLICATION (1<<1) /* Load/save for SYNC. */
|
||||
#define RDBFLAGS_ALLOW_DUP (1<<2) /* Allow duplicated keys when loading.*/
|
||||
|
||||
int rdbSaveType(rio *rdb, unsigned char type);
|
||||
int rdbLoadType(rio *rdb);
|
||||
@ -151,8 +152,8 @@ int rdbSaveFp(FILE *pf, const redisDbPersistentDataSnapshot **rgpdb, rdbSaveInfo
|
||||
int rdbSaveS3(char *path, const redisDbPersistentDataSnapshot **rgpdb, rdbSaveInfo *rsi);
|
||||
int rdbLoadS3(char *path, rdbSaveInfo *rsi, int rdbflags);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj_roptr o, robj_roptr key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key, uint64_t mvcc_tstamp);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key);
|
||||
robj *rdbLoadObject(int type, rio *rdb, sds key, uint64_t mvcc_tstamp);
|
||||
void backgroundSaveDoneHandler(int exitcode, bool fCancelled);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||
|
@ -96,6 +96,7 @@ static struct config {
|
||||
sds dbnumstr;
|
||||
char *tests;
|
||||
char *auth;
|
||||
const char *user;
|
||||
int precision;
|
||||
int num_threads;
|
||||
struct benchmarkThread **threads;
|
||||
@ -264,7 +265,10 @@ static redisConfig *getRedisConfig(const char *ip, int port,
|
||||
|
||||
if(config.auth) {
|
||||
void *authReply = NULL;
|
||||
redisAppendCommand(c, "AUTH %s", config.auth);
|
||||
if (config.user == NULL)
|
||||
redisAppendCommand(c, "AUTH %s", config.auth);
|
||||
else
|
||||
redisAppendCommand(c, "AUTH %s %s", config.user, config.auth);
|
||||
if (REDIS_OK != redisGetReply(c, &authReply)) goto fail;
|
||||
if (reply) freeReplyObject(reply);
|
||||
reply = ((redisReply *) authReply);
|
||||
@ -280,7 +284,7 @@ static redisConfig *getRedisConfig(const char *ip, int port,
|
||||
for (; i < 2; i++) {
|
||||
int res = redisGetReply(c, &r);
|
||||
if (reply) freeReplyObject(reply);
|
||||
reply = ((redisReply *) r);
|
||||
reply = res == REDIS_OK ? ((redisReply *) r) : NULL;
|
||||
if (res != REDIS_OK || !r) goto fail;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "ERROR: %s\n", reply->str);
|
||||
@ -633,7 +637,12 @@ static client createClient(const char *cmd, size_t len, client from, int thread_
|
||||
c->prefix_pending = 0;
|
||||
if (config.auth) {
|
||||
char *buf = NULL;
|
||||
int len = redisFormatCommand(&buf, "AUTH %s", config.auth);
|
||||
int len;
|
||||
if (config.user == NULL)
|
||||
len = redisFormatCommand(&buf, "AUTH %s", config.auth);
|
||||
else
|
||||
len = redisFormatCommand(&buf, "AUTH %s %s",
|
||||
config.user, config.auth);
|
||||
c->obuf = sdscatlen(c->obuf, buf, len);
|
||||
free(buf);
|
||||
c->prefix_pending++;
|
||||
@ -1305,6 +1314,9 @@ int parseOptions(int argc, const char **argv) {
|
||||
} else if (!strcmp(argv[i],"-a") ) {
|
||||
if (lastarg) goto invalid;
|
||||
config.auth = strdup(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--user")) {
|
||||
if (lastarg) goto invalid;
|
||||
config.user = argv[++i];
|
||||
} else if (!strcmp(argv[i],"-d")) {
|
||||
if (lastarg) goto invalid;
|
||||
config.datasize = atoi(argv[++i]);
|
||||
@ -1391,6 +1403,7 @@ usage:
|
||||
" -p <port> Server port (default 6379)\n"
|
||||
" -s <socket> Server socket (overrides host and port)\n"
|
||||
" -a <password> Password for Redis Auth\n"
|
||||
" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
|
||||
" -c <clients> Number of parallel connections (default 50)\n"
|
||||
" -n <requests> Total number of requests (default 100000)\n"
|
||||
" -d <size> Data size of SET/GET value in bytes (default 3)\n"
|
||||
|
@ -287,7 +287,7 @@ int redis_check_rdb(const char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb,key,OBJ_MVCC_INVALID)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb,szFromObj(key),OBJ_MVCC_INVALID)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
|
@ -28,6 +28,10 @@ extern "C" {
|
||||
#include "redis-cli.h"
|
||||
|
||||
static dict *clusterManagerGetLinkStatus(void);
|
||||
static clusterManagerNode *clusterManagerNodeMasterRandom();
|
||||
|
||||
/* Used by clusterManagerFixSlotsCoverage */
|
||||
struct dict *clusterManagerUncoveredSlots = NULL;
|
||||
|
||||
/* The Cluster Manager global structure */
|
||||
struct clusterManager cluster_manager;
|
||||
@ -60,18 +64,6 @@ extern "C" void dictListDestructor(void *privdata, void *val)
|
||||
listRelease((list*)val);
|
||||
}
|
||||
|
||||
/* Used by clusterManagerFixSlotsCoverage */
|
||||
dict *clusterManagerUncoveredSlots = NULL;
|
||||
|
||||
/* Info about a cluster internal link. */
|
||||
|
||||
typedef struct clusterManagerLink {
|
||||
sds node_name;
|
||||
sds node_addr;
|
||||
int connected;
|
||||
int handshaking;
|
||||
} clusterManagerLink;
|
||||
|
||||
static dictType clusterManagerDictType = {
|
||||
dictSdsHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
@ -116,6 +108,201 @@ extern "C" void freeClusterManager(void) {
|
||||
dictRelease(clusterManagerUncoveredSlots);
|
||||
}
|
||||
|
||||
static int clusterManagerFixSlotsCoverage(char *all_slots) {
|
||||
dictIterator *iter = nullptr;
|
||||
int force_fix = config.cluster_manager_command.flags &
|
||||
CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
|
||||
|
||||
if (cluster_manager.unreachable_masters > 0 && !force_fix) {
|
||||
clusterManagerLogWarn("*** Fixing slots coverage with %d unreachable masters is dangerous: redis-cli will assume that slots about masters that are not reachable are not covered, and will try to reassign them to the reachable nodes. This can cause data loss and is rarely what you want to do. If you really want to proceed use the --cluster-fix-with-unreachable-masters option.\n", cluster_manager.unreachable_masters);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int i, fixed = 0;
|
||||
list *none = NULL, *single = NULL, *multi = NULL;
|
||||
clusterManagerLogInfo(">>> Fixing slots coverage...\n");
|
||||
for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
|
||||
int covered = all_slots[i];
|
||||
if (!covered) {
|
||||
sds slot = sdsfromlonglong((long long) i);
|
||||
list *slot_nodes = listCreate();
|
||||
sds slot_nodes_str = sdsempty();
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(cluster_manager.nodes, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
clusterManagerNode *n = (clusterManagerNode*) ln->value;
|
||||
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
|
||||
continue;
|
||||
redisReply *reply = (redisReply*)CLUSTER_MANAGER_COMMAND(n,
|
||||
"CLUSTER GETKEYSINSLOT %d %d", i, 1);
|
||||
if (!clusterManagerCheckRedisReply(n, reply, NULL)) {
|
||||
fixed = -1;
|
||||
if (reply) freeReplyObject(reply);
|
||||
goto cleanup;
|
||||
}
|
||||
assert(reply->type == REDIS_REPLY_ARRAY);
|
||||
if (reply->elements > 0) {
|
||||
listAddNodeTail(slot_nodes, n);
|
||||
if (listLength(slot_nodes) > 1)
|
||||
slot_nodes_str = sdscat(slot_nodes_str, ", ");
|
||||
slot_nodes_str = sdscatfmt(slot_nodes_str,
|
||||
"%s:%u", n->ip, n->port);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
sdsfree(slot_nodes_str);
|
||||
dictAdd(clusterManagerUncoveredSlots, slot, slot_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
/* For every slot, take action depending on the actual condition:
|
||||
* 1) No node has keys for this slot.
|
||||
* 2) A single node has keys for this slot.
|
||||
* 3) Multiple nodes have keys for this slot. */
|
||||
none = listCreate();
|
||||
single = listCreate();
|
||||
multi = listCreate();
|
||||
iter = dictGetIterator(clusterManagerUncoveredSlots);
|
||||
dictEntry *entry;
|
||||
while ((entry = dictNext(iter)) != NULL) {
|
||||
sds slot = (sds) dictGetKey(entry);
|
||||
list *nodes = (list *) dictGetVal(entry);
|
||||
switch (listLength(nodes)){
|
||||
case 0: listAddNodeTail(none, slot); break;
|
||||
case 1: listAddNodeTail(single, slot); break;
|
||||
default: listAddNodeTail(multi, slot); break;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(iter);
|
||||
|
||||
/* Handle case "1": keys in no node. */
|
||||
if (listLength(none) > 0) {
|
||||
printf("The following uncovered slots have no keys "
|
||||
"across the cluster:\n");
|
||||
clusterManagerPrintSlotsList(none);
|
||||
if (confirmWithYes("Fix these slots by covering with a random node?")){
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(none, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds slot = (sds)ln->value;
|
||||
int s = atoi(slot);
|
||||
clusterManagerNode *n = clusterManagerNodeMasterRandom();
|
||||
clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
|
||||
slot, n->ip, n->port);
|
||||
if (!clusterManagerSetSlotOwner(n, s, 0)) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
|
||||
* info into the node struct, in order to keep it synced */
|
||||
n->slots[s] = 1;
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle case "2": keys only in one node. */
|
||||
if (listLength(single) > 0) {
|
||||
printf("The following uncovered slots have keys in just one node:\n");
|
||||
clusterManagerPrintSlotsList(single);
|
||||
if (confirmWithYes("Fix these slots by covering with those nodes?")){
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(single, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds slot = (sds)ln->value;
|
||||
int s = atoi(slot);
|
||||
dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
|
||||
assert(entry != NULL);
|
||||
list *nodes = (list *) dictGetVal(entry);
|
||||
listNode *fn = listFirst(nodes);
|
||||
assert(fn != NULL);
|
||||
clusterManagerNode *n = (clusterManagerNode*) fn->value;
|
||||
clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
|
||||
slot, n->ip, n->port);
|
||||
if (!clusterManagerSetSlotOwner(n, s, 0)) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
|
||||
* info into the node struct, in order to keep it synced */
|
||||
n->slots[atoi(slot)] = 1;
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle case "3": keys in multiple nodes. */
|
||||
if (listLength(multi) > 0) {
|
||||
printf("The following uncovered slots have keys in multiple nodes:\n");
|
||||
clusterManagerPrintSlotsList(multi);
|
||||
if (confirmWithYes("Fix these slots by moving keys "
|
||||
"into a single node?")) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(multi, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds slot = (sds)ln->value;
|
||||
dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
|
||||
assert(entry != NULL);
|
||||
list *nodes = (list *) dictGetVal(entry);
|
||||
int s = atoi(slot);
|
||||
clusterManagerNode *target =
|
||||
clusterManagerGetNodeWithMostKeysInSlot(nodes, s, NULL);
|
||||
if (target == NULL) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
clusterManagerLogInfo(">>> Covering slot %s moving keys "
|
||||
"to %s:%d\n", slot,
|
||||
target->ip, target->port);
|
||||
if (!clusterManagerSetSlotOwner(target, s, 1)) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
|
||||
* info into the node struct, in order to keep it synced */
|
||||
target->slots[atoi(slot)] = 1;
|
||||
listIter nli;
|
||||
listNode *nln;
|
||||
listRewind(nodes, &nli);
|
||||
while ((nln = listNext(&nli)) != NULL) {
|
||||
clusterManagerNode *src = (clusterManagerNode*) nln->value;
|
||||
if (src == target) continue;
|
||||
/* Assign the slot to target node in the source node. */
|
||||
if (!clusterManagerSetSlot(src, target, s, "NODE", NULL))
|
||||
fixed = -1;
|
||||
if (fixed < 0) goto cleanup;
|
||||
/* Set the source node in 'importing' state
|
||||
* (even if we will actually migrate keys away)
|
||||
* in order to avoid receiving redirections
|
||||
* for MIGRATE. */
|
||||
if (!clusterManagerSetSlot(src, target, s,
|
||||
"IMPORTING", NULL)) fixed = -1;
|
||||
if (fixed < 0) goto cleanup;
|
||||
int opts = CLUSTER_MANAGER_OPT_VERBOSE |
|
||||
CLUSTER_MANAGER_OPT_COLD;
|
||||
if (!clusterManagerMoveSlot(src, target, s, opts, NULL)) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
if (!clusterManagerClearSlotStatus(src, s))
|
||||
fixed = -1;
|
||||
if (fixed < 0) goto cleanup;
|
||||
}
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanup:
|
||||
if (none) listRelease(none);
|
||||
if (single) listRelease(single);
|
||||
if (multi) listRelease(multi);
|
||||
return fixed;
|
||||
}
|
||||
|
||||
/* Return the anti-affinity score, which is a measure of the amount of
|
||||
* violations of anti-affinity in the current cluster layout, that is, how
|
||||
* badly the masters and slaves are distributed in the different IP
|
||||
@ -362,7 +549,7 @@ static clusterManagerNode *clusterManagerNodeMasterRandom() {
|
||||
listNode *ln;
|
||||
listRewind(cluster_manager.nodes, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
clusterManagerNode *n = (clusterManagerNode*)ln->value;
|
||||
clusterManagerNode *n = (clusterManagerNode*) ln->value;
|
||||
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
|
||||
master_count++;
|
||||
}
|
||||
@ -371,7 +558,7 @@ static clusterManagerNode *clusterManagerNodeMasterRandom() {
|
||||
idx = rand() % master_count;
|
||||
listRewind(cluster_manager.nodes, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
clusterManagerNode *n = (clusterManagerNode*)ln->value;
|
||||
clusterManagerNode *n = (clusterManagerNode*) ln->value;
|
||||
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
|
||||
if (!idx--) {
|
||||
return n;
|
||||
@ -381,202 +568,6 @@ static clusterManagerNode *clusterManagerNodeMasterRandom() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int clusterManagerFixSlotsCoverage(char *all_slots) {
|
||||
int i, fixed = 0;
|
||||
dictIterator *iter = nullptr;
|
||||
dictEntry *entry = nullptr;
|
||||
list *none = NULL, *single = NULL, *multi = NULL;
|
||||
clusterManagerLogInfo(">>> Fixing slots coverage...\n");
|
||||
printf("List of not covered slots: \n");
|
||||
int uncovered_count = 0;
|
||||
sds log = sdsempty();
|
||||
for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
|
||||
int covered = all_slots[i];
|
||||
if (!covered) {
|
||||
sds key = sdsfromlonglong((long long) i);
|
||||
if (uncovered_count++ > 0) printf(",");
|
||||
printf("%s", (char *) key);
|
||||
list *slot_nodes = listCreate();
|
||||
sds slot_nodes_str = sdsempty();
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(cluster_manager.nodes, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
clusterManagerNode *n = (clusterManagerNode*)ln->value;
|
||||
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
|
||||
continue;
|
||||
redisReply *reply = (redisReply*)CLUSTER_MANAGER_COMMAND(n,
|
||||
"CLUSTER GETKEYSINSLOT %d %d", i, 1);
|
||||
if (!clusterManagerCheckRedisReply(n, reply, NULL)) {
|
||||
fixed = -1;
|
||||
if (reply) freeReplyObject(reply);
|
||||
goto cleanup;
|
||||
}
|
||||
assert(reply->type == REDIS_REPLY_ARRAY);
|
||||
if (reply->elements > 0) {
|
||||
listAddNodeTail(slot_nodes, n);
|
||||
if (listLength(slot_nodes) > 1)
|
||||
slot_nodes_str = sdscat(slot_nodes_str, ", ");
|
||||
slot_nodes_str = sdscatfmt(slot_nodes_str,
|
||||
"%s:%u", n->ip, n->port);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
log = sdscatfmt(log, "\nSlot %S has keys in %u nodes: %S",
|
||||
key, listLength(slot_nodes), slot_nodes_str);
|
||||
sdsfree(slot_nodes_str);
|
||||
dictAdd(clusterManagerUncoveredSlots, key, slot_nodes);
|
||||
}
|
||||
}
|
||||
printf("\n%s\n", log);
|
||||
/* For every slot, take action depending on the actual condition:
|
||||
* 1) No node has keys for this slot.
|
||||
* 2) A single node has keys for this slot.
|
||||
* 3) Multiple nodes have keys for this slot. */
|
||||
none = listCreate();
|
||||
single = listCreate();
|
||||
multi = listCreate();
|
||||
iter = dictGetIterator(clusterManagerUncoveredSlots);
|
||||
while ((entry = dictNext(iter)) != NULL) {
|
||||
sds slot = (sds) dictGetKey(entry);
|
||||
list *nodes = (list *) dictGetVal(entry);
|
||||
switch (listLength(nodes)){
|
||||
case 0: listAddNodeTail(none, slot); break;
|
||||
case 1: listAddNodeTail(single, slot); break;
|
||||
default: listAddNodeTail(multi, slot); break;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(iter);
|
||||
|
||||
/* Handle case "1": keys in no node. */
|
||||
if (listLength(none) > 0) {
|
||||
printf("The following uncovered slots have no keys "
|
||||
"across the cluster:\n");
|
||||
clusterManagerPrintSlotsList(none);
|
||||
if (confirmWithYes("Fix these slots by covering with a random node?")){
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(none, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds slot = (sds)ln->value;
|
||||
int s = atoi(slot);
|
||||
clusterManagerNode *n = clusterManagerNodeMasterRandom();
|
||||
clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
|
||||
slot, n->ip, n->port);
|
||||
if (!clusterManagerSetSlotOwner(n, s, 0)) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
|
||||
* info into the node struct, in order to keep it synced */
|
||||
n->slots[s] = 1;
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle case "2": keys only in one node. */
|
||||
if (listLength(single) > 0) {
|
||||
printf("The following uncovered slots have keys in just one node:\n");
|
||||
clusterManagerPrintSlotsList(single);
|
||||
if (confirmWithYes("Fix these slots by covering with those nodes?")){
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(single, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds slot = (sds)ln->value;
|
||||
int s = atoi(slot);
|
||||
dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
|
||||
assert(entry != NULL);
|
||||
list *nodes = (list *) dictGetVal(entry);
|
||||
listNode *fn = listFirst(nodes);
|
||||
assert(fn != NULL);
|
||||
clusterManagerNode *n = (clusterManagerNode*)fn->value;
|
||||
clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
|
||||
slot, n->ip, n->port);
|
||||
if (!clusterManagerSetSlotOwner(n, s, 0)) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
|
||||
* info into the node struct, in order to keep it synced */
|
||||
n->slots[atoi(slot)] = 1;
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle case "3": keys in multiple nodes. */
|
||||
if (listLength(multi) > 0) {
|
||||
printf("The following uncovered slots have keys in multiple nodes:\n");
|
||||
clusterManagerPrintSlotsList(multi);
|
||||
if (confirmWithYes("Fix these slots by moving keys "
|
||||
"into a single node?")) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(multi, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds slot = (sds)ln->value;
|
||||
dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
|
||||
assert(entry != NULL);
|
||||
list *nodes = (list *) dictGetVal(entry);
|
||||
int s = atoi(slot);
|
||||
clusterManagerNode *target =
|
||||
clusterManagerGetNodeWithMostKeysInSlot(nodes, s, NULL);
|
||||
if (target == NULL) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
clusterManagerLogInfo(">>> Covering slot %s moving keys "
|
||||
"to %s:%d\n", slot,
|
||||
target->ip, target->port);
|
||||
if (!clusterManagerSetSlotOwner(target, s, 1)) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
|
||||
* info into the node struct, in order to keep it synced */
|
||||
target->slots[atoi(slot)] = 1;
|
||||
listIter nli;
|
||||
listNode *nln;
|
||||
listRewind(nodes, &nli);
|
||||
while ((nln = listNext(&nli)) != NULL) {
|
||||
clusterManagerNode *src = (clusterManagerNode*)nln->value;
|
||||
if (src == target) continue;
|
||||
/* Assign the slot to target node in the source node. */
|
||||
if (!clusterManagerSetSlot(src, target, s, "NODE", NULL))
|
||||
fixed = -1;
|
||||
if (fixed < 0) goto cleanup;
|
||||
/* Set the source node in 'importing' state
|
||||
* (even if we will actually migrate keys away)
|
||||
* in order to avoid receiving redirections
|
||||
* for MIGRATE. */
|
||||
if (!clusterManagerSetSlot(src, target, s,
|
||||
"IMPORTING", NULL)) fixed = -1;
|
||||
if (fixed < 0) goto cleanup;
|
||||
int opts = CLUSTER_MANAGER_OPT_VERBOSE |
|
||||
CLUSTER_MANAGER_OPT_COLD;
|
||||
if (!clusterManagerMoveSlot(src, target, s, opts, NULL)) {
|
||||
fixed = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
if (!clusterManagerClearSlotStatus(src, s))
|
||||
fixed = -1;
|
||||
if (fixed < 0) goto cleanup;
|
||||
}
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanup:
|
||||
sdsfree(log);
|
||||
if (none) listRelease(none);
|
||||
if (single) listRelease(single);
|
||||
if (multi) listRelease(multi);
|
||||
return fixed;
|
||||
}
|
||||
|
||||
|
||||
extern "C" int clusterManagerCheckCluster(int quiet) {
|
||||
listNode *ln = listFirst(cluster_manager.nodes);
|
||||
if (!ln) return 0;
|
||||
|
@ -509,7 +509,8 @@ static char *hintsCallback(const char *buf, int *color, int *bold) {
|
||||
for (i = 0; i < helpEntriesLen; i++) {
|
||||
if (!(helpEntries[i].type & CLI_HELP_COMMAND)) continue;
|
||||
|
||||
if (strcasecmp(argv[0],helpEntries[i].full) == 0)
|
||||
if (strcasecmp(argv[0],helpEntries[i].full) == 0 ||
|
||||
strcasecmp(buf,helpEntries[i].full) == 0)
|
||||
{
|
||||
*color = 90;
|
||||
*bold = 0;
|
||||
@ -1410,6 +1411,9 @@ static int parseOptions(int argc, char **argv) {
|
||||
} else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
|
||||
config.cluster_manager_command.flags |=
|
||||
CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
|
||||
} else if (!strcmp(argv[i],"--cluster-fix-with-unreachable-masters")) {
|
||||
config.cluster_manager_command.flags |=
|
||||
CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
|
||||
#ifdef USE_OPENSSL
|
||||
} else if (!strcmp(argv[i],"--tls")) {
|
||||
config.tls = 1;
|
||||
@ -2021,7 +2025,7 @@ clusterManagerCommandDef clusterManagerCommands[] = {
|
||||
"search-multiple-owners"},
|
||||
{"info", clusterManagerCommandInfo, -1, "host:port", NULL},
|
||||
{"fix", clusterManagerCommandFix, -1, "host:port",
|
||||
"search-multiple-owners"},
|
||||
"search-multiple-owners,fix-with-unreachable-masters"},
|
||||
{"reshard", clusterManagerCommandReshard, -1, "host:port",
|
||||
"from <arg>,to <arg>,slots <arg>,yes,timeout <arg>,pipeline <arg>,"
|
||||
"replace"},
|
||||
@ -3571,7 +3575,9 @@ static int clusterManagerLoadInfoFromNode(clusterManagerNode *node, int opts) {
|
||||
if (friend->flags & (CLUSTER_MANAGER_FLAG_NOADDR |
|
||||
CLUSTER_MANAGER_FLAG_DISCONNECT |
|
||||
CLUSTER_MANAGER_FLAG_FAIL))
|
||||
{
|
||||
goto invalid_friend;
|
||||
}
|
||||
listAddNodeTail(cluster_manager.nodes, friend);
|
||||
} else {
|
||||
clusterManagerLogErr("[ERR] Unable to load info for "
|
||||
@ -3581,6 +3587,8 @@ static int clusterManagerLoadInfoFromNode(clusterManagerNode *node, int opts) {
|
||||
}
|
||||
continue;
|
||||
invalid_friend:
|
||||
if (!(friend->flags & CLUSTER_MANAGER_FLAG_SLAVE))
|
||||
cluster_manager.unreachable_masters++;
|
||||
freeClusterManagerNode(friend);
|
||||
}
|
||||
listRelease(node->friends);
|
||||
@ -3771,17 +3779,18 @@ int clusterManagerGetCoveredSlots(char *all_slots) {
|
||||
}
|
||||
|
||||
void clusterManagerPrintSlotsList(list *slots) {
|
||||
clusterManagerNode n = {0};
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(slots, &li);
|
||||
sds first = NULL;
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds slot = ln->value;
|
||||
if (!first) first = slot;
|
||||
else printf(", ");
|
||||
printf("%s", slot);
|
||||
int slot = atoi(ln->value);
|
||||
if (slot >= 0 && slot < CLUSTER_MANAGER_SLOTS)
|
||||
n.slots[slot] = 1;
|
||||
}
|
||||
printf("\n");
|
||||
sds nodeslist = clusterManagerNodeSlotsString(&n);
|
||||
printf("%s\n", nodeslist);
|
||||
sdsfree(nodeslist);
|
||||
}
|
||||
|
||||
/* Return the node, among 'nodes' with the greatest number of keys
|
||||
@ -3846,24 +3855,38 @@ static clusterManagerNode *clusterManagerNodeWithLeastReplicas() {
|
||||
* more nodes. This function fixes this condition by migrating keys where
|
||||
* it seems more sensible. */
|
||||
int clusterManagerFixOpenSlot(int slot) {
|
||||
int force_fix = config.cluster_manager_command.flags &
|
||||
CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
|
||||
|
||||
if (cluster_manager.unreachable_masters > 0 && !force_fix) {
|
||||
clusterManagerLogWarn("*** Fixing open slots with %d unreachable masters is dangerous: redis-cli will assume that slots about masters that are not reachable are not covered, and will try to reassign them to the reachable nodes. This can cause data loss and is rarely what you want to do. If you really want to proceed use the --cluster-fix-with-unreachable-masters option.\n", cluster_manager.unreachable_masters);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
clusterManagerLogInfo(">>> Fixing open slot %d\n", slot);
|
||||
/* Try to obtain the current slot owner, according to the current
|
||||
* nodes configuration. */
|
||||
int success = 1;
|
||||
list *owners = listCreate();
|
||||
list *owners = listCreate(); /* List of nodes claiming some ownership.
|
||||
it could be stating in the configuration
|
||||
to have the node ownership, or just
|
||||
holding keys for such slot. */
|
||||
list *migrating = listCreate();
|
||||
list *importing = listCreate();
|
||||
sds migrating_str = sdsempty();
|
||||
sds importing_str = sdsempty();
|
||||
clusterManagerNode *owner = NULL;
|
||||
clusterManagerNode *owner = NULL; /* The obvious slot owner if any. */
|
||||
|
||||
/* Iterate all the nodes, looking for potential owners of this slot. */
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(cluster_manager.nodes, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
clusterManagerNode *n = ln->value;
|
||||
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
|
||||
if (n->slots[slot]) listAddNodeTail(owners, n);
|
||||
else {
|
||||
if (n->slots[slot]) {
|
||||
listAddNodeTail(owners, n);
|
||||
} else {
|
||||
redisReply *r = CLUSTER_MANAGER_COMMAND(n,
|
||||
"CLUSTER COUNTKEYSINSLOT %d", slot);
|
||||
success = clusterManagerCheckRedisReply(n, r, NULL);
|
||||
@ -3877,7 +3900,14 @@ int clusterManagerFixOpenSlot(int slot) {
|
||||
if (!success) goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have only a single potential owner for this slot,
|
||||
* set it as "owner". */
|
||||
if (listLength(owners) == 1) owner = listFirst(owners)->value;
|
||||
|
||||
/* Scan the list of nodes again, in order to populate the
|
||||
* list of nodes in importing or migrating state for
|
||||
* this slot. */
|
||||
listRewind(cluster_manager.nodes, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
clusterManagerNode *n = ln->value;
|
||||
@ -3909,6 +3939,7 @@ int clusterManagerFixOpenSlot(int slot) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If the node is neither migrating nor importing and it's not
|
||||
* the owner, then is added to the importing list in case
|
||||
* it has keys in the slot. */
|
||||
@ -3933,11 +3964,12 @@ int clusterManagerFixOpenSlot(int slot) {
|
||||
printf("Set as migrating in: %s\n", migrating_str);
|
||||
if (sdslen(importing_str) > 0)
|
||||
printf("Set as importing in: %s\n", importing_str);
|
||||
|
||||
/* If there is no slot owner, set as owner the node with the biggest
|
||||
* number of keys, among the set of migrating / importing nodes. */
|
||||
if (owner == NULL) {
|
||||
clusterManagerLogInfo(">>> Nobody claims ownership, "
|
||||
"selecting an owner...\n");
|
||||
clusterManagerLogInfo(">>> No single clear owner for the slot, "
|
||||
"selecting an owner by # of keys...\n");
|
||||
owner = clusterManagerGetNodeWithMostKeysInSlot(cluster_manager.nodes,
|
||||
slot, NULL);
|
||||
// If we still don't have an owner, we can't fix it.
|
||||
@ -3968,6 +4000,7 @@ int clusterManagerFixOpenSlot(int slot) {
|
||||
clusterManagerRemoveNodeFromList(migrating, owner);
|
||||
clusterManagerRemoveNodeFromList(importing, owner);
|
||||
}
|
||||
|
||||
/* If there are multiple owners of the slot, we need to fix it
|
||||
* so that a single node is the owner and all the other nodes
|
||||
* are in importing state. Later the fix can be handled by one
|
||||
@ -4000,6 +4033,7 @@ int clusterManagerFixOpenSlot(int slot) {
|
||||
}
|
||||
}
|
||||
int move_opts = CLUSTER_MANAGER_OPT_VERBOSE;
|
||||
|
||||
/* Case 1: The slot is in migrating state in one node, and in
|
||||
* importing state in 1 node. That's trivial to address. */
|
||||
if (listLength(migrating) == 1 && listLength(importing) == 1) {
|
||||
@ -4011,6 +4045,7 @@ int clusterManagerFixOpenSlot(int slot) {
|
||||
move_opts |= CLUSTER_MANAGER_OPT_UPDATE;
|
||||
success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
|
||||
}
|
||||
|
||||
/* Case 2: There are multiple nodes that claim the slot as importing,
|
||||
* they probably got keys about the slot after a restart so opened
|
||||
* the slot. In this case we just move all the keys to the owner
|
||||
@ -4041,6 +4076,7 @@ int clusterManagerFixOpenSlot(int slot) {
|
||||
if (!success) goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* Case 3: The slot is in migrating state in one node but multiple
|
||||
* other nodes claim to be in importing state and don't have any key in
|
||||
* the slot. We search for the importing node having the same ID as
|
||||
|
@ -66,6 +66,7 @@ extern "C" {
|
||||
#define CLUSTER_MANAGER_CMD_FLAG_COPY 1 << 7
|
||||
#define CLUSTER_MANAGER_CMD_FLAG_COLOR 1 << 8
|
||||
#define CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS 1 << 9
|
||||
#define CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS 1 << 10
|
||||
|
||||
#define CLUSTER_MANAGER_OPT_GETFRIENDS 1 << 0
|
||||
#define CLUSTER_MANAGER_OPT_COLD 1 << 1
|
||||
@ -180,11 +181,12 @@ extern struct config {
|
||||
int resp3;
|
||||
} config;
|
||||
|
||||
/* The Cluster Manager global structure */
|
||||
extern struct clusterManager {
|
||||
struct clusterManager {
|
||||
list *nodes; /* List of nodes in the configuration. */
|
||||
list *errors;
|
||||
} cluster_manager;
|
||||
int unreachable_masters; /* Masters we are not able to reach. */
|
||||
};
|
||||
extern struct clusterManager cluster_manager;
|
||||
|
||||
typedef struct clusterManagerNode {
|
||||
redisContext *context;
|
||||
@ -196,7 +198,7 @@ typedef struct clusterManagerNode {
|
||||
time_t ping_recv;
|
||||
int flags;
|
||||
list *flags_str; /* Flags string representations */
|
||||
sds replicate; /* Master ID if node is a replica */
|
||||
sds replicate; /* Master ID if node is a slave */
|
||||
int dirty; /* Node has changes that can be flushed */
|
||||
uint8_t slots[CLUSTER_MANAGER_SLOTS];
|
||||
int slots_count;
|
||||
@ -226,6 +228,15 @@ typedef struct clusterManagerReshardTableItem {
|
||||
int slot;
|
||||
} clusterManagerReshardTableItem;
|
||||
|
||||
/* Info about a cluster internal link. */
|
||||
|
||||
typedef struct clusterManagerLink {
|
||||
sds node_name;
|
||||
sds node_addr;
|
||||
int connected;
|
||||
int handshaking;
|
||||
} clusterManagerLink;
|
||||
|
||||
typedef struct typeinfo {
|
||||
char *name;
|
||||
char *sizecmd;
|
||||
|
@ -474,7 +474,11 @@ RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(Re
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
|
||||
#ifdef __GNUC__
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
#else
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
|
||||
#endif
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
|
||||
@ -558,8 +562,13 @@ void REDISMODULE_API_FUNC(RedisModule_SaveLongDouble)(RedisModuleIO *io, long do
|
||||
long double REDISMODULE_API_FUNC(RedisModule_LoadLongDouble)(RedisModuleIO *io);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt);
|
||||
#ifdef __GNUC__
|
||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
#else
|
||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
||||
#endif
|
||||
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
|
||||
void REDISMODULE_API_FUNC(RedisModule_LatencyAddSample)(const char *event, mstime_t latency);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include "bio.h"
|
||||
#include "aelocker.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
@ -46,6 +47,7 @@
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
long long adjustMeaningfulReplOffset();
|
||||
void replicationDiscardCachedMaster(redisMaster *mi);
|
||||
void replicationResurrectCachedMaster(redisMaster *mi, connection *conn);
|
||||
void replicationSendAck(redisMaster *mi);
|
||||
@ -1301,6 +1303,7 @@ void sendBulkToSlave(connection *conn) {
|
||||
serverAssert(FCorrectThread(replica));
|
||||
char buf[PROTO_IOBUF_LEN];
|
||||
ssize_t nwritten, buflen;
|
||||
AeLocker aeLock;
|
||||
std::unique_lock<fastlock> ul(replica->lock);
|
||||
|
||||
/* Before sending the RDB file, we send the preamble as configured by the
|
||||
@ -1312,6 +1315,8 @@ void sendBulkToSlave(connection *conn) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Write error sending RDB preamble to replica: %s",
|
||||
connGetLastError(conn));
|
||||
ul.unlock();
|
||||
aeLock.arm(nullptr);
|
||||
freeClient(replica);
|
||||
return;
|
||||
}
|
||||
@ -1332,6 +1337,8 @@ void sendBulkToSlave(connection *conn) {
|
||||
if (buflen <= 0) {
|
||||
serverLog(LL_WARNING,"Read error sending DB to replica: %s",
|
||||
(buflen == 0) ? "premature EOF" : strerror(errno));
|
||||
ul.unlock();
|
||||
aeLock.arm(nullptr);
|
||||
freeClient(replica);
|
||||
return;
|
||||
}
|
||||
@ -1339,6 +1346,8 @@ void sendBulkToSlave(connection *conn) {
|
||||
if (connGetState(conn) != CONN_STATE_CONNECTED) {
|
||||
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
|
||||
connGetLastError(conn));
|
||||
ul.unlock();
|
||||
aeLock.arm(nullptr);
|
||||
freeClient(replica);
|
||||
}
|
||||
return;
|
||||
@ -1362,9 +1371,11 @@ void rdbPipeWriteHandlerConnRemoved(struct connection *conn) {
|
||||
g_pserver->rdb_pipe_numconns_writing--;
|
||||
/* if there are no more writes for now for this conn, or write error: */
|
||||
if (g_pserver->rdb_pipe_numconns_writing == 0) {
|
||||
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.");
|
||||
}
|
||||
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, []{
|
||||
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.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1421,12 +1432,12 @@ void RdbPipeCleanup() {
|
||||
void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask) {
|
||||
UNUSED(mask);
|
||||
UNUSED(clientData);
|
||||
UNUSED(eventLoop);
|
||||
|
||||
int i;
|
||||
if (!g_pserver->rdb_pipe_buff)
|
||||
g_pserver->rdb_pipe_buff = (char*)zmalloc(PROTO_IOBUF_LEN);
|
||||
serverAssert(g_pserver->rdb_pipe_numconns_writing==0);
|
||||
serverAssert(eventLoop == g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el);
|
||||
|
||||
while (1) {
|
||||
g_pserver->rdb_pipe_bufflen = read(fd, g_pserver->rdb_pipe_buff, PROTO_IOBUF_LEN);
|
||||
@ -1471,9 +1482,10 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData,
|
||||
continue;
|
||||
|
||||
client *slave = (client*)connGetPrivateData(conn);
|
||||
std::unique_lock<fastlock> ul(slave->lock);
|
||||
serverAssert(slave->conn == conn);
|
||||
if (slave->flags & CLIENT_CLOSE_ASAP)
|
||||
continue;
|
||||
if(slave->flags.load(std::memory_order_relaxed) & CLIENT_CLOSE_ASAP)
|
||||
continue; // if we asked to free the client don't send any more data
|
||||
|
||||
// Normally it would be bug to talk a client conn from a different thread, but here we know nobody else will
|
||||
// be sending anything while in this replication state so it is OK
|
||||
@ -1495,12 +1507,8 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData,
|
||||
* setup write handler (and disable pipe read handler, below) */
|
||||
if (nwritten != g_pserver->rdb_pipe_bufflen) {
|
||||
g_pserver->rdb_pipe_numconns_writing++;
|
||||
slave->casyncOpsPending++;
|
||||
aePostFunction(g_pserver->rgthreadvar[slave->iel].el, [slave] {
|
||||
slave->casyncOpsPending--;
|
||||
if (slave->flags & CLIENT_CLOSE_ASAP)
|
||||
return;
|
||||
connSetWriteHandler(slave->conn, rdbPipeWriteHandler);
|
||||
slave->postFunction([conn](client *) {
|
||||
connSetWriteHandler(conn, rdbPipeWriteHandler);
|
||||
});
|
||||
}
|
||||
stillAlive++;
|
||||
@ -1545,6 +1553,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type)
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)ln->value;
|
||||
|
||||
std::unique_lock<fastlock> ul(replica->lock);
|
||||
|
||||
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
|
||||
startbgsave = 1;
|
||||
mincapa = (mincapa == -1) ? replica->slave_capa :
|
||||
@ -1591,6 +1601,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type)
|
||||
replica->repl_ack_time = g_pserver->unixtime; /* Timeout otherwise. */
|
||||
} else {
|
||||
if (bgsaveerr != C_OK) {
|
||||
ul.unlock();
|
||||
if (FCorrectThread(replica))
|
||||
freeClient(replica);
|
||||
else
|
||||
@ -1600,6 +1611,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type)
|
||||
}
|
||||
if ((replica->repldbfd = open(g_pserver->rdb_filename,O_RDONLY)) == -1 ||
|
||||
redis_fstat(replica->repldbfd,&buf) == -1) {
|
||||
ul.unlock();
|
||||
if (FCorrectThread(replica))
|
||||
freeClient(replica);
|
||||
else
|
||||
@ -1617,27 +1629,14 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type)
|
||||
{
|
||||
connSetWriteHandler(replica->conn,NULL);
|
||||
if (connSetWriteHandler(replica->conn,sendBulkToSlave) == C_ERR) {
|
||||
ul.unlock();
|
||||
freeClient(replica);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
aePostFunction(g_pserver->rgthreadvar[replica->iel].el, [replica] {
|
||||
// Because the client could have been closed while the lambda waited to run we need to
|
||||
// verify the replica is still connected
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
bool fFound = false;
|
||||
while ((ln = listNext(&li))) {
|
||||
if (listNodeValue(ln) == replica) {
|
||||
fFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!fFound)
|
||||
return;
|
||||
replica->postFunction([](client *replica) {
|
||||
connSetWriteHandler(replica->conn,NULL);
|
||||
if (connSetWriteHandler(replica->conn,sendBulkToSlave) == C_ERR) {
|
||||
freeClient(replica);
|
||||
@ -3306,6 +3305,10 @@ void replicationCacheMaster(redisMaster *mi, client *c) {
|
||||
* pending outputs to the master. */
|
||||
sdsclear(mi->master->querybuf);
|
||||
sdsclear(mi->master->pending_querybuf);
|
||||
|
||||
/* Adjust reploff and read_reploff to the last meaningful offset we executed.
|
||||
* this is the offset the replica will use for future PSYNC. */
|
||||
mi->master->reploff = adjustMeaningfulReplOffset();
|
||||
mi->master->read_reploff = mi->master->reploff;
|
||||
if (c->flags & CLIENT_MULTI) discardTransaction(c);
|
||||
listEmpty(c->reply);
|
||||
@ -3331,9 +3334,41 @@ void replicationCacheMaster(redisMaster *mi, client *c) {
|
||||
replicationHandleMasterDisconnection(mi);
|
||||
}
|
||||
|
||||
/* This function is called when a master is turend into a replica, in order to
|
||||
/* If the "meaningful" offset, that is the offset without the final PINGs
|
||||
* in the stream, is different than the last offset, use it 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.
|
||||
* This function trims the replication backlog when needed, and returns
|
||||
* the offset to be used for future partial sync. */
|
||||
long long adjustMeaningfulReplOffset() {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
return g_pserver->master_repl_offset;
|
||||
}
|
||||
|
||||
/* This function is called when a master is turend into a slave, in order to
|
||||
* create from scratch a cached master for the new client, that will allow
|
||||
* to PSYNC with the replica that was promoted as the new master after a
|
||||
* to PSYNC with the slave that was promoted as the new master after a
|
||||
* failover.
|
||||
*
|
||||
* Assuming this instance was previously the master instance of the new master,
|
||||
@ -3356,35 +3391,7 @@ void replicationCacheMasterUsingMyself(redisMaster *mi) {
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
mi->master_initial_offset = adjustMeaningfulReplOffset();
|
||||
|
||||
/* The master client we create can be set to any DBID, because
|
||||
* the new master will start its replication stream with SELECT. */
|
||||
@ -3853,6 +3860,7 @@ void replicationCron(void) {
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)ln->value;
|
||||
std::unique_lock<fastlock> ul(replica->lock);
|
||||
|
||||
if (replica->replstate != SLAVE_STATE_ONLINE) continue;
|
||||
if (replica->flags & CLIENT_PRE_PSYNC) continue;
|
||||
@ -3861,9 +3869,15 @@ void replicationCron(void) {
|
||||
serverLog(LL_WARNING, "Disconnecting timedout replica: %s",
|
||||
replicationGetSlaveName(replica));
|
||||
if (FCorrectThread(replica))
|
||||
freeClient(replica);
|
||||
{
|
||||
ul.release();
|
||||
if (!freeClient(replica))
|
||||
replica->lock.unlock(); // we didn't free so we have undo the lock we just released
|
||||
}
|
||||
else
|
||||
{
|
||||
freeClientAsync(replica);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -671,12 +671,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
!g_pserver->loading && /* Don't care about mem if loading. */
|
||||
!listLength(g_pserver->masters) && /* Slave must execute the script. */
|
||||
g_pserver->lua_write_dirty == 0 && /* Script had no side effects so far. */
|
||||
g_pserver->lua_oom && /* Detected OOM when script started. */
|
||||
(cmd->flags & CMD_DENYOOM))
|
||||
{
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
|
||||
luaPushError(lua, (char*)ptrFromObj(shared.oomerr));
|
||||
goto cleanup;
|
||||
}
|
||||
luaPushError(lua, (char*)ptrFromObj(shared.oomerr));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (cmd->flags & CMD_RANDOM) g_pserver->lua_random_dirty = 1;
|
||||
@ -973,6 +972,7 @@ int luaLogCommand(lua_State *lua) {
|
||||
lua_pushstring(lua, "Invalid debug level.");
|
||||
return lua_error(lua);
|
||||
}
|
||||
if (level < cserver.verbosity) return 0;
|
||||
|
||||
/* Glue together all the arguments */
|
||||
log = sdsempty();
|
||||
|
@ -106,11 +106,11 @@ sds sdsnewlen(const void *init, ssize_t initlen) {
|
||||
unsigned char *fp; /* flags pointer. */
|
||||
|
||||
sh = s_malloc(hdrlen+initlen+1, MALLOC_SHARED);
|
||||
if (sh == NULL) return NULL;
|
||||
if (init==SDS_NOINIT)
|
||||
init = NULL;
|
||||
else if (!init)
|
||||
memset(sh, 0, hdrlen+initlen+1);
|
||||
if (sh == NULL) return NULL;
|
||||
s = (char*)sh+hdrlen;
|
||||
fp = ((unsigned char*)s)-1;
|
||||
switch(type) {
|
||||
|
@ -871,8 +871,8 @@ void sentinelCollectTerminatedScripts(void) {
|
||||
}
|
||||
listDelNode(sentinel.scripts_queue,ln);
|
||||
sentinelReleaseScriptJob(sj);
|
||||
sentinel.running_scripts--;
|
||||
}
|
||||
sentinel.running_scripts--;
|
||||
}
|
||||
}
|
||||
|
||||
|
175
src/server.cpp
175
src/server.cpp
@ -706,15 +706,15 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"multi",multiCommand,1,
|
||||
"no-script fast @transaction",
|
||||
"no-script fast ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"exec",execCommand,1,
|
||||
"no-script no-monitor no-slowlog @transaction",
|
||||
"no-script no-monitor no-slowlog ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"discard",discardCommand,1,
|
||||
"no-script fast @transaction",
|
||||
"no-script fast ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"sync",syncCommand,1,
|
||||
@ -981,11 +981,11 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"xread",xreadCommand,-4,
|
||||
"read-only no-script @stream @blocking",
|
||||
"read-only @stream @blocking",
|
||||
0,xreadGetKeys,1,1,1,0,0,0},
|
||||
|
||||
{"xreadgroup",xreadCommand,-7,
|
||||
"write no-script @stream @blocking",
|
||||
"write @stream @blocking",
|
||||
0,xreadGetKeys,1,1,1,0,0,0},
|
||||
|
||||
{"xgroup",xgroupCommand,-2,
|
||||
@ -1046,7 +1046,11 @@ struct redisCommand redisCommandTable[] = {
|
||||
|
||||
{"keydb.hrename", hrenameCommand, 4,
|
||||
"write fast @hash",
|
||||
0,NULL,0,0,0,0,0,0}
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"stralgo",stralgoCommand,-2,
|
||||
"read-only @string",
|
||||
0,lcsGetKeys,0,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
@ -1290,11 +1294,15 @@ int dictEncObjKeyCompare(void *privdata, const void *key1,
|
||||
o2->encoding == OBJ_ENCODING_INT)
|
||||
return ptrFromObj(o1) == ptrFromObj(o2);
|
||||
|
||||
o1 = getDecodedObject(o1);
|
||||
o2 = getDecodedObject(o2);
|
||||
/* Due to OBJ_STATIC_REFCOUNT, we avoid calling getDecodedObject() without
|
||||
* good reasons, because it would incrRefCount() the object, which
|
||||
* is invalid. So we check to make sure dictFind() works with static
|
||||
* objects as well. */
|
||||
if (o1->getrefcount() != OBJ_STATIC_REFCOUNT) o1 = getDecodedObject(o1);
|
||||
if (o2->getrefcount() != OBJ_STATIC_REFCOUNT) o2 = getDecodedObject(o2);
|
||||
cmp = dictSdsKeyCompare(privdata,ptrFromObj(o1),ptrFromObj(o2));
|
||||
decrRefCount(o1);
|
||||
decrRefCount(o2);
|
||||
if (o1->getrefcount() != OBJ_STATIC_REFCOUNT) decrRefCount(o1);
|
||||
if (o2->getrefcount() != OBJ_STATIC_REFCOUNT) decrRefCount(o2);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
@ -1737,6 +1745,28 @@ int clientsCronTrackExpansiveClients(client *c) {
|
||||
return 0; /* This function never terminates the client. */
|
||||
}
|
||||
|
||||
/* Iterating all the clients in getMemoryOverheadData() is too slow and
|
||||
* in turn would make the INFO command too slow. So we perform this
|
||||
* computation incrementally and track the (not instantaneous but updated
|
||||
* to the second) total memory used by clients using clinetsCron() in
|
||||
* a more incremental way (depending on server.hz). */
|
||||
int clientsCronTrackClientsMemUsage(client *c) {
|
||||
size_t mem = 0;
|
||||
int type = getClientType(c);
|
||||
mem += getClientOutputBufferMemoryUsage(c);
|
||||
mem += sdsAllocSize(c->querybuf);
|
||||
mem += sizeof(client);
|
||||
/* Now that we have the memory used by the client, remove the old
|
||||
* value from the old categoty, and add it back. */
|
||||
g_pserver->stat_clients_type_memory[c->client_cron_last_memory_type] -=
|
||||
c->client_cron_last_memory_usage;
|
||||
g_pserver->stat_clients_type_memory[type] += mem;
|
||||
/* Remember what we added and where, to remove it next time. */
|
||||
c->client_cron_last_memory_usage = mem;
|
||||
c->client_cron_last_memory_type = type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the max samples in the memory usage of clients tracked by
|
||||
* the function clientsCronTrackExpansiveClients(). */
|
||||
void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
|
||||
@ -1788,7 +1818,7 @@ void clientsCron(int iel) {
|
||||
/* Rotate the list, take the current head, process.
|
||||
* This way if the client must be removed from the list it's the
|
||||
* first element and we don't incur into O(N) computation. */
|
||||
listRotate(g_pserver->clients);
|
||||
listRotateTailToHead(g_pserver->clients);
|
||||
head = (listNode*)listFirst(g_pserver->clients);
|
||||
c = (client*)listNodeValue(head);
|
||||
if (c->iel == iel)
|
||||
@ -1800,6 +1830,7 @@ void clientsCron(int iel) {
|
||||
if (clientsCronHandleTimeout(c,now)) continue; // Client free'd so don't release the lock
|
||||
if (clientsCronResizeQueryBuffer(c)) goto LContinue;
|
||||
if (clientsCronTrackExpansiveClients(c)) goto LContinue;
|
||||
if (clientsCronTrackClientsMemUsage(c)) goto LContinue;
|
||||
LContinue:
|
||||
fastlock_unlock(&c->lock);
|
||||
}
|
||||
@ -2207,6 +2238,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
checkTrialTimeout();
|
||||
}
|
||||
|
||||
/* Resize tracking keys table if needed. This is also done at every
|
||||
* command execution, but we want to be sure that if the last command
|
||||
* executed changes the value via CONFIG SET, the server will perform
|
||||
* the operation even if completely idle. */
|
||||
if (g_pserver->tracking_clients) trackingLimitUsedSlots();
|
||||
|
||||
/* Start a scheduled BGSAVE if the corresponding flag is set. This is
|
||||
* useful when we are forced to postpone a BGSAVE because an AOF
|
||||
* rewrite is in progress.
|
||||
@ -2267,9 +2304,22 @@ int serverCronLite(struct aeEventLoop *eventLoop, long long id, void *clientData
|
||||
return 1000/g_pserver->hz;
|
||||
}
|
||||
|
||||
extern int ProcessingEventsWhileBlocked;
|
||||
|
||||
/* This function gets called every time Redis is entering the
|
||||
* main loop of the event driven library, that is, before to sleep
|
||||
* for ready file descriptors. */
|
||||
* for ready file descriptors.
|
||||
*
|
||||
* Note: This function is (currently) called from two functions:
|
||||
* 1. aeMain - The main server loop
|
||||
* 2. processEventsWhileBlocked - Process clients during RDB/AOF load
|
||||
*
|
||||
* If it was called from processEventsWhileBlocked we don't want
|
||||
* to perform all actions (For example, we don't want to expire
|
||||
* keys), but we do need to perform some actions.
|
||||
*
|
||||
* The most important is freeClientsInAsyncFreeQueue but we also
|
||||
* call some other low-risk functions. */
|
||||
void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
int iel = ielFromEventLoop(eventLoop);
|
||||
|
||||
@ -2295,21 +2345,6 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
if (g_pserver->active_expire_enabled && (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica))
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);
|
||||
|
||||
/* Send all the slaves an ACK request if at least one client blocked
|
||||
* during the previous event loop iteration. */
|
||||
if (g_pserver->get_ack_from_slaves) {
|
||||
robj *argv[3];
|
||||
|
||||
argv[0] = createStringObject("REPLCONF",8);
|
||||
argv[1] = createStringObject("GETACK",6);
|
||||
argv[2] = createStringObject("*",1); /* Not used argument. */
|
||||
replicationFeedSlaves(g_pserver->slaves, g_pserver->replicaseldb, argv, 3);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
decrRefCount(argv[2]);
|
||||
g_pserver->get_ack_from_slaves = 0;
|
||||
}
|
||||
|
||||
/* Unblock all the clients blocked for synchronous replication
|
||||
* in WAIT. */
|
||||
if (listLength(g_pserver->clients_waiting_acks))
|
||||
@ -2325,6 +2360,24 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
processUnblockedClients(iel);
|
||||
}
|
||||
|
||||
/* Send all the slaves an ACK request if at least one client blocked
|
||||
* during the previous event loop iteration. Note that we do this after
|
||||
* processUnblockedClients(), so if there are multiple pipelined WAITs
|
||||
* and the just unblocked WAIT gets blocked again, we don't have to wait
|
||||
* a server cron cycle in absence of other event loop events. See #6623. */
|
||||
if (g_pserver->get_ack_from_slaves) {
|
||||
robj *argv[3];
|
||||
|
||||
argv[0] = createStringObject("REPLCONF",8);
|
||||
argv[1] = createStringObject("GETACK",6);
|
||||
argv[2] = createStringObject("*",1); /* Not used argument. */
|
||||
replicationFeedSlaves(g_pserver->slaves, g_pserver->replicaseldb, argv, 3);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
decrRefCount(argv[2]);
|
||||
g_pserver->get_ack_from_slaves = 0;
|
||||
}
|
||||
|
||||
/* Send the invalidation messages to clients participating to the
|
||||
* client side caching protocol in broadcasting (BCAST) mode. */
|
||||
trackingBroadcastInvalidationMessages();
|
||||
@ -2547,6 +2600,7 @@ void initServerConfig(void) {
|
||||
g_pserver->slaves = listCreate();
|
||||
g_pserver->monitors = listCreate();
|
||||
g_pserver->clients_timeout_table = raxNew();
|
||||
g_pserver->events_processed_while_blocked = 0;
|
||||
g_pserver->timezone = getTimeZone(); /* Initialized by tzset(). */
|
||||
cserver.configfile = NULL;
|
||||
cserver.executable = NULL;
|
||||
@ -3062,9 +3116,9 @@ static void initNetworkingThread(int iel, int fReusePort)
|
||||
|
||||
static void initNetworking(int fReusePort)
|
||||
{
|
||||
int celListen = (fReusePort) ? cserver.cthreads : 1;
|
||||
for (int iel = 0; iel < celListen; ++iel)
|
||||
initNetworkingThread(iel, fReusePort);
|
||||
// We only initialize the main thread here, since RDB load is a special case that processes
|
||||
// clients before our server threads are launched.
|
||||
initNetworkingThread(IDX_EVENT_LOOP_MAIN, fReusePort);
|
||||
|
||||
/* Open the listening Unix domain socket. */
|
||||
if (g_pserver->unixsocket != NULL) {
|
||||
@ -3096,6 +3150,8 @@ static void initServerThread(struct redisServerThreadVars *pvar, int fMain)
|
||||
pvar->tlsfd_count = 0;
|
||||
pvar->cclients = 0;
|
||||
pvar->el = aeCreateEventLoop(g_pserver->maxclients+CONFIG_FDSET_INCR);
|
||||
aeSetBeforeSleepProc(pvar->el, beforeSleep, AE_SLEEP_THREADSAFE);
|
||||
aeSetAfterSleepProc(pvar->el, afterSleep, AE_SLEEP_THREADSAFE);
|
||||
pvar->current_client = nullptr;
|
||||
pvar->clients_paused = 0;
|
||||
pvar->fRetrySetAofEvent = false;
|
||||
@ -3230,6 +3286,8 @@ void initServer(void) {
|
||||
g_pserver->stat_rdb_cow_bytes = 0;
|
||||
g_pserver->stat_aof_cow_bytes = 0;
|
||||
g_pserver->stat_module_cow_bytes = 0;
|
||||
for (int j = 0; j < CLIENT_TYPE_COUNT; j++)
|
||||
g_pserver->stat_clients_type_memory[j] = 0;
|
||||
g_pserver->cron_malloc_stats.zmalloc_used = 0;
|
||||
g_pserver->cron_malloc_stats.process_rss = 0;
|
||||
g_pserver->cron_malloc_stats.allocator_allocated = 0;
|
||||
@ -3479,8 +3537,13 @@ struct redisCommand *lookupCommandOrOriginal(sds name) {
|
||||
* + PROPAGATE_AOF (propagate into the AOF file if is enabled)
|
||||
* + PROPAGATE_REPL (propagate into the replication link)
|
||||
*
|
||||
* This should not be used inside commands implementation. Use instead
|
||||
* alsoPropagate(), preventCommandPropagation(), forceCommandPropagation().
|
||||
* This should not be used inside commands implementation since it will not
|
||||
* wrap the resulting commands in MULTI/EXEC. Use instead alsoPropagate(),
|
||||
* preventCommandPropagation(), forceCommandPropagation().
|
||||
*
|
||||
* However for functions that need to (also) propagate out of the context of a
|
||||
* command execution, for example when serving a blocked client, you
|
||||
* want to use propagate().
|
||||
*/
|
||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
|
||||
int flags)
|
||||
@ -3591,8 +3654,8 @@ void call(client *c, int flags) {
|
||||
|
||||
serverTL->fixed_time_expire++;
|
||||
|
||||
/* Sent the command to clients in MONITOR mode, only if the commands are
|
||||
* not generated from reading an AOF. */
|
||||
/* Send the command to clients in MONITOR mode if applicable.
|
||||
* Administrative commands are considered too dangerous to be shown. */
|
||||
if (listLength(g_pserver->monitors) &&
|
||||
!g_pserver->loading &&
|
||||
!(c->cmd->flags & (CMD_SKIP_MONITOR|CMD_ADMIN)))
|
||||
@ -3761,8 +3824,7 @@ void call(client *c, int flags) {
|
||||
* If C_OK is returned the client is still alive and valid and
|
||||
* other operations can be performed by the caller. Otherwise
|
||||
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
|
||||
int processCommand(client *c, int callFlags) {
|
||||
AeLocker locker;
|
||||
int processCommand(client *c, int callFlags, AeLocker &locker) {
|
||||
AssertCorrectThread(c);
|
||||
|
||||
if (moduleHasCommandFilters())
|
||||
@ -3891,6 +3953,13 @@ int processCommand(client *c, int callFlags) {
|
||||
addReply(c, shared.oomerr);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Save out_of_memory result at script start, otherwise if we check OOM
|
||||
* untill first write within script, memory used by lua stack and
|
||||
* arguments might interfere. */
|
||||
if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand) {
|
||||
g_pserver->lua_oom = out_of_memory;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure to use a reasonable amount of memory for client side
|
||||
@ -4031,6 +4100,15 @@ int processCommand(client *c, int callFlags) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
bool client::postFunction(std::function<void(client *)> fn) {
|
||||
this->casyncOpsPending++;
|
||||
return aePostFunction(g_pserver->rgthreadvar[this->iel].el, [this, fn]{
|
||||
std::lock_guard<decltype(this->lock)> lock(this->lock);
|
||||
--casyncOpsPending;
|
||||
fn(this);
|
||||
}) == AE_OK;
|
||||
}
|
||||
|
||||
/*================================== Shutdown =============================== */
|
||||
|
||||
/* Close listening sockets. Also unlink the unix domain socket if
|
||||
@ -4485,7 +4563,7 @@ sds genRedisInfoString(const char *section) {
|
||||
size_t zmalloc_used = zmalloc_used_memory();
|
||||
size_t total_system_mem = cserver.system_memory_size;
|
||||
const char *evict_policy = evictPolicyToString();
|
||||
long long memory_lua = (long long)lua_gc(g_pserver->lua,LUA_GCCOUNT,0)*1024;
|
||||
long long memory_lua = g_pserver->lua ? (long long)lua_gc(g_pserver->lua,LUA_GCCOUNT,0)*1024 : 0;
|
||||
struct redisMemOverhead *mh = getMemoryOverheadData();
|
||||
|
||||
/* Peak memory is updated from time to time by serverCron() so it
|
||||
@ -4723,7 +4801,8 @@ sds genRedisInfoString(const char *section) {
|
||||
"active_defrag_key_misses:%lld\r\n"
|
||||
"tracking_total_keys:%lld\r\n"
|
||||
"tracking_total_items:%llu\r\n"
|
||||
"unexpected_error_replies:%lld\r\n",
|
||||
"tracking_total_prefixes:%lld\r\n"
|
||||
"unexpected_error_replies:%lld\r\n",
|
||||
g_pserver->stat_numconnections,
|
||||
g_pserver->stat_numcommands,
|
||||
getInstantaneousMetric(STATS_METRIC_COMMAND),
|
||||
@ -4753,6 +4832,7 @@ sds genRedisInfoString(const char *section) {
|
||||
g_pserver->stat_active_defrag_key_misses,
|
||||
(unsigned long long) trackingGetTotalKeys(),
|
||||
(unsigned long long) trackingGetTotalItems(),
|
||||
(unsigned long long) trackingGetTotalPrefixes(),
|
||||
g_pserver->stat_unexpected_error_replies);
|
||||
}
|
||||
|
||||
@ -5360,6 +5440,14 @@ void redisSetProcTitle(const char *title) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void redisSetCpuAffinity(const char *cpulist) {
|
||||
#ifdef USE_SETCPUAFFINITY
|
||||
setcpuaffinity(cpulist);
|
||||
#else
|
||||
UNUSED(cpulist);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether systemd or upstart have been used to start redis.
|
||||
*/
|
||||
@ -5482,10 +5570,15 @@ void *workerThreadMain(void *parg)
|
||||
serverTL = g_pserver->rgthreadvar+iel; // set the TLS threadsafe global
|
||||
tlsInitThread();
|
||||
|
||||
if (iel != IDX_EVENT_LOOP_MAIN)
|
||||
{
|
||||
aeAcquireLock();
|
||||
initNetworkingThread(iel, cserver.cthreads > 1);
|
||||
aeReleaseLock();
|
||||
}
|
||||
|
||||
moduleAcquireGIL(true); // Normally afterSleep acquires this, but that won't be called on the first run
|
||||
aeEventLoop *el = g_pserver->rgthreadvar[iel].el;
|
||||
aeSetBeforeSleepProc(el, beforeSleep, AE_SLEEP_THREADSAFE);
|
||||
aeSetAfterSleepProc(el, afterSleep, AE_SLEEP_THREADSAFE);
|
||||
try
|
||||
{
|
||||
aeMain(el);
|
||||
@ -5581,6 +5674,7 @@ int main(int argc, char **argv) {
|
||||
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
|
||||
srand(time(NULL)^getpid());
|
||||
gettimeofday(&tv,NULL);
|
||||
crc64_init();
|
||||
|
||||
uint8_t hashseed[16];
|
||||
getRandomHexChars((char*)hashseed,sizeof(hashseed));
|
||||
@ -5794,6 +5888,7 @@ int main(int argc, char **argv) {
|
||||
serverLog(LL_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", g_pserver->maxmemory);
|
||||
}
|
||||
|
||||
redisSetCpuAffinity(g_pserver->server_cpulist);
|
||||
aeReleaseLock(); //Finally we can dump the lock
|
||||
moduleReleaseGIL(true);
|
||||
|
||||
|
65
src/server.h
65
src/server.h
@ -490,8 +490,11 @@ public:
|
||||
#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? */
|
||||
#define CLIENT_TRACKING_NOLOOP (1ULL<<37) /* Don't send invalidation messages
|
||||
about writes performed by myself.*/
|
||||
#define CLIENT_IN_TO_TABLE (1ULL<<38) /* This client is in the timeout table. */
|
||||
#define CLIENT_PROTOCOL_ERROR (1ULL<<39) /* Protocol error chatting with it. */
|
||||
#define CLIENT_FORCE_REPLY (1ULL<<40) /* Should addReply be forced to write the text? */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@ -514,6 +517,7 @@ public:
|
||||
#define CLIENT_TYPE_SLAVE 1 /* Slaves. */
|
||||
#define CLIENT_TYPE_PUBSUB 2 /* Clients subscribed to PubSub channels. */
|
||||
#define CLIENT_TYPE_MASTER 3 /* Master. */
|
||||
#define CLIENT_TYPE_COUNT 4 /* Total number of client types. */
|
||||
#define CLIENT_TYPE_OBUF_COUNT 3 /* Number of clients to expose to output
|
||||
buffer configuration. Just the first
|
||||
three: normal, replica, pubsub. */
|
||||
@ -839,6 +843,8 @@ typedef struct RedisModuleDigest {
|
||||
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
|
||||
|
||||
#define OBJ_SHARED_REFCOUNT (0x7FFFFFFF)
|
||||
#define OBJ_STATIC_REFCOUNT (OBJ_SHARED_REFCOUNT-1)
|
||||
#define OBJ_FIRST_SPECIAL_REFCOUNT OBJ_STATIC_REFCOUNT
|
||||
#define OBJ_MVCC_INVALID (0xFFFFFFFFFFFFFFFFULL)
|
||||
|
||||
#define MVCC_MS_SHIFT 20
|
||||
@ -859,9 +865,9 @@ public:
|
||||
void SetFExpires(bool fExpires);
|
||||
|
||||
void setrefcount(unsigned ref);
|
||||
unsigned getrefcount(std::memory_order order) const { return (refcount.load(order) & ~(1U << 31)); }
|
||||
unsigned getrefcount(std::memory_order order = std::memory_order_relaxed) const { return (refcount.load(order) & ~(1U << 31)); }
|
||||
void addref() const { refcount.fetch_add(1, std::memory_order_relaxed); }
|
||||
unsigned release() const { return refcount.fetch_sub(1, std::memory_order_relaxed) & ~(1U << 31); }
|
||||
unsigned release() const { return refcount.fetch_sub(1, std::memory_order_seq_cst) & ~(1U << 31); }
|
||||
} robj;
|
||||
static_assert(sizeof(redisObject) == 24, "object size is critical, don't increase");
|
||||
|
||||
@ -1173,7 +1179,7 @@ const char *getObjectTypeName(robj_roptr o);
|
||||
* we'll update it when the structure is changed, to avoid bugs like
|
||||
* bug #85 introduced exactly in this way. */
|
||||
#define initStaticStringObject(_var,_ptr) do { \
|
||||
_var.setrefcount(1); \
|
||||
_var.setrefcount(OBJ_STATIC_REFCOUNT); \
|
||||
_var.type = OBJ_STRING; \
|
||||
_var.encoding = OBJ_ENCODING_RAW; \
|
||||
_var.m_ptr = _ptr; \
|
||||
@ -1571,7 +1577,7 @@ typedef struct readyList {
|
||||
no AUTH is needed, and every
|
||||
connection is immediately
|
||||
authenticated. */
|
||||
typedef struct user {
|
||||
typedef struct {
|
||||
sds name; /* The username as an SDS string. */
|
||||
uint64_t flags; /* See USER_FLAG_* */
|
||||
|
||||
@ -1668,10 +1674,10 @@ typedef struct client {
|
||||
* when the authenticated user
|
||||
* changes. */
|
||||
void *auth_callback_privdata; /* Private data that is passed when the auth
|
||||
* changed callback is executed. Opaque for
|
||||
* changed callback is executed. Opaque for
|
||||
* Redis Core. */
|
||||
void *auth_module; /* The module that owns the callback, which is used
|
||||
* to disconnect the client if the module is
|
||||
* to disconnect the client if the module is
|
||||
* unloaded for cleanup. Opaque for Redis Core.*/
|
||||
|
||||
/* UUID announced by the client (default nil) - used to detect multiple connections to/from the same peer */
|
||||
@ -1686,6 +1692,13 @@ typedef struct client {
|
||||
rax *client_tracking_prefixes; /* A dictionary of prefixes we are already
|
||||
subscribed to in BCAST mode, in the
|
||||
context of client side caching. */
|
||||
/* In clientsCronTrackClientsMemUsage() we track the memory usage of
|
||||
* each client and add it to the sum of all the clients of a given type,
|
||||
* however we need to remember what was the old contribution of each
|
||||
* client, and in which categoty the client was, in order to remove it
|
||||
* before adding it the new value. */
|
||||
uint64_t client_cron_last_memory_usage;
|
||||
int client_cron_last_memory_type;
|
||||
/* Response buffer */
|
||||
int bufpos;
|
||||
char buf[PROTO_REPLY_CHUNK_BYTES];
|
||||
@ -1700,6 +1713,9 @@ typedef struct client {
|
||||
int iel; /* the event loop index we're registered with */
|
||||
struct fastlock lock;
|
||||
int master_error;
|
||||
|
||||
// post a function from a non-client thread to run on its client thread
|
||||
bool postFunction(std::function<void(client *)> fn);
|
||||
} client;
|
||||
|
||||
struct saveparam {
|
||||
@ -2055,6 +2071,8 @@ struct redisServer {
|
||||
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
|
||||
std::atomic<uint64_t> next_client_id; /* Next client unique ID. Incremental. */
|
||||
int protected_mode; /* Don't accept external connections. */
|
||||
long long events_processed_while_blocked; /* processEventsWhileBlocked() */
|
||||
|
||||
/* RDB / AOF loading information */
|
||||
std::atomic<int> loading; /* We are loading data from disk if true */
|
||||
off_t loading_total_bytes;
|
||||
@ -2096,6 +2114,7 @@ struct redisServer {
|
||||
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_module_cow_bytes; /* Copy on write bytes during module fork. */
|
||||
uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */
|
||||
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
|
||||
/* The following two are used to track instantaneous metrics, like
|
||||
* number of operations per second, network traffic. */
|
||||
@ -2328,10 +2347,12 @@ struct redisServer {
|
||||
execution. */
|
||||
int lua_kill; /* Kill the script if true. */
|
||||
int lua_always_replicate_commands; /* Default replication type. */
|
||||
int lua_oom; /* OOM detected when script start? */
|
||||
/* Lazy free */
|
||||
int lazyfree_lazy_eviction;
|
||||
int lazyfree_lazy_expire;
|
||||
int lazyfree_lazy_server_del;
|
||||
int lazyfree_lazy_user_del;
|
||||
/* Latency monitor */
|
||||
long long latency_monitor_threshold;
|
||||
dict *latency_events;
|
||||
@ -2370,6 +2391,11 @@ struct redisServer {
|
||||
int tls_auth_clients;
|
||||
redisTLSContextConfig tls_ctx_config;
|
||||
|
||||
/* cpu affinity */
|
||||
char *server_cpulist; /* cpu affinity list of redis server main/io thread. */
|
||||
char *bio_cpulist; /* cpu affinity list of bio thread. */
|
||||
char *aof_rewrite_cpulist; /* cpu affinity list of aof rewrite process. */
|
||||
char *bgsave_cpulist; /* cpu affinity list of bgsave process. */
|
||||
|
||||
bool FRdbSaveInProgress() const { return rdbThreadVars.fRdbThreadActive; }
|
||||
};
|
||||
@ -2531,6 +2557,7 @@ void exitFromChild(int retcode);
|
||||
size_t redisPopcount(const void *s, long count);
|
||||
void redisSetProcTitle(const char *title);
|
||||
int redisCommunicateSystemd(const char *sd_notify_msg);
|
||||
void redisSetCpuAffinity(const char *cpulist);
|
||||
|
||||
/* networking.c -- Networking and Client related operations */
|
||||
client *createClient(connection *conn, int iel);
|
||||
@ -2546,7 +2573,6 @@ void setDeferredSetLen(client *c, void *node, long length);
|
||||
void setDeferredAttributeLen(client *c, void *node, long length);
|
||||
void setDeferredPushLen(client *c, void *node, long length);
|
||||
void processInputBuffer(client *c, int callFlags);
|
||||
void processInputBufferAndReplicate(client *c);
|
||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
@ -2599,7 +2625,7 @@ void rewriteClientCommandVector(client *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(client *c, int i, robj *newval);
|
||||
void replaceClientCommandVector(client *c, int argc, robj **argv);
|
||||
unsigned long getClientOutputBufferMemoryUsage(client *c);
|
||||
void freeClientsInAsyncFreeQueue(int iel);
|
||||
int freeClientsInAsyncFreeQueue(int iel);
|
||||
void asyncCloseClientOnOutputBufferLimitReached(client *c);
|
||||
int getClientType(client *c);
|
||||
int getClientTypeByName(const char *name);
|
||||
@ -2611,7 +2637,7 @@ int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListe
|
||||
void pauseClients(mstime_t duration);
|
||||
int clientsArePaused(void);
|
||||
void unpauseClientsIfNecessary();
|
||||
int processEventsWhileBlocked(int iel);
|
||||
void processEventsWhileBlocked(int iel);
|
||||
int handleClientsWithPendingWrites(int iel, int aof_state);
|
||||
int clientHasPendingReplies(client *c);
|
||||
void unlinkClient(client *c);
|
||||
@ -2654,11 +2680,12 @@ void addReplyStatusFormat(client *c, const char *fmt, ...);
|
||||
void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix);
|
||||
void disableTracking(client *c);
|
||||
void trackingRememberKeys(client *c);
|
||||
void trackingInvalidateKey(robj *keyobj);
|
||||
void trackingInvalidateKey(client *c, robj *keyobj);
|
||||
void trackingInvalidateKeysOnFlush(int dbid);
|
||||
void trackingLimitUsedSlots(void);
|
||||
uint64_t trackingGetTotalItems(void);
|
||||
uint64_t trackingGetTotalKeys(void);
|
||||
uint64_t trackingGetTotalPrefixes(void);
|
||||
void trackingBroadcastInvalidationMessages(void);
|
||||
|
||||
/* List data type */
|
||||
@ -2926,7 +2953,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
size_t freeMemoryGetNotCountedMemory();
|
||||
int freeMemoryIfNeeded(bool fPreSnapshot);
|
||||
int freeMemoryIfNeededAndSafe(bool fPreSnapshot);
|
||||
int processCommand(client *c, int callFlags);
|
||||
int processCommand(client *c, int callFlags, class AeLocker &locker);
|
||||
void setupSignalHandlers(void);
|
||||
struct redisCommand *lookupCommand(sds name);
|
||||
struct redisCommand *lookupCommandByCString(const char *s);
|
||||
@ -3055,8 +3082,8 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
void dbAdd(redisDb *db, robj *key, robj *val);
|
||||
void dbOverwrite(redisDb *db, robj *key, robj *val, bool fRemoveExpire = false);
|
||||
int dbMerge(redisDb *db, robj *key, robj *val, int fReplace);
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl);
|
||||
void setKey(redisDb *db, robj *key, robj *val);
|
||||
void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal);
|
||||
void setKey(client *c, redisDb *db, robj *key, robj *val);
|
||||
int dbExists(redisDb *db, robj *key);
|
||||
robj *dbRandomKey(redisDb *db);
|
||||
int dbSyncDelete(redisDb *db, robj *key);
|
||||
@ -3073,7 +3100,7 @@ void flushAllDataAndResetRDB(int flags);
|
||||
long long dbTotalServerKeyCount();
|
||||
|
||||
int selectDb(client *c, int id);
|
||||
void signalModifiedKey(redisDb *db, robj *key);
|
||||
void signalModifiedKey(client *c, redisDb *db, robj *key);
|
||||
void signalFlushedDb(int dbid);
|
||||
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
|
||||
unsigned int countKeysInSlot(unsigned int hashslot);
|
||||
@ -3081,8 +3108,8 @@ unsigned int delKeysInSlot(unsigned int hashslot);
|
||||
int verifyClusterConfigWithData(void);
|
||||
void scanGenericCommand(client *c, robj_roptr o, unsigned long cursor);
|
||||
int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor);
|
||||
void slotToKeyAdd(robj *key);
|
||||
void slotToKeyDel(robj *key);
|
||||
void slotToKeyAdd(sds key);
|
||||
void slotToKeyDel(sds key);
|
||||
void slotToKeyFlush(void);
|
||||
int dbAsyncDelete(redisDb *db, robj *key);
|
||||
void slotToKeyFlushAsync(void);
|
||||
@ -3099,6 +3126,7 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
|
||||
int *georadiusGetKeys(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);
|
||||
int *lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
|
||||
/* Cluster */
|
||||
void clusterInit(void);
|
||||
@ -3377,6 +3405,7 @@ void xtrimCommand(client *c);
|
||||
void aclCommand(client *c);
|
||||
void replicaReplayCommand(client *c);
|
||||
void hrenameCommand(client *c);
|
||||
void stralgoCommand(client *c);
|
||||
|
||||
int FBrokenLinkToMaster();
|
||||
int FActiveMaster(client *c);
|
||||
|
148
src/setcpuaffinity.c
Normal file
148
src/setcpuaffinity.c
Normal file
@ -0,0 +1,148 @@
|
||||
/* ==========================================================================
|
||||
* setcpuaffinity.c - Linux/BSD setcpuaffinity.
|
||||
* --------------------------------------------------------------------------
|
||||
* Copyright (C) 2020 zhenwei pi
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
* persons to whom the Software is furnished to do so, subject to the
|
||||
* following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
* ==========================================================================
|
||||
*/
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#ifdef __linux__
|
||||
#include <sched.h>
|
||||
#endif
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/param.h>
|
||||
#include <sys/cpuset.h>
|
||||
#endif
|
||||
#ifdef __NetBSD__
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#endif
|
||||
#include "config.h"
|
||||
|
||||
#ifdef USE_SETCPUAFFINITY
|
||||
static const char *next_token(const char *q, int sep) {
|
||||
if (q)
|
||||
q = strchr(q, sep);
|
||||
if (q)
|
||||
q++;
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
static int next_num(const char *str, char **end, int *result) {
|
||||
if (!str || *str == '\0' || !isdigit(*str))
|
||||
return -1;
|
||||
|
||||
*result = strtoul(str, end, 10);
|
||||
if (str == *end)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set current thread cpu affinity to cpu list, this function works like
|
||||
* taskset command (actually cpulist parsing logic reference to util-linux).
|
||||
* example of this function: "0,2,3", "0,2-3", "0-20:2". */
|
||||
void setcpuaffinity(const char *cpulist) {
|
||||
const char *p, *q;
|
||||
char *end = NULL;
|
||||
#ifdef __linux__
|
||||
cpu_set_t cpuset;
|
||||
#endif
|
||||
#ifdef __FreeBSD__
|
||||
cpuset_t cpuset;
|
||||
#endif
|
||||
#ifdef __NetBSD__
|
||||
cpuset_t *cpuset;
|
||||
#endif
|
||||
|
||||
if (!cpulist)
|
||||
return;
|
||||
|
||||
#ifndef __NetBSD__
|
||||
CPU_ZERO(&cpuset);
|
||||
#else
|
||||
cpuset = cpuset_create();
|
||||
#endif
|
||||
|
||||
q = cpulist;
|
||||
while (p = q, q = next_token(q, ','), p) {
|
||||
int a, b, s;
|
||||
const char *c1, *c2;
|
||||
|
||||
if (next_num(p, &end, &a) != 0)
|
||||
return;
|
||||
|
||||
b = a;
|
||||
s = 1;
|
||||
p = end;
|
||||
|
||||
c1 = next_token(p, '-');
|
||||
c2 = next_token(p, ',');
|
||||
|
||||
if (c1 != NULL && (c2 == NULL || c1 < c2)) {
|
||||
if (next_num(c1, &end, &b) != 0)
|
||||
return;
|
||||
|
||||
c1 = end && *end ? next_token(end, ':') : NULL;
|
||||
if (c1 != NULL && (c2 == NULL || c1 < c2)) {
|
||||
if (next_num(c1, &end, &s) != 0)
|
||||
return;
|
||||
|
||||
if (s == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((a > b))
|
||||
return;
|
||||
|
||||
while (a <= b) {
|
||||
#ifndef __NetBSD__
|
||||
CPU_SET(a, &cpuset);
|
||||
#else
|
||||
cpuset_set(a, cpuset);
|
||||
#endif
|
||||
a += s;
|
||||
}
|
||||
}
|
||||
|
||||
if (end && *end)
|
||||
return;
|
||||
|
||||
#ifdef __linux__
|
||||
sched_setaffinity(0, sizeof(cpuset), &cpuset);
|
||||
#endif
|
||||
#ifdef __FreeBSD__
|
||||
cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(cpuset), &cpuset);
|
||||
#endif
|
||||
#ifdef __NetBSD__
|
||||
pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), cpuset);
|
||||
cpuset_destroy(cpuset);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* USE_SETCPUAFFINITY */
|
@ -571,12 +571,12 @@ void sortCommand(client *c) {
|
||||
}
|
||||
}
|
||||
if (outputlen) {
|
||||
setKey(c->db,storekey,sobj);
|
||||
setKey(c,c->db,storekey,sobj);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"sortstore",storekey,
|
||||
c->db->id);
|
||||
g_pserver->dirty += outputlen;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
signalModifiedKey(c->db,storekey);
|
||||
signalModifiedKey(c,c->db,storekey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
|
@ -96,6 +96,11 @@ typedef struct streamPropInfo {
|
||||
/* Prototypes of exported APIs. */
|
||||
struct client;
|
||||
|
||||
/* Flags for streamLookupConsumer */
|
||||
#define SLC_NONE 0
|
||||
#define SLC_NOCREAT (1<<0) /* Do not create the consumer if it doesn't exist */
|
||||
#define SLC_NOREFRESH (1<<1) /* Do not update consumer's seen-time */
|
||||
|
||||
stream *streamNew(void);
|
||||
void freeStream(stream *s);
|
||||
unsigned long streamLength(robj_roptr subject);
|
||||
@ -105,7 +110,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);
|
||||
void streamIteratorGetField(streamIterator *si, unsigned char **fieldptr, unsigned char **valueptr, int64_t *fieldlen, int64_t *valuelen);
|
||||
void streamIteratorStop(streamIterator *si);
|
||||
streamCG *streamLookupCG(stream *s, sds groupname);
|
||||
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create);
|
||||
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int flags);
|
||||
streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id);
|
||||
streamNACK *streamCreateNACK(streamConsumer *consumer);
|
||||
void streamDecodeID(void *buf, streamID *id);
|
||||
|
@ -521,7 +521,7 @@ void hsetnxCommand(client *c) {
|
||||
} else {
|
||||
hashTypeSet(o,szFromObj(c->argv[2]),szFromObj(c->argv[3]),HASH_SET_COPY);
|
||||
addReply(c, shared.cone);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
@ -551,7 +551,7 @@ void hsetCommand(client *c) {
|
||||
/* HMSET */
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
@ -586,7 +586,7 @@ void hincrbyCommand(client *c) {
|
||||
newstr = sdsfromlonglong(value);
|
||||
hashTypeSet(o,szFromObj(c->argv[2]),newstr,HASH_SET_TAKE_VALUE);
|
||||
addReplyLongLong(c,value);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
@ -625,7 +625,7 @@ void hincrbyfloatCommand(client *c) {
|
||||
newstr = sdsnewlen(buf,len);
|
||||
hashTypeSet(o,szFromObj(c->argv[2]),newstr,HASH_SET_TAKE_VALUE);
|
||||
addReplyBulkCBuffer(c,buf,len);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
|
||||
@ -721,7 +721,7 @@ void hdelCommand(client *c) {
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
|
||||
|
@ -217,7 +217,7 @@ void pushGenericCommand(client *c, int where) {
|
||||
if (pushed) {
|
||||
const char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
|
||||
}
|
||||
g_pserver->dirty += pushed;
|
||||
@ -247,7 +247,7 @@ void pushxGenericCommand(client *c, int where) {
|
||||
|
||||
if (pushed) {
|
||||
const char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
|
||||
}
|
||||
g_pserver->dirty += pushed;
|
||||
@ -292,7 +292,7 @@ void linsertCommand(client *c) {
|
||||
listTypeReleaseIterator(iter);
|
||||
|
||||
if (inserted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"linsert",
|
||||
c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
@ -355,7 +355,7 @@ void lsetCommand(client *c) {
|
||||
addReply(c,shared.outofrangeerr);
|
||||
} else {
|
||||
addReply(c,shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"lset",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
@ -382,7 +382,7 @@ void popGenericCommand(client *c, int where) {
|
||||
c->argv[1],c->db->id);
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
}
|
||||
@ -482,7 +482,7 @@ void ltrimCommand(client *c) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
g_pserver->dirty++;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
@ -519,7 +519,7 @@ void lremCommand(client *c) {
|
||||
listTypeReleaseIterator(li);
|
||||
|
||||
if (removed) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
@ -555,7 +555,7 @@ static void rpoplpushHandlePush(client *c, robj *dstkey, robj *dstobj, robj *val
|
||||
g_pserver->list_compress_depth);
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
listTypePush(dstobj,value,LIST_HEAD);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"lpush",dstkey,c->db->id);
|
||||
/* Always send the pushed value to the client. */
|
||||
@ -593,7 +593,7 @@ void rpoplpushCommand(client *c) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
|
||||
touchedkey,c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,touchedkey);
|
||||
signalModifiedKey(c,c->db,touchedkey);
|
||||
decrRefCount(touchedkey);
|
||||
g_pserver->dirty++;
|
||||
if (c->cmd->proc == brpoplpushCommand) {
|
||||
@ -713,7 +713,7 @@ void blockingPopGenericCommand(client *c, int where) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
|
||||
c->argv[j],c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
signalModifiedKey(c,c->db,c->argv[j]);
|
||||
g_pserver->dirty++;
|
||||
|
||||
/* Replicate it as an [LR]POP instead of B[LR]POP. */
|
||||
|
@ -285,7 +285,7 @@ void saddCommand(client *c) {
|
||||
if (setTypeAdd(set,szFromObj(c->argv[j]))) added++;
|
||||
}
|
||||
if (added) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
|
||||
}
|
||||
g_pserver->dirty += added;
|
||||
@ -310,7 +310,7 @@ void sremCommand(client *c) {
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"srem",c->argv[1],c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
|
||||
@ -363,8 +363,8 @@ void smoveCommand(client *c) {
|
||||
dbAdd(c->db,c->argv[2],dstset);
|
||||
}
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[2]);
|
||||
g_pserver->dirty++;
|
||||
|
||||
/* An extra key has changed when ele was successfully added to dstset */
|
||||
@ -449,7 +449,7 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
/* Propagate this command as an DEL operation */
|
||||
rewriteClientCommandVector(c,2,shared.del,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
g_pserver->dirty++;
|
||||
return;
|
||||
}
|
||||
@ -551,7 +551,7 @@ void spopWithCountCommand(client *c) {
|
||||
* the alsoPropagate() API. */
|
||||
decrRefCount(propargv[0]);
|
||||
preventCommandPropagation(c);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
|
||||
@ -604,7 +604,7 @@ void spopCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Set has been modified */
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
|
||||
@ -813,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
zfree(sets);
|
||||
if (dstkey) {
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
addReply(c,shared.czero);
|
||||
@ -913,7 +913,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
|
||||
dstkey,c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
setDeferredSetLen(c,replylen,cardinality);
|
||||
@ -1088,7 +1088,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
|
||||
dstkey,c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
zfree(sets);
|
||||
|
317
src/t_stream.cpp
317
src/t_stream.cpp
@ -853,7 +853,12 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
|
||||
argv[11] = createStringObject("JUSTID",6);
|
||||
argv[12] = createStringObject("LASTID",6);
|
||||
argv[13] = createObjectFromStreamID(&group->last_id);
|
||||
alsoPropagate(cserver.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
|
||||
/* We use progagate() because this code path is not always called from
|
||||
* the command execution context. Moreover this will just alter the
|
||||
* consumer group state, and we don't need MULTI/EXEC wrapping because
|
||||
* there is no message state cross-message atomicity required. */
|
||||
propagate(cserver.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[3]);
|
||||
decrRefCount(argv[4]);
|
||||
@ -880,7 +885,12 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
|
||||
argv[2] = key;
|
||||
argv[3] = groupname;
|
||||
argv[4] = createObjectFromStreamID(&group->last_id);
|
||||
alsoPropagate(cserver.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
|
||||
/* We use progagate() because this code path is not always called from
|
||||
* the command execution context. Moreover this will just alter the
|
||||
* consumer group state, and we don't need MULTI/EXEC wrapping because
|
||||
* there is no message state cross-message atomicity required. */
|
||||
propagate(cserver.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
decrRefCount(argv[4]);
|
||||
@ -941,6 +951,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
int64_t numfields;
|
||||
streamID id;
|
||||
int propagate_last_id = 0;
|
||||
int noack = flags & STREAM_RWR_NOACK;
|
||||
|
||||
/* If the client is asking for some history, we serve it using a
|
||||
* different function, so that we return entries *solely* from its
|
||||
@ -959,7 +970,10 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
/* Update the group last_id if needed. */
|
||||
if (group && streamCompareID(&id,&group->last_id) > 0) {
|
||||
group->last_id = id;
|
||||
propagate_last_id = 1;
|
||||
/* Group last ID should be propagated only if NOACK was
|
||||
* specified, otherwise the last id will be included
|
||||
* in the propagation of XCLAIM itself. */
|
||||
if (noack) propagate_last_id = 1;
|
||||
}
|
||||
|
||||
/* Emit a two elements array for each item. The first is
|
||||
@ -967,7 +981,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
addReplyArrayLenAsync(c,2);
|
||||
addReplyStreamIDAsync(c,&id);
|
||||
|
||||
addReplyMapLenAsync(c,numfields);
|
||||
addReplyArrayLenAsync(c,numfields*2);
|
||||
|
||||
/* Emit the field-value pairs. */
|
||||
while(numfields--) {
|
||||
@ -987,7 +1001,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
* XGROUP SETID command. So if we find that there is already
|
||||
* a NACK for the entry, we need to associate it to the new
|
||||
* consumer. */
|
||||
if (group && !(flags & STREAM_RWR_NOACK)) {
|
||||
if (group && !noack) {
|
||||
unsigned char buf[sizeof(streamID)];
|
||||
streamEncodeID(buf,&id);
|
||||
|
||||
@ -1024,14 +1038,15 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
streamPropagateXCLAIM(c,spi->keyname,group,spi->groupname,idarg,nack);
|
||||
decrRefCount(idarg);
|
||||
}
|
||||
} else {
|
||||
if (propagate_last_id)
|
||||
streamPropagateGroupID(c,spi->keyname,group,spi->groupname);
|
||||
}
|
||||
|
||||
arraylen++;
|
||||
if (count && count == arraylen) break;
|
||||
}
|
||||
|
||||
if (spi && propagate_last_id)
|
||||
streamPropagateGroupID(c,spi->keyname,group,spi->groupname);
|
||||
|
||||
streamIteratorStop(&si);
|
||||
if (arraylen_ptr) setDeferredArrayLenAsync(c,arraylen_ptr,arraylen);
|
||||
return arraylen;
|
||||
@ -1267,7 +1282,7 @@ void xaddCommand(client *c) {
|
||||
}
|
||||
addReplyStreamID(c,&id);
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xadd",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
|
||||
@ -1381,6 +1396,11 @@ void xreadCommand(client *c) {
|
||||
int moreargs = c->argc-i-1;
|
||||
char *o = szFromObj(c->argv[i]);
|
||||
if (!strcasecmp(o,"BLOCK") && moreargs) {
|
||||
if (c->flags & CLIENT_LUA) {
|
||||
/* There is no sense to use BLOCK option within LUA */
|
||||
addReplyErrorFormat(c, "%s command is not allowed with BLOCK option from scripts", szFromObj(c->argv[0]));
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[i],&timeout,
|
||||
UNIT_MILLISECONDS) != C_OK) return;
|
||||
@ -1556,7 +1576,8 @@ void xreadCommand(client *c) {
|
||||
addReplyBulk(c,c->argv[streams_arg+i]);
|
||||
streamConsumer *consumer = NULL;
|
||||
if (groups) consumer = streamLookupConsumer(groups[i],
|
||||
szFromObj(consumername),1);
|
||||
szFromObj(consumername),
|
||||
SLC_NONE);
|
||||
streamPropInfo spi = {c->argv[i+streams_arg],groupname};
|
||||
int flags = 0;
|
||||
if (noack) flags |= STREAM_RWR_NOACK;
|
||||
@ -1692,7 +1713,9 @@ streamCG *streamLookupCG(stream *s, sds groupname) {
|
||||
* consumer does not exist it is automatically created as a side effect
|
||||
* of calling this function, otherwise its last seen time is updated and
|
||||
* the existing consumer reference returned. */
|
||||
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
|
||||
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int flags) {
|
||||
int create = !(flags & SLC_NOCREAT);
|
||||
int refresh = !(flags & SLC_NOREFRESH);
|
||||
streamConsumer *consumer = (streamConsumer*)raxFind(cg->consumers,(unsigned char*)name,
|
||||
sdslen(name));
|
||||
if (consumer == raxNotFound) {
|
||||
@ -1703,7 +1726,7 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
|
||||
raxInsert(cg->consumers,(unsigned char*)name,sdslen(name),
|
||||
consumer,NULL);
|
||||
}
|
||||
consumer->seen_time = mstime();
|
||||
if (refresh) consumer->seen_time = mstime();
|
||||
return consumer;
|
||||
}
|
||||
|
||||
@ -1711,7 +1734,8 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
|
||||
* may have pending messages: they are removed from the PEL, and the number
|
||||
* of pending messages "lost" is returned. */
|
||||
uint64_t streamDelConsumer(streamCG *cg, sds name) {
|
||||
streamConsumer *consumer = streamLookupConsumer(cg,name,0);
|
||||
streamConsumer *consumer =
|
||||
streamLookupConsumer(cg,name,SLC_NOCREAT|SLC_NOREFRESH);
|
||||
if (consumer == NULL) return 0;
|
||||
|
||||
uint64_t retval = raxSize(consumer->pel);
|
||||
@ -1927,11 +1951,21 @@ void xackCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Start parsing the IDs, so that we abort ASAP if there is a syntax
|
||||
* error: the return value of this command cannot be an error in case
|
||||
* the client successfully acknowledged some messages, so it should be
|
||||
* executed in a "all or nothing" fashion. */
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
streamID id;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
}
|
||||
|
||||
int acknowledged = 0;
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
streamID id;
|
||||
unsigned char buf[sizeof(streamID)];
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK)
|
||||
serverPanic("StreamID invalid after check. Should not be possible.");
|
||||
streamEncodeID(buf,&id);
|
||||
|
||||
/* Lookup the ID in the group PEL: it will have a reference to the
|
||||
@ -2044,15 +2078,18 @@ void xpendingCommand(client *c) {
|
||||
}
|
||||
/* XPENDING <key> <group> <start> <stop> <count> [<consumer>] variant. */
|
||||
else {
|
||||
streamConsumer *consumer = consumername ?
|
||||
streamLookupConsumer(group,szFromObj(consumername),0):
|
||||
NULL;
|
||||
streamConsumer *consumer = NULL;
|
||||
if (consumername) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
szFromObj(consumername),
|
||||
SLC_NOCREAT|SLC_NOREFRESH);
|
||||
|
||||
/* If a consumer name was mentioned but it does not exist, we can
|
||||
* just return an empty array. */
|
||||
if (consumername && consumer == NULL) {
|
||||
addReplyArrayLen(c,0);
|
||||
return;
|
||||
/* If a consumer name was mentioned but it does not exist, we can
|
||||
* just return an empty array. */
|
||||
if (consumer == NULL) {
|
||||
addReplyArrayLen(c,0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rax *pel = consumer ? consumer->pel : group->pel;
|
||||
@ -2314,7 +2351,7 @@ void xclaimCommand(client *c) {
|
||||
raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
|
||||
/* Update the consumer and idle time. */
|
||||
if (consumer == NULL)
|
||||
consumer = streamLookupConsumer(group,szFromObj(c->argv[3]),1);
|
||||
consumer = streamLookupConsumer(group,szFromObj(c->argv[3]),SLC_NONE);
|
||||
nack->consumer = consumer;
|
||||
nack->delivery_time = deliverytime;
|
||||
/* Set the delivery attempts counter if given, otherwise
|
||||
@ -2380,7 +2417,7 @@ void xdelCommand(client *c) {
|
||||
|
||||
/* Propagate the write if needed. */
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xdel",c->argv[1],c->db->id);
|
||||
g_pserver->dirty += deleted;
|
||||
}
|
||||
@ -2457,7 +2494,7 @@ void xtrimCommand(client *c) {
|
||||
|
||||
/* Propagate the write if needed. */
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xtrim",c->argv[1],c->db->id);
|
||||
g_pserver->dirty += deleted;
|
||||
if (approx_maxlen) streamRewriteApproxMaxlen(c,s,maxlen_arg_idx);
|
||||
@ -2465,16 +2502,204 @@ void xtrimCommand(client *c) {
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
/* Helper function for xinfoCommand.
|
||||
* Handles the variants of XINFO STREAM */
|
||||
void xinfoReplyWithStreamInfo(client *c, stream *s) {
|
||||
int full = 1;
|
||||
long long count = 10; /* Default COUNT is 10 so we don't block the server */
|
||||
robj **optv = c->argv + 3; /* Options start after XINFO STREAM <key> */
|
||||
int optc = c->argc - 3;
|
||||
|
||||
/* Parse options. */
|
||||
if (optc == 0) {
|
||||
full = 0;
|
||||
} else {
|
||||
/* Valid options are [FULL] or [FULL COUNT <count>] */
|
||||
if (optc != 1 && optc != 3) {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* First option must be "FULL" */
|
||||
if (strcasecmp(szFromObj(optv[0]),"full")) {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (optc == 3) {
|
||||
/* First option must be "FULL" */
|
||||
if (strcasecmp(szFromObj(optv[1]),"count")) {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
if (getLongLongFromObjectOrReply(c,optv[2],&count,NULL) == C_ERR)
|
||||
return;
|
||||
if (count < 0) count = 10;
|
||||
}
|
||||
}
|
||||
|
||||
addReplyMapLen(c,full ? 6 : 7);
|
||||
addReplyBulkCString(c,"length");
|
||||
addReplyLongLong(c,s->length);
|
||||
addReplyBulkCString(c,"radix-tree-keys");
|
||||
addReplyLongLong(c,raxSize(s->prax));
|
||||
addReplyBulkCString(c,"radix-tree-nodes");
|
||||
addReplyLongLong(c,s->prax->numnodes);
|
||||
addReplyBulkCString(c,"last-generated-id");
|
||||
addReplyStreamID(c,&s->last_id);
|
||||
|
||||
if (!full) {
|
||||
/* XINFO STREAM <key> */
|
||||
|
||||
addReplyBulkCString(c,"groups");
|
||||
addReplyLongLong(c,s->cgroups ? raxSize(s->cgroups) : 0);
|
||||
|
||||
/* To emit the first/last entry we use streamReplyWithRange(). */
|
||||
int emitted;
|
||||
streamID start, end;
|
||||
start.ms = start.seq = 0;
|
||||
end.ms = end.seq = UINT64_MAX;
|
||||
addReplyBulkCString(c,"first-entry");
|
||||
emitted = streamReplyWithRange(c,s,&start,&end,1,0,NULL,NULL,
|
||||
STREAM_RWR_RAWENTRIES,NULL);
|
||||
if (!emitted) addReplyNull(c);
|
||||
addReplyBulkCString(c,"last-entry");
|
||||
emitted = streamReplyWithRange(c,s,&start,&end,1,1,NULL,NULL,
|
||||
STREAM_RWR_RAWENTRIES,NULL);
|
||||
if (!emitted) addReplyNull(c);
|
||||
} else {
|
||||
/* XINFO STREAM <key> FULL [COUNT <count>] */
|
||||
|
||||
/* Stream entries */
|
||||
addReplyBulkCString(c,"entries");
|
||||
streamReplyWithRange(c,s,NULL,NULL,count,0,NULL,NULL,0,NULL);
|
||||
|
||||
/* Consumer groups */
|
||||
addReplyBulkCString(c,"groups");
|
||||
if (s->cgroups == NULL) {
|
||||
addReplyArrayLen(c,0);
|
||||
} else {
|
||||
addReplyArrayLen(c,raxSize(s->cgroups));
|
||||
raxIterator ri_cgroups;
|
||||
raxStart(&ri_cgroups,s->cgroups);
|
||||
raxSeek(&ri_cgroups,"^",NULL,0);
|
||||
while(raxNext(&ri_cgroups)) {
|
||||
streamCG *cg = (streamCG*) ri_cgroups.data;
|
||||
addReplyMapLen(c,5);
|
||||
|
||||
/* Name */
|
||||
addReplyBulkCString(c,"name");
|
||||
addReplyBulkCBuffer(c,ri_cgroups.key,ri_cgroups.key_len);
|
||||
|
||||
/* Last delivered ID */
|
||||
addReplyBulkCString(c,"last-delivered-id");
|
||||
addReplyStreamID(c,&cg->last_id);
|
||||
|
||||
/* Group PEL count */
|
||||
addReplyBulkCString(c,"pel-count");
|
||||
addReplyLongLong(c,raxSize(cg->pel));
|
||||
|
||||
/* Group PEL */
|
||||
addReplyBulkCString(c,"pending");
|
||||
long long arraylen_cg_pel = 0;
|
||||
void *arrayptr_cg_pel = addReplyDeferredLen(c);
|
||||
raxIterator ri_cg_pel;
|
||||
raxStart(&ri_cg_pel,cg->pel);
|
||||
raxSeek(&ri_cg_pel,"^",NULL,0);
|
||||
while(raxNext(&ri_cg_pel) && (!count || arraylen_cg_pel < count)) {
|
||||
streamNACK *nack = (streamNACK*) ri_cg_pel.data;
|
||||
addReplyArrayLen(c,4);
|
||||
|
||||
/* Entry ID. */
|
||||
streamID id;
|
||||
streamDecodeID(ri_cg_pel.key,&id);
|
||||
addReplyStreamID(c,&id);
|
||||
|
||||
/* Consumer name. */
|
||||
addReplyBulkCBuffer(c,nack->consumer->name,
|
||||
sdslen(nack->consumer->name));
|
||||
|
||||
/* Last delivery. */
|
||||
addReplyLongLong(c,nack->delivery_time);
|
||||
|
||||
/* Number of deliveries. */
|
||||
addReplyLongLong(c,nack->delivery_count);
|
||||
|
||||
arraylen_cg_pel++;
|
||||
}
|
||||
setDeferredArrayLen(c,arrayptr_cg_pel,arraylen_cg_pel);
|
||||
raxStop(&ri_cg_pel);
|
||||
|
||||
/* Consumers */
|
||||
addReplyBulkCString(c,"consumers");
|
||||
addReplyArrayLen(c,raxSize(cg->consumers));
|
||||
raxIterator ri_consumers;
|
||||
raxStart(&ri_consumers,cg->consumers);
|
||||
raxSeek(&ri_consumers,"^",NULL,0);
|
||||
while(raxNext(&ri_consumers)) {
|
||||
streamConsumer *consumer = (streamConsumer*)ri_consumers.data;
|
||||
addReplyMapLen(c,4);
|
||||
|
||||
/* Consumer name */
|
||||
addReplyBulkCString(c,"name");
|
||||
addReplyBulkCBuffer(c,consumer->name,sdslen(consumer->name));
|
||||
|
||||
/* Seen-time */
|
||||
addReplyBulkCString(c,"seen-time");
|
||||
addReplyLongLong(c,consumer->seen_time);
|
||||
|
||||
/* Consumer PEL count */
|
||||
addReplyBulkCString(c,"pel-count");
|
||||
addReplyLongLong(c,raxSize(consumer->pel));
|
||||
|
||||
/* Consumer PEL */
|
||||
addReplyBulkCString(c,"pending");
|
||||
long long arraylen_cpel = 0;
|
||||
void *arrayptr_cpel = addReplyDeferredLen(c);
|
||||
raxIterator ri_cpel;
|
||||
raxStart(&ri_cpel,consumer->pel);
|
||||
raxSeek(&ri_cpel,"^",NULL,0);
|
||||
while(raxNext(&ri_cpel) && (!count || arraylen_cpel < count)) {
|
||||
streamNACK *nack = (streamNACK*)ri_cpel.data;
|
||||
addReplyArrayLen(c,3);
|
||||
|
||||
/* Entry ID. */
|
||||
streamID id;
|
||||
streamDecodeID(ri_cpel.key,&id);
|
||||
addReplyStreamID(c,&id);
|
||||
|
||||
/* Last delivery. */
|
||||
addReplyLongLong(c,nack->delivery_time);
|
||||
|
||||
/* Number of deliveries. */
|
||||
addReplyLongLong(c,nack->delivery_count);
|
||||
|
||||
arraylen_cpel++;
|
||||
}
|
||||
setDeferredArrayLen(c,arrayptr_cpel,arraylen_cpel);
|
||||
raxStop(&ri_cpel);
|
||||
}
|
||||
raxStop(&ri_consumers);
|
||||
}
|
||||
raxStop(&ri_cgroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* XINFO CONSUMERS <key> <group>
|
||||
* XINFO GROUPS <key>
|
||||
* XINFO STREAM <key>
|
||||
* XINFO STREAM <key> [FULL [COUNT <count>]]
|
||||
* XINFO HELP. */
|
||||
void xinfoCommand(client *c) {
|
||||
const char *help[] = {
|
||||
"CONSUMERS <key> <groupname> -- Show consumer groups of group <groupname>.",
|
||||
"GROUPS <key> -- Show the stream consumer groups.",
|
||||
"STREAM <key> -- Show information about the stream.",
|
||||
"HELP -- Print this help.",
|
||||
"CONSUMERS <key> <groupname> -- Show consumer groups of group <groupname>.",
|
||||
"GROUPS <key> -- Show the stream consumer groups.",
|
||||
"STREAM <key> [FULL [COUNT <count>]] -- Show information about the stream.",
|
||||
" FULL will return the full state of the stream,",
|
||||
" including all entries, groups, consumers and PELs.",
|
||||
" It's possible to show only the first stream/PEL entries",
|
||||
" by using the COUNT modifier (Default is 10)",
|
||||
"HELP -- Print this help.",
|
||||
NULL
|
||||
};
|
||||
stream *s = NULL;
|
||||
@ -2554,36 +2779,10 @@ NULL
|
||||
addReplyStreamID(c,&cg->last_id);
|
||||
}
|
||||
raxStop(&ri);
|
||||
} else if (!strcasecmp(opt,"STREAM") && c->argc == 3) {
|
||||
/* XINFO STREAM <key> (or the alias XINFO <key>). */
|
||||
addReplyMapLen(c,7);
|
||||
addReplyBulkCString(c,"length");
|
||||
addReplyLongLong(c,s->length);
|
||||
addReplyBulkCString(c,"radix-tree-keys");
|
||||
addReplyLongLong(c,raxSize(s->prax));
|
||||
addReplyBulkCString(c,"radix-tree-nodes");
|
||||
addReplyLongLong(c,s->prax->numnodes);
|
||||
addReplyBulkCString(c,"groups");
|
||||
addReplyLongLong(c,s->cgroups ? raxSize(s->cgroups) : 0);
|
||||
addReplyBulkCString(c,"last-generated-id");
|
||||
addReplyStreamID(c,&s->last_id);
|
||||
|
||||
/* To emit the first/last entry we us the streamReplyWithRange()
|
||||
* API. */
|
||||
int count;
|
||||
streamID start, end;
|
||||
start.ms = start.seq = 0;
|
||||
end.ms = end.seq = UINT64_MAX;
|
||||
addReplyBulkCString(c,"first-entry");
|
||||
count = streamReplyWithRange(c,s,&start,&end,1,0,NULL,NULL,
|
||||
STREAM_RWR_RAWENTRIES,NULL);
|
||||
if (!count) addReplyNull(c);
|
||||
addReplyBulkCString(c,"last-entry");
|
||||
count = streamReplyWithRange(c,s,&start,&end,1,1,NULL,NULL,
|
||||
STREAM_RWR_RAWENTRIES,NULL);
|
||||
if (!count) addReplyNull(c);
|
||||
} else if (!strcasecmp(opt,"STREAM")) {
|
||||
/* XINFO STREAM <key> [FULL [COUNT <count>]]. */
|
||||
xinfoReplyWithStreamInfo(c,s);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
232
src/t_string.cpp
232
src/t_string.cpp
@ -84,7 +84,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
return;
|
||||
}
|
||||
genericSetKey(c->db,key,val,flags & OBJ_SET_KEEPTTL);
|
||||
genericSetKey(c,c->db,key,val,flags & OBJ_SET_KEEPTTL,1);
|
||||
g_pserver->dirty++;
|
||||
if (expire) setExpire(c,c->db,key,nullptr,mstime()+milliseconds);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
|
||||
@ -183,7 +183,7 @@ void getCommand(client *c) {
|
||||
void getsetCommand(client *c) {
|
||||
if (getGenericCommand(c) == C_ERR) return;
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setKey(c->db,c->argv[1],c->argv[2]);
|
||||
setKey(c,c->db,c->argv[1],c->argv[2]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
@ -240,7 +240,7 @@ void setrangeCommand(client *c) {
|
||||
if (sdslen(value) > 0) {
|
||||
o->m_ptr = sdsgrowzero((sds)ptrFromObj(o),offset+sdslen(value));
|
||||
memcpy((char*)ptrFromObj(o)+offset,value,sdslen(value));
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,
|
||||
"setrange",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
@ -329,7 +329,7 @@ void msetGenericCommand(client *c, int nx) {
|
||||
|
||||
for (j = 1; j < c->argc; j += 2) {
|
||||
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
|
||||
setKey(c->db,c->argv[j],c->argv[j+1]);
|
||||
setKey(c,c->db,c->argv[j],c->argv[j+1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id);
|
||||
}
|
||||
g_pserver->dirty += (c->argc-1)/2;
|
||||
@ -374,7 +374,7 @@ void incrDecrCommand(client *c, long long incr) {
|
||||
dbAdd(c->db,c->argv[1],newObj);
|
||||
}
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
addReply(c,shared.colon);
|
||||
@ -424,7 +424,7 @@ void incrbyfloatCommand(client *c) {
|
||||
dbOverwrite(c->db,c->argv[1],newObj);
|
||||
else
|
||||
dbAdd(c->db,c->argv[1],newObj);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
addReplyBulk(c,newObj);
|
||||
@ -468,7 +468,7 @@ void appendCommand(client *c) {
|
||||
o->m_ptr = sdscatlen((sds)ptrFromObj(o),ptrFromObj(append),sdslen((sds)ptrFromObj(append)));
|
||||
totlen = sdslen((sds)ptrFromObj(o));
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id);
|
||||
g_pserver->dirty++;
|
||||
addReplyLongLong(c,totlen);
|
||||
@ -480,3 +480,221 @@ void strlenCommand(client *c) {
|
||||
checkType(c,o,OBJ_STRING)) return;
|
||||
addReplyLongLong(c,stringObjectLen(o));
|
||||
}
|
||||
|
||||
|
||||
/* STRALGO -- Implement complex algorithms on strings.
|
||||
*
|
||||
* STRALGO <algorithm> ... arguments ... */
|
||||
void stralgoLCS(client *c); /* This implements the LCS algorithm. */
|
||||
void stralgoCommand(client *c) {
|
||||
/* Select the algorithm. */
|
||||
if (!strcasecmp(szFromObj(c->argv[1]),"lcs")) {
|
||||
stralgoLCS(c);
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
}
|
||||
}
|
||||
|
||||
/* STRALGO <algo> [IDX] [MINMATCHLEN <len>] [WITHMATCHLEN]
|
||||
* STRINGS <string> <string> | KEYS <keya> <keyb>
|
||||
*/
|
||||
void stralgoLCS(client *c) {
|
||||
uint32_t i, j;
|
||||
long long minmatchlen = 0;
|
||||
const char *a = NULL;
|
||||
const char *b = NULL;
|
||||
int getlen = 0, getidx = 0, withmatchlen = 0;
|
||||
robj_roptr obja, objb;
|
||||
|
||||
for (j = 2; j < (uint32_t)c->argc; j++) {
|
||||
char *opt = szFromObj(c->argv[j]);
|
||||
int moreargs = (c->argc-1) - j;
|
||||
|
||||
if (!strcasecmp(opt,"IDX")) {
|
||||
getidx = 1;
|
||||
} else if (!strcasecmp(opt,"LEN")) {
|
||||
getlen = 1;
|
||||
} else if (!strcasecmp(opt,"WITHMATCHLEN")) {
|
||||
withmatchlen = 1;
|
||||
} else if (!strcasecmp(opt,"MINMATCHLEN") && moreargs) {
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j+1],&minmatchlen,NULL)
|
||||
!= C_OK) return;
|
||||
if (minmatchlen < 0) minmatchlen = 0;
|
||||
j++;
|
||||
} else if (!strcasecmp(opt,"STRINGS")) {
|
||||
if (a != NULL) {
|
||||
addReplyError(c,"Either use STRINGS or KEYS");
|
||||
return;
|
||||
}
|
||||
a = szFromObj(c->argv[j+1]);
|
||||
b = szFromObj(c->argv[j+2]);
|
||||
j += 2;
|
||||
} else if (!strcasecmp(opt,"KEYS")) {
|
||||
if (a != NULL) {
|
||||
addReplyError(c,"Either use STRINGS or KEYS");
|
||||
return;
|
||||
}
|
||||
obja = lookupKeyRead(c->db,c->argv[j+1]);
|
||||
objb = lookupKeyRead(c->db,c->argv[j+2]);
|
||||
obja = obja ? getDecodedObject(obja) : createStringObject("",0);
|
||||
objb = objb ? getDecodedObject(objb) : createStringObject("",0);
|
||||
a = szFromObj(obja);
|
||||
b = szFromObj(objb);
|
||||
j += 2;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Complain if the user passed ambiguous parameters. */
|
||||
if (a == NULL) {
|
||||
addReplyError(c,"Please specify two strings: "
|
||||
"STRINGS or KEYS options are mandatory");
|
||||
return;
|
||||
} else if (getlen && getidx) {
|
||||
addReplyError(c,
|
||||
"If you want both the length and indexes, please "
|
||||
"just use IDX.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute the LCS using the vanilla dynamic programming technique of
|
||||
* building a table of LCS(x,y) substrings. */
|
||||
uint32_t alen = sdslen(a);
|
||||
uint32_t blen = sdslen(b);
|
||||
|
||||
/* Setup an uint32_t array to store at LCS[i,j] the length of the
|
||||
* LCS A0..i-1, B0..j-1. Note that we have a linear array here, so
|
||||
* we index it as LCS[j+(blen+1)*j] */
|
||||
uint32_t *lcs = (uint32_t*)zmalloc((alen+1)*(blen+1)*sizeof(uint32_t));
|
||||
#define LCS(A,B) lcs[(B)+((A)*(blen+1))]
|
||||
|
||||
/* Start building the LCS table. */
|
||||
for (uint32_t i = 0; i <= alen; i++) {
|
||||
for (uint32_t j = 0; j <= blen; j++) {
|
||||
if (i == 0 || j == 0) {
|
||||
/* If one substring has length of zero, the
|
||||
* LCS length is zero. */
|
||||
LCS(i,j) = 0;
|
||||
} else if (a[i-1] == b[j-1]) {
|
||||
/* The len LCS (and the LCS itself) of two
|
||||
* sequences with the same final character, is the
|
||||
* LCS of the two sequences without the last char
|
||||
* plus that last char. */
|
||||
LCS(i,j) = LCS(i-1,j-1)+1;
|
||||
} else {
|
||||
/* If the last character is different, take the longest
|
||||
* between the LCS of the first string and the second
|
||||
* minus the last char, and the reverse. */
|
||||
uint32_t lcs1 = LCS(i-1,j);
|
||||
uint32_t lcs2 = LCS(i,j-1);
|
||||
LCS(i,j) = lcs1 > lcs2 ? lcs1 : lcs2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Store the actual LCS string in "result" if needed. We create
|
||||
* it backward, but the length is already known, we store it into idx. */
|
||||
uint32_t idx = LCS(alen,blen);
|
||||
sds result = NULL; /* Resulting LCS string. */
|
||||
void *arraylenptr = NULL; /* Deffered length of the array for IDX. */
|
||||
uint32_t arange_start = alen, /* alen signals that values are not set. */
|
||||
arange_end = 0,
|
||||
brange_start = 0,
|
||||
brange_end = 0;
|
||||
|
||||
/* Do we need to compute the actual LCS string? Allocate it in that case. */
|
||||
int computelcs = getidx || !getlen;
|
||||
if (computelcs) result = sdsnewlen(SDS_NOINIT,idx);
|
||||
|
||||
/* Start with a deferred array if we have to emit the ranges. */
|
||||
uint32_t arraylen = 0; /* Number of ranges emitted in the array. */
|
||||
if (getidx) {
|
||||
addReplyMapLen(c,2);
|
||||
addReplyBulkCString(c,"matches");
|
||||
arraylenptr = addReplyDeferredLen(c);
|
||||
}
|
||||
|
||||
i = alen, j = blen;
|
||||
while (computelcs && i > 0 && j > 0) {
|
||||
int emit_range = 0;
|
||||
if (a[i-1] == b[j-1]) {
|
||||
/* If there is a match, store the character and reduce
|
||||
* the indexes to look for a new match. */
|
||||
result[idx-1] = a[i-1];
|
||||
|
||||
/* Track the current range. */
|
||||
if (arange_start == alen) {
|
||||
arange_start = i-1;
|
||||
arange_end = i-1;
|
||||
brange_start = j-1;
|
||||
brange_end = j-1;
|
||||
} else {
|
||||
/* Let's see if we can extend the range backward since
|
||||
* it is contiguous. */
|
||||
if (arange_start == i && brange_start == j) {
|
||||
arange_start--;
|
||||
brange_start--;
|
||||
} else {
|
||||
emit_range = 1;
|
||||
}
|
||||
}
|
||||
/* Emit the range if we matched with the first byte of
|
||||
* one of the two strings. We'll exit the loop ASAP. */
|
||||
if (arange_start == 0 || brange_start == 0) emit_range = 1;
|
||||
idx--; i--; j--;
|
||||
} else {
|
||||
/* Otherwise reduce i and j depending on the largest
|
||||
* LCS between, to understand what direction we need to go. */
|
||||
uint32_t lcs1 = LCS(i-1,j);
|
||||
uint32_t lcs2 = LCS(i,j-1);
|
||||
if (lcs1 > lcs2)
|
||||
i--;
|
||||
else
|
||||
j--;
|
||||
if (arange_start != alen) emit_range = 1;
|
||||
}
|
||||
|
||||
/* Emit the current range if needed. */
|
||||
uint32_t match_len = arange_end - arange_start + 1;
|
||||
if (emit_range) {
|
||||
if (minmatchlen == 0 || match_len >= minmatchlen) {
|
||||
if (arraylenptr) {
|
||||
addReplyArrayLen(c,2+withmatchlen);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c,arange_start);
|
||||
addReplyLongLong(c,arange_end);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c,brange_start);
|
||||
addReplyLongLong(c,brange_end);
|
||||
if (withmatchlen) addReplyLongLong(c,match_len);
|
||||
arraylen++;
|
||||
}
|
||||
}
|
||||
arange_start = alen; /* Restart at the next match. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Signal modified key, increment dirty, ... */
|
||||
|
||||
/* Reply depending on the given options. */
|
||||
if (arraylenptr) {
|
||||
addReplyBulkCString(c,"len");
|
||||
addReplyLongLong(c,LCS(alen,blen));
|
||||
setDeferredArrayLen(c,arraylenptr,arraylen);
|
||||
} else if (getlen) {
|
||||
addReplyLongLong(c,LCS(alen,blen));
|
||||
} else {
|
||||
addReplyBulkSds(c,result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
/* Cleanup. */
|
||||
if (obja) decrRefCount(obja);
|
||||
if (objb) decrRefCount(objb);
|
||||
sdsfree(result);
|
||||
zfree(lcs);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,8 @@ zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
|
||||
/* Internal function used by zslDelete, zslDeleteRangeByScore and
|
||||
* zslDeleteRangeByRank. */
|
||||
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
|
||||
int i;
|
||||
for (i = 0; i < zsl->level; i++) {
|
||||
@ -1300,14 +1301,14 @@ int zsetScore(robj_roptr zobj, sds member, double *score) {
|
||||
* none could be set if we re-added an element using the same score it used
|
||||
* to have, or in the case a zero increment is used).
|
||||
*
|
||||
* The function returns 0 on erorr, currently only when the increment
|
||||
* The function returns 0 on error, currently only when the increment
|
||||
* produces a NAN condition, or when the 'score' value is NAN since the
|
||||
* start.
|
||||
*
|
||||
* The commad as a side effect of adding a new element may convert the sorted
|
||||
* The command as a side effect of adding a new element may convert the sorted
|
||||
* set internal encoding from ziplist to hashtable+skiplist.
|
||||
*
|
||||
* Memory managemnet of 'ele':
|
||||
* Memory management of 'ele':
|
||||
*
|
||||
* The function does not take ownership of the 'ele' SDS string, but copies
|
||||
* it if needed. */
|
||||
@ -1645,7 +1646,7 @@ reply_to_client:
|
||||
cleanup:
|
||||
zfree(scores);
|
||||
if (added || updated) {
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,
|
||||
incr ? "zincr" : "zadd", key, c->db->id);
|
||||
}
|
||||
@ -1680,7 +1681,7 @@ void zremCommand(client *c) {
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"zrem",key,c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
g_pserver->dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
@ -1778,7 +1779,7 @@ void zremrangeGenericCommand(client *c, int rangetype) {
|
||||
/* Step 4: Notifications and reply. */
|
||||
if (deleted) {
|
||||
const char *event[3] = {"zremrangebyrank","zremrangebyscore","zremrangebylex"};
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,event[rangetype],key,c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
@ -2382,7 +2383,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
zsetConvertToZiplistIfNeeded(dstobj,maxelelen);
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
addReplyLongLong(c,zsetLength(dstobj));
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,
|
||||
(op == SET_OP_UNION) ? "zunionstore" : "zinterstore",
|
||||
dstkey,c->db->id);
|
||||
@ -2391,7 +2392,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
decrRefCount(dstobj);
|
||||
addReply(c,shared.czero);
|
||||
if (touched) {
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",dstkey,c->db->id);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
@ -3215,7 +3216,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
if (arraylen == 0) { /* Do this only for the first iteration. */
|
||||
const char *events[2] = {"zpopmin","zpopmax"};
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,events[where],key,c->db->id);
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
}
|
||||
|
||||
addReplyBulkCBufferAsync(c,ele,sdslen(ele));
|
||||
|
@ -168,7 +168,7 @@ int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int
|
||||
|
||||
if (unit == UNIT_SECONDS) {
|
||||
if (getLongDoubleFromObjectOrReply(c,object,&ftval,
|
||||
"timeout is not an float or out of range") != C_OK)
|
||||
"timeout is not a float or out of range") != C_OK)
|
||||
return C_ERR;
|
||||
tval = (long long) (ftval * 1000.0);
|
||||
} else {
|
||||
|
56
src/tls.cpp
56
src/tls.cpp
@ -96,11 +96,56 @@ static int parseProtocolsConfig(const char *str) {
|
||||
* served to the reader yet. */
|
||||
static thread_local list *pending_list = NULL;
|
||||
|
||||
/**
|
||||
* OpenSSL global initialization and locking handling callbacks.
|
||||
* Note that this is only required for OpenSSL < 1.1.0.
|
||||
*/
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
#define USE_CRYPTO_LOCKS
|
||||
#endif
|
||||
|
||||
#ifdef USE_CRYPTO_LOCKS
|
||||
|
||||
static pthread_mutex_t *openssl_locks;
|
||||
|
||||
static void sslLockingCallback(int mode, int lock_id, const char *f, int line) {
|
||||
pthread_mutex_t *mt = openssl_locks + lock_id;
|
||||
|
||||
if (mode & CRYPTO_LOCK) {
|
||||
pthread_mutex_lock(mt);
|
||||
} else {
|
||||
pthread_mutex_unlock(mt);
|
||||
}
|
||||
|
||||
(void)f;
|
||||
(void)line;
|
||||
}
|
||||
|
||||
static void initCryptoLocks(void) {
|
||||
unsigned i, nlocks;
|
||||
if (CRYPTO_get_locking_callback() != NULL) {
|
||||
/* Someone already set the callback before us. Don't destroy it! */
|
||||
return;
|
||||
}
|
||||
nlocks = CRYPTO_num_locks();
|
||||
openssl_locks = zmalloc(sizeof(*openssl_locks) * nlocks);
|
||||
for (i = 0; i < nlocks; i++) {
|
||||
pthread_mutex_init(openssl_locks + i, NULL);
|
||||
}
|
||||
CRYPTO_set_locking_callback(sslLockingCallback);
|
||||
}
|
||||
#endif /* USE_CRYPTO_LOCKS */
|
||||
|
||||
void tlsInit(void) {
|
||||
ERR_load_crypto_strings();
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
|
||||
#ifdef USE_CRYPTO_LOCKS
|
||||
initCryptoLocks();
|
||||
#endif
|
||||
|
||||
if (!RAND_poll()) {
|
||||
serverLog(LL_WARNING, "OpenSSL: Failed to seed random number generator.");
|
||||
}
|
||||
@ -169,7 +214,7 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
|
||||
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
|
||||
SSL_CTX_set_options(ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
|
||||
#endif
|
||||
|
||||
if (ctx_config->prefer_server_ciphers)
|
||||
@ -177,7 +222,9 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
|
||||
|
||||
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
|
||||
#if defined(SSL_CTX_set_ecdh_auto)
|
||||
SSL_CTX_set_ecdh_auto(ctx, 1);
|
||||
#endif
|
||||
|
||||
if (SSL_CTX_use_certificate_file(ctx, ctx_config->cert_file, SSL_FILETYPE_PEM) <= 0) {
|
||||
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||
@ -854,15 +901,17 @@ int tlsHasPendingData() {
|
||||
return listLength(pending_list) > 0;
|
||||
}
|
||||
|
||||
void tlsProcessPendingData() {
|
||||
int tlsProcessPendingData() {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
int processed = listLength(pending_list);
|
||||
listRewind(pending_list,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
tls_connection *conn = (tls_connection*)listNodeValue(ln);
|
||||
tlsHandleEvent(conn, AE_READABLE);
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
#else /* USE_OPENSSL */
|
||||
@ -890,7 +939,8 @@ int tlsHasPendingData() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tlsProcessPendingData() {
|
||||
int tlsProcessPendingData() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tlsInitThread() {}
|
||||
|
169
src/tracking.cpp
169
src/tracking.cpp
@ -94,7 +94,8 @@ void disableTracking(client *c) {
|
||||
g_pserver->tracking_clients--;
|
||||
c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR|
|
||||
CLIENT_TRACKING_BCAST|CLIENT_TRACKING_OPTIN|
|
||||
CLIENT_TRACKING_OPTOUT|CLIENT_TRACKING_CACHING);
|
||||
CLIENT_TRACKING_OPTOUT|CLIENT_TRACKING_CACHING|
|
||||
CLIENT_TRACKING_NOLOOP);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,14 +130,19 @@ void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **pr
|
||||
if (!(c->flags & CLIENT_TRACKING)) g_pserver->tracking_clients++;
|
||||
c->flags |= CLIENT_TRACKING;
|
||||
c->flags &= ~(CLIENT_TRACKING_BROKEN_REDIR|CLIENT_TRACKING_BCAST|
|
||||
CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
|
||||
CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT|
|
||||
CLIENT_TRACKING_NOLOOP);
|
||||
c->client_tracking_redirection = redirect_to;
|
||||
|
||||
/* This may be the first client we ever enable. Crete the tracking
|
||||
* table if it does not exist. */
|
||||
if (TrackingTable == NULL) {
|
||||
TrackingTable = raxNew();
|
||||
PrefixTable = raxNew();
|
||||
TrackingChannelName = createStringObject("__redis__:invalidate",20);
|
||||
}
|
||||
|
||||
/* For broadcasting, set the list of prefixes in the client. */
|
||||
if (options & CLIENT_TRACKING_BCAST) {
|
||||
c->flags |= CLIENT_TRACKING_BCAST;
|
||||
if (numprefix == 0) enableBcastTrackingForPrefix(c,"",0);
|
||||
@ -145,7 +151,10 @@ void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **pr
|
||||
enableBcastTrackingForPrefix(c,sdsprefix,sdslen(sdsprefix));
|
||||
}
|
||||
}
|
||||
c->flags |= options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
|
||||
|
||||
/* Set the remaining flags that don't need any special handling. */
|
||||
c->flags |= options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT|
|
||||
CLIENT_TRACKING_NOLOOP);
|
||||
}
|
||||
|
||||
/* This function is called after the execution of a readonly command in the
|
||||
@ -245,7 +254,7 @@ void sendTrackingMessage(client *c, const char *keyname, size_t keylen, int prot
|
||||
* 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) {
|
||||
void trackingRememberKeyToBroadcast(client *c, char *keyname, size_t keylen) {
|
||||
raxIterator ri;
|
||||
raxStart(&ri,PrefixTable);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
@ -254,7 +263,11 @@ void trackingRememberKeyToBroadcast(char *keyname, size_t keylen) {
|
||||
if (ri.key_len != 0 && memcmp(ri.key,keyname,ri.key_len) != 0)
|
||||
continue;
|
||||
bcastState *bs = (bcastState*)ri.data;
|
||||
raxTryInsert(bs->keys,(unsigned char*)keyname,keylen,NULL,NULL);
|
||||
/* We insert the client pointer as associated value in the radix
|
||||
* tree. This way we know who was the client that did the last
|
||||
* change to the key, and can avoid sending the notification in the
|
||||
* case the client is in NOLOOP mode. */
|
||||
raxTryInsert(bs->keys,(unsigned char*)keyname,keylen,c,NULL);
|
||||
}
|
||||
raxStop(&ri);
|
||||
}
|
||||
@ -262,15 +275,26 @@ void trackingRememberKeyToBroadcast(char *keyname, size_t keylen) {
|
||||
/* This function is called from signalModifiedKey() or other places in Redis
|
||||
* when a key changes value. In the context of keys tracking, our task here is
|
||||
* to send a notification to every client that may have keys about such caching
|
||||
* slot. */
|
||||
void trackingInvalidateKey(robj *keyobj) {
|
||||
* slot.
|
||||
*
|
||||
* Note that 'c' may be NULL in case the operation was performed outside the
|
||||
* context of a client modifying the database (for instance when we delete a
|
||||
* key because of expire).
|
||||
*
|
||||
* The last argument 'bcast' tells the function if it should also schedule
|
||||
* the key for broadcasting to clients in BCAST mode. This is the case when
|
||||
* the function is called from the Redis core once a key is modified, however
|
||||
* we also call the function in order to evict keys in the key table in case
|
||||
* of memory pressure: in that case the key didn't really change, so we want
|
||||
* just to notify the clients that are in the table for this key, that would
|
||||
* otherwise miss the fact we are no longer tracking the key for them. */
|
||||
void trackingInvalidateKeyRaw(client *c, char *key, size_t keylen, int bcast) {
|
||||
if (TrackingTable == NULL) return;
|
||||
sds sdskey = szFromObj(keyobj);
|
||||
|
||||
if (raxSize(PrefixTable) > 0)
|
||||
trackingRememberKeyToBroadcast(sdskey,sdslen(sdskey));
|
||||
if (bcast && raxSize(PrefixTable) > 0)
|
||||
trackingRememberKeyToBroadcast(c,key,keylen);
|
||||
|
||||
rax *ids = (rax*)raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
|
||||
rax *ids = (rax*)raxFind(TrackingTable,(unsigned char*)key,keylen);
|
||||
if (ids == raxNotFound) return;
|
||||
|
||||
raxIterator ri;
|
||||
@ -279,19 +303,28 @@ void trackingInvalidateKey(robj *keyobj) {
|
||||
while(raxNext(&ri)) {
|
||||
uint64_t id;
|
||||
memcpy(&id,ri.key,sizeof(id));
|
||||
client *c = lookupClientByID(id);
|
||||
client *target = 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)
|
||||
if (target == NULL ||
|
||||
!(target->flags & CLIENT_TRACKING)||
|
||||
target->flags & CLIENT_TRACKING_BCAST)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
sendTrackingMessage(c,sdskey,sdslen(sdskey),0);
|
||||
|
||||
/* If the client enabled the NOLOOP mode, don't send notifications
|
||||
* about keys changed by the client itself. */
|
||||
if (target->flags & CLIENT_TRACKING_NOLOOP &&
|
||||
target == c)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sendTrackingMessage(target,key,keylen,0);
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
@ -299,7 +332,13 @@ void trackingInvalidateKey(robj *keyobj) {
|
||||
* again if more keys will be modified in this caching slot. */
|
||||
TrackingTableTotalItems -= raxSize(ids);
|
||||
raxFree(ids);
|
||||
raxRemove(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey),NULL);
|
||||
raxRemove(TrackingTable,(unsigned char*)key,keylen,NULL);
|
||||
}
|
||||
|
||||
/* Wrapper (the one actually called across the core) to pass the key
|
||||
* as object. */
|
||||
void trackingInvalidateKey(client *c, robj *keyobj) {
|
||||
trackingInvalidateKeyRaw(c,szFromObj(keyobj),sdslen(szFromObj(keyobj)),1);
|
||||
}
|
||||
|
||||
/* This function is called when one or all the Redis databases are flushed
|
||||
@ -366,10 +405,8 @@ void trackingLimitUsedSlots(void) {
|
||||
effort--;
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
raxRandomWalk(&ri,0);
|
||||
rax *ids = (rax*)ri.data;
|
||||
TrackingTableTotalItems -= raxSize(ids);
|
||||
raxFree(ids);
|
||||
raxRemove(TrackingTable,ri.key,ri.key_len,NULL);
|
||||
if (raxEOF(&ri)) break;
|
||||
trackingInvalidateKeyRaw(NULL,(char*)ri.key,ri.key_len,0);
|
||||
if (raxSize(TrackingTable) <= max_keys) {
|
||||
timeout_counter = 0;
|
||||
raxStop(&ri);
|
||||
@ -383,6 +420,54 @@ void trackingLimitUsedSlots(void) {
|
||||
timeout_counter++;
|
||||
}
|
||||
|
||||
/* Generate Redis protocol for an array containing all the key names
|
||||
* in the 'keys' radix tree. If the client is not NULL, the list will not
|
||||
* include keys that were modified the last time by this client, in order
|
||||
* to implement the NOLOOP option.
|
||||
*
|
||||
* If the resultin array would be empty, NULL is returned instead. */
|
||||
sds trackingBuildBroadcastReply(client *c, rax *keys) {
|
||||
raxIterator ri;
|
||||
uint64_t count;
|
||||
|
||||
if (c == NULL) {
|
||||
count = raxSize(keys);
|
||||
} else {
|
||||
count = 0;
|
||||
raxStart(&ri,keys);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
if (ri.data != c) count++;
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
if (count == 0) return NULL;
|
||||
}
|
||||
|
||||
/* 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),count);
|
||||
sds proto = sdsempty();
|
||||
proto = sdsMakeRoomFor(proto,count*15);
|
||||
proto = sdscatlen(proto,"*",1);
|
||||
proto = sdscatlen(proto,buf,len);
|
||||
proto = sdscatlen(proto,"\r\n",2);
|
||||
raxStart(&ri,keys);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
if (c && ri.data == c) continue;
|
||||
len = ll2string(buf,sizeof(buf),ri.key_len);
|
||||
proto = sdscatlen(proto,"$",1);
|
||||
proto = sdscatlen(proto,buf,len);
|
||||
proto = sdscatlen(proto,"\r\n",2);
|
||||
proto = sdscatlen(proto,ri.key,ri.key_len);
|
||||
proto = sdscatlen(proto,"\r\n",2);
|
||||
}
|
||||
raxStop(&ri);
|
||||
return proto;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
@ -394,29 +479,15 @@ void trackingBroadcastInvalidationMessages(void) {
|
||||
|
||||
raxStart(&ri,PrefixTable);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
|
||||
/* For each prefix... */
|
||||
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);
|
||||
/* Generate the common protocol for all the clients that are
|
||||
* not using the NOLOOP option. */
|
||||
sds proto = trackingBuildBroadcastReply(NULL,bs->keys);
|
||||
|
||||
/* Send this array of keys to every client in the list. */
|
||||
raxStart(&ri2,bs->clients);
|
||||
@ -424,7 +495,16 @@ void trackingBroadcastInvalidationMessages(void) {
|
||||
while(raxNext(&ri2)) {
|
||||
client *c;
|
||||
memcpy(&c,ri2.key,sizeof(c));
|
||||
sendTrackingMessage(c,proto,sdslen(proto),1);
|
||||
if (c->flags & CLIENT_TRACKING_NOLOOP) {
|
||||
/* This client may have certain keys excluded. */
|
||||
sds adhoc = trackingBuildBroadcastReply(c,bs->keys);
|
||||
if (adhoc) {
|
||||
sendTrackingMessage(c,adhoc,sdslen(adhoc),1);
|
||||
sdsfree(adhoc);
|
||||
}
|
||||
} else {
|
||||
sendTrackingMessage(c,proto,sdslen(proto),1);
|
||||
}
|
||||
}
|
||||
raxStop(&ri2);
|
||||
|
||||
@ -449,3 +529,8 @@ uint64_t trackingGetTotalKeys(void) {
|
||||
if (TrackingTable == NULL) return 0;
|
||||
return raxSize(TrackingTable);
|
||||
}
|
||||
|
||||
uint64_t trackingGetTotalPrefixes(void) {
|
||||
if (PrefixTable == NULL) return 0;
|
||||
return raxSize(PrefixTable);
|
||||
}
|
||||
|
48
src/util.c
48
src/util.c
@ -42,7 +42,7 @@
|
||||
#include <time.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "sha1.h"
|
||||
#include "sha256.h"
|
||||
|
||||
/* Glob-style pattern matching. */
|
||||
int stringmatchlen(const char *pattern, int patternLen,
|
||||
@ -51,7 +51,7 @@ int stringmatchlen(const char *pattern, int patternLen,
|
||||
while(patternLen && stringLen) {
|
||||
switch(pattern[0]) {
|
||||
case '*':
|
||||
while (pattern[1] == '*') {
|
||||
while (patternLen && pattern[1] == '*') {
|
||||
pattern++;
|
||||
patternLen--;
|
||||
}
|
||||
@ -67,8 +67,6 @@ int stringmatchlen(const char *pattern, int patternLen,
|
||||
return 0; /* no match */
|
||||
break;
|
||||
case '?':
|
||||
if (stringLen == 0)
|
||||
return 0; /* no match */
|
||||
string++;
|
||||
stringLen--;
|
||||
break;
|
||||
@ -96,7 +94,7 @@ int stringmatchlen(const char *pattern, int patternLen,
|
||||
pattern--;
|
||||
patternLen++;
|
||||
break;
|
||||
} else if (pattern[1] == '-' && patternLen >= 3) {
|
||||
} else if (patternLen >= 3 && pattern[1] == '-') {
|
||||
int start = pattern[0];
|
||||
int end = pattern[2];
|
||||
int c = string[0];
|
||||
@ -602,6 +600,10 @@ int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) {
|
||||
}
|
||||
if (*p == '.') l--;
|
||||
}
|
||||
if (l == 2 && buf[0] == '-' && buf[1] == '0') {
|
||||
buf[0] = '0';
|
||||
l = 1;
|
||||
}
|
||||
break;
|
||||
default: return 0; /* Invalid mode. */
|
||||
}
|
||||
@ -618,7 +620,7 @@ int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) {
|
||||
void getRandomBytes(unsigned char *p, size_t len) {
|
||||
/* Global state. */
|
||||
static int seed_initialized = 0;
|
||||
static unsigned char seed[20]; /* The SHA1 seed, from /dev/urandom. */
|
||||
static unsigned char seed[64]; /* 512 bit internal block size. */
|
||||
static uint64_t counter = 0; /* The counter we hash with the seed. */
|
||||
|
||||
if (!seed_initialized) {
|
||||
@ -643,14 +645,34 @@ void getRandomBytes(unsigned char *p, size_t len) {
|
||||
}
|
||||
|
||||
while(len) {
|
||||
unsigned char digest[20];
|
||||
SHA1_CTX ctx;
|
||||
unsigned int copylen = len > 20 ? 20 : len;
|
||||
/* This implements SHA256-HMAC. */
|
||||
unsigned char digest[SHA256_BLOCK_SIZE];
|
||||
unsigned char kxor[64];
|
||||
unsigned int copylen =
|
||||
len > SHA256_BLOCK_SIZE ? SHA256_BLOCK_SIZE : len;
|
||||
|
||||
SHA1Init(&ctx);
|
||||
SHA1Update(&ctx, seed, sizeof(seed));
|
||||
SHA1Update(&ctx, (unsigned char*)&counter,sizeof(counter));
|
||||
SHA1Final(digest, &ctx);
|
||||
/* IKEY: key xored with 0x36. */
|
||||
memcpy(kxor,seed,sizeof(kxor));
|
||||
for (unsigned int i = 0; i < sizeof(kxor); i++) kxor[i] ^= 0x36;
|
||||
|
||||
/* Obtain HASH(IKEY||MESSAGE). */
|
||||
SHA256_CTX ctx;
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx,kxor,sizeof(kxor));
|
||||
sha256_update(&ctx,(unsigned char*)&counter,sizeof(counter));
|
||||
sha256_final(&ctx,digest);
|
||||
|
||||
/* OKEY: key xored with 0x5c. */
|
||||
memcpy(kxor,seed,sizeof(kxor));
|
||||
for (unsigned int i = 0; i < sizeof(kxor); i++) kxor[i] ^= 0x5C;
|
||||
|
||||
/* Obtain HASH(OKEY || HASH(IKEY||MESSAGE)). */
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx,kxor,sizeof(kxor));
|
||||
sha256_update(&ctx,digest,SHA256_BLOCK_SIZE);
|
||||
sha256_final(&ctx,digest);
|
||||
|
||||
/* Increment the counter for the next iteration. */
|
||||
counter++;
|
||||
|
||||
memcpy(p,digest,copylen);
|
||||
|
@ -440,7 +440,7 @@ unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
|
||||
if ((prevlensize) == 1) { \
|
||||
(prevlen) = (ptr)[0]; \
|
||||
} else if ((prevlensize) == 5) { \
|
||||
assert(sizeof((prevlen)) == 4); \
|
||||
assert(sizeof((prevlen)) == 4); \
|
||||
memcpy(&(prevlen), ((char*)(ptr)) + 1, 4); \
|
||||
memrev32ifbe(&prevlen); \
|
||||
} \
|
||||
|
@ -88,11 +88,13 @@ extern "C" {
|
||||
|
||||
#ifdef __cplusplus
|
||||
void *zmalloc(size_t size, enum MALLOC_CLASS mclass = MALLOC_LOCAL);
|
||||
void *zcalloc(size_t size, enum MALLOC_CLASS mclass = MALLOC_LOCAL);
|
||||
void *zrealloc(void *ptr, size_t size, enum MALLOC_CLASS mclass = MALLOC_LOCAL);
|
||||
#else
|
||||
void *zmalloc(size_t size, enum MALLOC_CLASS mclass);
|
||||
#endif
|
||||
void *zcalloc(size_t size, enum MALLOC_CLASS mclass);
|
||||
void *zrealloc(void *ptr, size_t size, enum MALLOC_CLASS mclass);
|
||||
#endif
|
||||
void zfree(const void *ptr);
|
||||
char *zstrdup(const char *s);
|
||||
size_t zmalloc_used_memory(void);
|
||||
|
44
tests/cluster/tests/15-cluster-slots.tcl
Normal file
44
tests/cluster/tests/15-cluster-slots.tcl
Normal file
@ -0,0 +1,44 @@
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
proc cluster_allocate_mixedSlots {n} {
|
||||
set slot 16383
|
||||
while {$slot >= 0} {
|
||||
set node [expr {$slot % $n}]
|
||||
lappend slots_$node $slot
|
||||
incr slot -1
|
||||
}
|
||||
for {set j 0} {$j < $n} {incr j} {
|
||||
R $j cluster addslots {*}[set slots_${j}]
|
||||
}
|
||||
}
|
||||
|
||||
proc create_cluster_with_mixedSlot {masters slaves} {
|
||||
cluster_allocate_mixedSlots $masters
|
||||
if {$slaves} {
|
||||
cluster_allocate_slaves $masters $slaves
|
||||
}
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster_with_mixedSlot 5 15
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
|
||||
test "Instance #5 is a slave" {
|
||||
assert {[RI 5 role] eq {slave}}
|
||||
}
|
||||
|
||||
test "client do not break when cluster slot" {
|
||||
R 0 config set client-output-buffer-limit "normal 33554432 16777216 60"
|
||||
if { [catch {R 0 cluster slots}] } {
|
||||
fail "output overflow when cluster slots"
|
||||
}
|
||||
}
|
@ -54,6 +54,12 @@ tags {"aof"} {
|
||||
|
||||
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
|
||||
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
|
||||
test "Truncated AOF loaded: we expect foo to be equal to 5" {
|
||||
assert {[$client get foo] eq "5"}
|
||||
}
|
||||
@ -71,6 +77,12 @@ tags {"aof"} {
|
||||
|
||||
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
|
||||
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
|
||||
test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
|
||||
assert {[$client get foo] eq "6"}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ start_server {} {
|
||||
$R(1) replicaof $R_host(0) $R_port(0)
|
||||
$R(0) set foo bar
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R(1) master_link_status] == "up" &&
|
||||
[$R(0) dbsize] == 1 && [$R(1) dbsize] == 1
|
||||
} else {
|
||||
fail "Replicas not replicating from master"
|
||||
|
@ -28,7 +28,10 @@ start_server {} {
|
||||
$R(2) slaveof $R_host(0) $R_port(0)
|
||||
$R(0) set foo bar
|
||||
wait_for_condition 50 1000 {
|
||||
[$R(1) dbsize] == 1 && [$R(2) dbsize] == 1
|
||||
[status $R(1) master_link_status] == "up" &&
|
||||
[status $R(2) master_link_status] == "up" &&
|
||||
[$R(1) dbsize] == 1 &&
|
||||
[$R(2) dbsize] == 1
|
||||
} else {
|
||||
fail "Replicas not replicating from master"
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ start_server {} {
|
||||
set used [list $master_id]
|
||||
test "PSYNC2: \[NEW LAYOUT\] Set #$master_id as master" {
|
||||
$R($master_id) slaveof no one
|
||||
$R($master_id) config set repl-ping-replica-period 1 ;# increse the chance that random ping will cause issues
|
||||
if {$counter_value == 0} {
|
||||
$R($master_id) set x $counter_value
|
||||
}
|
||||
@ -66,6 +67,16 @@ start_server {} {
|
||||
lappend used $slave_id
|
||||
}
|
||||
|
||||
# Wait for replicas to sync. so next loop won't get -LOADING error
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R([expr {($master_id+1)%5}]) master_link_status] == "up" &&
|
||||
[status $R([expr {($master_id+2)%5}]) master_link_status] == "up" &&
|
||||
[status $R([expr {($master_id+3)%5}]) master_link_status] == "up" &&
|
||||
[status $R([expr {($master_id+4)%5}]) master_link_status] == "up"
|
||||
} else {
|
||||
fail "Replica not reconnecting"
|
||||
}
|
||||
|
||||
# 3) Increment the counter and wait for all the instances
|
||||
# to converge.
|
||||
test "PSYNC2: cluster is consistent after failover" {
|
||||
@ -114,23 +125,20 @@ 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 all the slaves to be in sync with the master, due to pings, we have to re-sample the master constantly too
|
||||
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]
|
||||
[status $R($master_id) master_repl_offset] == [status $R(0) master_repl_offset] &&
|
||||
[status $R($master_id) master_repl_offset] == [status $R(1) master_repl_offset] &&
|
||||
[status $R($master_id) master_repl_offset] == [status $R(2) master_repl_offset] &&
|
||||
[status $R($master_id) master_repl_offset] == [status $R(3) master_repl_offset] &&
|
||||
[status $R($master_id) master_repl_offset] == [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 "---"
|
||||
}
|
||||
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."
|
||||
}
|
||||
@ -175,9 +183,14 @@ start_server {} {
|
||||
$R($j) slaveof $master_host $master_port
|
||||
}
|
||||
|
||||
# Wait for slaves to sync
|
||||
wait_for_condition 50 2000 {
|
||||
[status $R($master_id) connected_slaves] == 4
|
||||
# Wait for replicas to sync. it is not enough to just wait for connected_slaves==4
|
||||
# since we might do the check before the master realized that they're disconnected
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R($master_id) connected_slaves] == 4 &&
|
||||
[status $R([expr {($master_id+1)%5}]) master_link_status] == "up" &&
|
||||
[status $R([expr {($master_id+2)%5}]) master_link_status] == "up" &&
|
||||
[status $R([expr {($master_id+3)%5}]) master_link_status] == "up" &&
|
||||
[status $R([expr {($master_id+4)%5}]) master_link_status] == "up"
|
||||
} else {
|
||||
fail "Replica not reconnecting"
|
||||
}
|
||||
@ -188,6 +201,7 @@ start_server {} {
|
||||
set slave_id [expr {($master_id+1)%5}]
|
||||
set sync_count [status $R($master_id) sync_full]
|
||||
set sync_partial [status $R($master_id) sync_partial_ok]
|
||||
set sync_partial_err [status $R($master_id) sync_partial_err]
|
||||
catch {
|
||||
$R($slave_id) config rewrite
|
||||
$R($slave_id) debug restart
|
||||
@ -197,7 +211,11 @@ start_server {} {
|
||||
wait_for_condition 50 2000 {
|
||||
[status $R($master_id) sync_partial_ok] == $sync_partial + 1
|
||||
} else {
|
||||
fail "Replica not reconnecting"
|
||||
puts "prev sync_full: $sync_count"
|
||||
puts "prev sync_partial_ok: $sync_partial"
|
||||
puts "prev sync_partial_err: $sync_partial_err"
|
||||
puts [$R($master_id) info stats]
|
||||
fail "Replica didn't partial sync"
|
||||
}
|
||||
set new_sync_count [status $R($master_id) sync_full]
|
||||
assert {$sync_count == $new_sync_count}
|
||||
@ -271,3 +289,103 @@ start_server {} {
|
||||
}
|
||||
|
||||
}}}}}
|
||||
|
||||
start_server {tags {"psync2"}} {
|
||||
start_server {} {
|
||||
start_server {} {
|
||||
start_server {} {
|
||||
start_server {} {
|
||||
test {pings at the end of replication stream are ignored for psync} {
|
||||
set master [srv -4 client]
|
||||
set master_host [srv -4 host]
|
||||
set master_port [srv -4 port]
|
||||
set replica1 [srv -3 client]
|
||||
set replica2 [srv -2 client]
|
||||
set replica3 [srv -1 client]
|
||||
set replica4 [srv -0 client]
|
||||
|
||||
$replica1 replicaof $master_host $master_port
|
||||
$replica2 replicaof $master_host $master_port
|
||||
$replica3 replicaof $master_host $master_port
|
||||
$replica4 replicaof $master_host $master_port
|
||||
wait_for_condition 50 1000 {
|
||||
[status $master connected_slaves] == 4
|
||||
} else {
|
||||
fail "replicas didn't connect"
|
||||
}
|
||||
|
||||
$master incr x
|
||||
wait_for_condition 50 1000 {
|
||||
[$replica1 get x] == 1 && [$replica2 get x] == 1 &&
|
||||
[$replica3 get x] == 1 && [$replica4 get x] == 1
|
||||
} else {
|
||||
fail "replicas didn't get incr"
|
||||
}
|
||||
|
||||
# disconnect replica1 and replica2
|
||||
# and wait for the master to send a ping to replica3 and replica4
|
||||
$replica1 replicaof no one
|
||||
$replica2 replicaof 127.0.0.1 1 ;# we can't promote it to master since that will cycle the replication id
|
||||
$master config set repl-ping-replica-period 1
|
||||
after 1500
|
||||
|
||||
# make everyone sync from the replica1 that didn't get the last ping from the old master
|
||||
# replica4 will keep syncing from the old master which now syncs from replica1
|
||||
# and replica2 will re-connect to the old master (which went back in time)
|
||||
set new_master_host [srv -3 host]
|
||||
set new_master_port [srv -3 port]
|
||||
$replica3 replicaof $new_master_host $new_master_port
|
||||
$master replicaof $new_master_host $new_master_port
|
||||
$replica2 replicaof $master_host $master_port
|
||||
wait_for_condition 50 1000 {
|
||||
[status $replica2 master_link_status] == "up" &&
|
||||
[status $replica3 master_link_status] == "up" &&
|
||||
[status $replica4 master_link_status] == "up" &&
|
||||
[status $master master_link_status] == "up"
|
||||
} else {
|
||||
fail "replicas didn't connect"
|
||||
}
|
||||
|
||||
# make sure replication is still alive and kicking
|
||||
$replica1 incr x
|
||||
wait_for_condition 50 1000 {
|
||||
[$replica2 get x] == 2 &&
|
||||
[$replica3 get x] == 2 &&
|
||||
[$replica4 get x] == 2 &&
|
||||
[$master get x] == 2
|
||||
} else {
|
||||
fail "replicas didn't get incr"
|
||||
}
|
||||
|
||||
# make sure there are full syncs other than the initial ones
|
||||
assert_equal [status $master sync_full] 4
|
||||
assert_equal [status $replica1 sync_full] 0
|
||||
assert_equal [status $replica2 sync_full] 0
|
||||
assert_equal [status $replica3 sync_full] 0
|
||||
assert_equal [status $replica4 sync_full] 0
|
||||
|
||||
# force psync
|
||||
$master client kill type master
|
||||
$replica2 client kill type master
|
||||
$replica3 client kill type master
|
||||
$replica4 client kill type master
|
||||
|
||||
# make sure replication is still alive and kicking
|
||||
$replica1 incr x
|
||||
wait_for_condition 50 1000 {
|
||||
[$replica2 get x] == 3 &&
|
||||
[$replica3 get x] == 3 &&
|
||||
[$replica4 get x] == 3 &&
|
||||
[$master get x] == 3
|
||||
} else {
|
||||
fail "replicas didn't get incr"
|
||||
}
|
||||
|
||||
# make sure there are full syncs other than the initial ones
|
||||
assert_equal [status $master sync_full] 4
|
||||
assert_equal [status $replica1 sync_full] 0
|
||||
assert_equal [status $replica2 sync_full] 0
|
||||
assert_equal [status $replica3 sync_full] 0
|
||||
assert_equal [status $replica4 sync_full] 0
|
||||
}
|
||||
}}}}}
|
||||
|
@ -128,4 +128,56 @@ start_server {} {
|
||||
# make sure the server is still writable
|
||||
r set x xx
|
||||
}
|
||||
}
|
||||
|
||||
test {client freed during loading} {
|
||||
start_server [list overrides [list key-load-delay 10 rdbcompression no]] {
|
||||
# create a big rdb that will take long to load. it is important
|
||||
# for keys to be big since the server processes events only once in 2mb.
|
||||
# 100mb of rdb, 100k keys will load in more than 1 second
|
||||
r debug populate 100000 key 1000
|
||||
|
||||
catch {
|
||||
r debug restart
|
||||
}
|
||||
|
||||
set stdout [srv 0 stdout]
|
||||
while 1 {
|
||||
# check that the new server actually started and is ready for connections
|
||||
if {[exec grep -i "Server initialized" | wc -l < $stdout] > 1} {
|
||||
break
|
||||
}
|
||||
after 10
|
||||
}
|
||||
# make sure it's still loading
|
||||
assert_equal [s loading] 1
|
||||
|
||||
# connect and disconnect 10 clients
|
||||
set clients {}
|
||||
for {set j 0} {$j < 10} {incr j} {
|
||||
lappend clients [redis_deferring_client]
|
||||
}
|
||||
foreach rd $clients {
|
||||
$rd debug log bla
|
||||
}
|
||||
foreach rd $clients {
|
||||
$rd read
|
||||
}
|
||||
foreach rd $clients {
|
||||
$rd close
|
||||
}
|
||||
|
||||
# make sure the server freed the clients
|
||||
wait_for_condition 100 100 {
|
||||
[s connected_clients] < 3
|
||||
} else {
|
||||
fail "clients didn't disconnect"
|
||||
}
|
||||
|
||||
# make sure it's still loading
|
||||
assert_equal [s loading] 1
|
||||
|
||||
# no need to keep waiting for loading to complete
|
||||
exec kill [srv 0 pid]
|
||||
}
|
||||
}
|
@ -118,7 +118,7 @@ start_server {tags {"repl"}} {
|
||||
# correctly the RDB file: such file will contain "lua" AUX
|
||||
# sections with scripts already in the memory of the master.
|
||||
|
||||
wait_for_condition 50 100 {
|
||||
wait_for_condition 500 100 {
|
||||
[s -1 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
|
@ -513,9 +513,9 @@ start_server {tags {"repl"}} {
|
||||
set master_port [srv 0 port]
|
||||
set master_pid [srv 0 pid]
|
||||
# put enough data in the db that the rdb file will be bigger than the socket buffers
|
||||
# and since we'll have key-load-delay of 100, 10000 keys will take at least 1 second
|
||||
# and since we'll have key-load-delay of 100, 20000 keys will take at least 2 seconds
|
||||
# we also need the replica to process requests during transfer (which it does only once in 2mb)
|
||||
$master debug populate 10000 test 10000
|
||||
$master debug populate 20000 test 10000
|
||||
$master config set rdbcompression no
|
||||
# If running on Linux, we also measure utime/stime to detect possible I/O handling issues
|
||||
set os [catch {exec unamee}]
|
||||
|
@ -109,41 +109,33 @@ int fsl_push(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return RedisModule_ReplyWithError(ctx,"ERR new element has to be greater than the head element");
|
||||
|
||||
fsl->list[fsl->length++] = ele;
|
||||
|
||||
if (fsl->length >= 2)
|
||||
RedisModule_SignalKeyAsReady(ctx, argv[1]);
|
||||
RedisModule_SignalKeyAsReady(ctx, argv[1]);
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
int bpop2_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int bpop_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||
|
||||
fsl_t *fsl;
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0) || !fsl)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (!fsl || fsl->length < 2)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int bpop2_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int bpop_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||
}
|
||||
|
||||
|
||||
/* FSL.BPOP2 <key> <timeout> - Block clients until list has two or more elements.
|
||||
/* FSL.BPOP <key> <timeout> - Block clients until list has two or more elements.
|
||||
* When that happens, unblock client and pop the last two elements (from the right). */
|
||||
int fsl_bpop2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int fsl_bpop(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
@ -155,13 +147,10 @@ int fsl_bpop2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!fsl || fsl->length < 2) {
|
||||
/* Key is empty or has <2 elements, we must block */
|
||||
RedisModule_BlockClientOnKeys(ctx, bpop2_reply_callback, bpop2_timeout_callback,
|
||||
if (!fsl) {
|
||||
RedisModule_BlockClientOnKeys(ctx, bpop_reply_callback, bpop_timeout_callback,
|
||||
NULL, timeout, &argv[1], 1, NULL);
|
||||
} else {
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
}
|
||||
|
||||
@ -175,10 +164,10 @@ int bpopgt_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
|
||||
long long *pgt = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
|
||||
fsl_t *fsl;
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0) || !fsl)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (!fsl || fsl->list[fsl->length-1] <= *pgt)
|
||||
if (fsl->list[fsl->length-1] <= *pgt)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
@ -218,7 +207,6 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
/* 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 */
|
||||
RedisModule_BlockClientOnKeys(ctx, bpopgt_reply_callback, bpopgt_timeout_callback,
|
||||
bpopgt_free_privdata, timeout, &argv[1], 1, pgt);
|
||||
} else {
|
||||
@ -228,6 +216,88 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int bpoppush_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
RedisModuleString *src_keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||
RedisModuleString *dst_keyname = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
|
||||
fsl_t *src;
|
||||
if (!get_fsl(ctx, src_keyname, REDISMODULE_READ, 0, &src, 0) || !src)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
fsl_t *dst;
|
||||
if (!get_fsl(ctx, dst_keyname, REDISMODULE_WRITE, 1, &dst, 0) || !dst)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
long long ele = src->list[--src->length];
|
||||
dst->list[dst->length++] = ele;
|
||||
RedisModule_SignalKeyAsReady(ctx, dst_keyname);
|
||||
return RedisModule_ReplyWithLongLong(ctx, ele);
|
||||
}
|
||||
|
||||
int bpoppush_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||
}
|
||||
|
||||
void bpoppush_free_privdata(RedisModuleCtx *ctx, void *privdata) {
|
||||
RedisModule_FreeString(ctx, privdata);
|
||||
}
|
||||
|
||||
/* FSL.BPOPPUSH <src> <dst> <timeout> - Block clients until <src> has an element.
|
||||
* When that happens, unblock client, pop the last element from <src> and push it to <dst>
|
||||
* (from the right). */
|
||||
int fsl_bpoppush(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 4)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
long long timeout;
|
||||
if (RedisModule_StringToLongLong(argv[3],&timeout) != REDISMODULE_OK || timeout < 0)
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
|
||||
|
||||
fsl_t *src;
|
||||
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &src, 1))
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!src) {
|
||||
/* Retain string for reply callback */
|
||||
RedisModule_RetainString(ctx, argv[2]);
|
||||
/* Key is empty, we must block */
|
||||
RedisModule_BlockClientOnKeys(ctx, bpoppush_reply_callback, bpoppush_timeout_callback,
|
||||
bpoppush_free_privdata, timeout, &argv[1], 1, argv[2]);
|
||||
} else {
|
||||
fsl_t *dst;
|
||||
if (!get_fsl(ctx, argv[2], REDISMODULE_WRITE, 1, &dst, 1))
|
||||
return REDISMODULE_OK;
|
||||
long long ele = src->list[--src->length];
|
||||
dst->list[dst->length++] = ele;
|
||||
RedisModule_SignalKeyAsReady(ctx, argv[2]);
|
||||
RedisModule_ReplyWithLongLong(ctx, ele);
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* FSL.GETALL <key> - Reply with an array containing all elements. */
|
||||
int fsl_getall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
fsl_t *fsl;
|
||||
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!fsl)
|
||||
return RedisModule_ReplyWithArray(ctx, 0);
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, fsl->length);
|
||||
for (int i = 0; i < fsl->length; i++)
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[i]);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
@ -252,11 +322,17 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.push",fsl_push,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpop2",fsl_bpop2,"",0,0,0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpop",fsl_bpop,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpopgt",fsl_bpopgt,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpoppush",fsl_bpoppush,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.getall",fsl_getall,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
@ -159,12 +159,9 @@ proc start_server {options {code undefined}} {
|
||||
if {$::external} {
|
||||
if {[llength $::servers] == 0} {
|
||||
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 "port" $baseport
|
||||
set client [redis $::host $baseport 0 $::tls]
|
||||
dict set srv "port" $::port
|
||||
set client [redis $::host $::port 0 $::tls]
|
||||
dict set srv "client" $client
|
||||
$client select 9
|
||||
|
||||
|
@ -88,6 +88,7 @@ set ::skiptests {}
|
||||
set ::allowtags {}
|
||||
set ::only_tests {}
|
||||
set ::single_tests {}
|
||||
set ::run_solo_tests {}
|
||||
set ::skip_till ""
|
||||
set ::external 0; # If "1" this means, we are running against external instance
|
||||
set ::file ""; # If set, runs only the tests in this comma separated list
|
||||
@ -111,13 +112,27 @@ set ::tlsdir "tests/tls"
|
||||
set ::client 0
|
||||
set ::numclients 16
|
||||
|
||||
proc execute_tests name {
|
||||
# This function is called by one of the test clients when it receives
|
||||
# a "run" command from the server, with a filename as data.
|
||||
# It will run the specified test source file and signal it to the
|
||||
# test server when finished.
|
||||
proc execute_test_file name {
|
||||
set path "tests/$name.tcl"
|
||||
set ::curfile $path
|
||||
source $path
|
||||
send_data_packet $::test_server_fd done "$name"
|
||||
}
|
||||
|
||||
# This function is called by one of the test clients when it receives
|
||||
# a "run_code" command from the server, with a verbatim test source code
|
||||
# as argument, and an associated name.
|
||||
# It will run the specified code and signal it to the test server when
|
||||
# finished.
|
||||
proc execute_test_code {name code} {
|
||||
eval $code
|
||||
send_data_packet $::test_server_fd done "$name"
|
||||
}
|
||||
|
||||
# Setup a list to hold a stack of server configs. When calls to start_server
|
||||
# are nested, use "srv 0 pid" to get the pid of the inner server. To access
|
||||
# outer servers, use "srv -1 pid" etcetera.
|
||||
@ -194,10 +209,19 @@ proc s {args} {
|
||||
status [srv $level "client"] [lindex $args 0]
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
if {$::dont_clean} {
|
||||
# Test wrapped into run_solo are sent back from the client to the
|
||||
# test server, so that the test server will send them again to
|
||||
# clients once the clients are idle.
|
||||
proc run_solo {name code} {
|
||||
if {$::numclients == 1 || $::loop || $::external} {
|
||||
# run_solo is not supported in these scenarios, just run the code.
|
||||
eval $code
|
||||
return
|
||||
}
|
||||
send_data_packet $::test_server_fd run_solo [list $name $code]
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
if {!$::quiet} {puts -nonewline "Cleanup: may take some time... "}
|
||||
flush stdout
|
||||
catch {exec rm -rf {*}[glob tests/tmp/redis.conf.*]}
|
||||
@ -218,13 +242,19 @@ proc test_server_main {} {
|
||||
|
||||
# Start the client instances
|
||||
set ::clients_pids {}
|
||||
set start_port [expr {$::port+100}]
|
||||
for {set j 0} {$j < $::numclients} {incr j} {
|
||||
set start_port [find_available_port $start_port]
|
||||
if {$::external} {
|
||||
set p [exec $tclsh [info script] {*}$::argv \
|
||||
--client $port --port $start_port &]
|
||||
--client $port --port $::port &]
|
||||
lappend ::clients_pids $p
|
||||
incr start_port 10
|
||||
} else {
|
||||
set start_port [expr {$::port+100}]
|
||||
for {set j 0} {$j < $::numclients} {incr j} {
|
||||
set start_port [find_available_port $start_port]
|
||||
set p [exec $tclsh [info script] {*}$::argv \
|
||||
--client $port --port $start_port &]
|
||||
lappend ::clients_pids $p
|
||||
incr start_port 10
|
||||
}
|
||||
}
|
||||
|
||||
# Setup global state for the test server
|
||||
@ -337,6 +367,8 @@ proc read_from_test_client fd {
|
||||
} elseif {$status eq {server-killed}} {
|
||||
set ::active_servers [lsearch -all -inline -not -exact $::active_servers $data]
|
||||
set ::active_clients_task($fd) "(KILLED SERVER) pid:$data"
|
||||
} elseif {$status eq {run_solo}} {
|
||||
lappend ::run_solo_tests $data
|
||||
} else {
|
||||
if {!$::quiet} {
|
||||
puts "\[$status\]: $data"
|
||||
@ -369,6 +401,13 @@ proc force_kill_all_servers {} {
|
||||
}
|
||||
}
|
||||
|
||||
proc lpop {listVar {count 1}} {
|
||||
upvar 1 $listVar l
|
||||
set ele [lindex $l 0]
|
||||
set l [lrange $l 1 end]
|
||||
set ele
|
||||
}
|
||||
|
||||
# A new client is idle. Remove it from the list of active clients and
|
||||
# if there are still test units to run, launch them.
|
||||
proc signal_idle_client fd {
|
||||
@ -389,6 +428,14 @@ proc signal_idle_client fd {
|
||||
if {$::loop && $::next_test == [llength $::all_tests]} {
|
||||
set ::next_test 0
|
||||
}
|
||||
} elseif {[llength $::run_solo_tests] != 0 && [llength $::active_clients] == 0} {
|
||||
if {!$::quiet} {
|
||||
puts [colorstr bold-white "Testing solo test"]
|
||||
set ::active_clients_task($fd) "ASSIGNED: $fd solo test"
|
||||
}
|
||||
set ::clients_start_time($fd) [clock seconds]
|
||||
send_data_packet $fd run_code [lpop ::run_solo_tests]
|
||||
lappend ::active_clients $fd
|
||||
} else {
|
||||
lappend ::idle_clients $fd
|
||||
set ::active_clients_task($fd) "SLEEPING, no more units to assign"
|
||||
@ -412,11 +459,11 @@ proc the_end {} {
|
||||
foreach failed $::failed_tests {
|
||||
puts "*** $failed"
|
||||
}
|
||||
cleanup
|
||||
if {!$::dont_clean} cleanup
|
||||
exit 1
|
||||
} else {
|
||||
puts "\n[colorstr bold-white {\o/}] [colorstr bold-green {All tests passed without errors!}]\n"
|
||||
cleanup
|
||||
if {!$::dont_clean} cleanup
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
@ -432,7 +479,10 @@ proc test_client_main server_port {
|
||||
set payload [read $::test_server_fd $bytes]
|
||||
foreach {cmd data} $payload break
|
||||
if {$cmd eq {run}} {
|
||||
execute_tests $data
|
||||
execute_test_file $data
|
||||
} elseif {$cmd eq {run_code}} {
|
||||
foreach {name code} $data break
|
||||
execute_test_code $name $code
|
||||
} else {
|
||||
error "Unknown test client command: $cmd"
|
||||
}
|
||||
@ -512,9 +562,6 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--host}} {
|
||||
set ::external 1
|
||||
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
|
||||
} elseif {$opt eq {--port}} {
|
||||
set ::port $arg
|
||||
|
@ -95,6 +95,10 @@ start_server {tags {"introspection"}} {
|
||||
bind
|
||||
requirepass
|
||||
multi-master
|
||||
server_cpulist
|
||||
bio_cpulist
|
||||
aof_rewrite_cpulist
|
||||
bgsave_cpulist
|
||||
}
|
||||
|
||||
set configs {}
|
||||
|
@ -36,6 +36,7 @@ start_server {tags {"memefficiency"}} {
|
||||
}
|
||||
}
|
||||
|
||||
run_solo {defrag} {
|
||||
start_server {tags {"defrag"}} {
|
||||
if {[string match {*jemalloc*} [s mem_allocator]]} {
|
||||
test "Active defrag" {
|
||||
@ -328,3 +329,4 @@ start_server {tags {"defrag"}} {
|
||||
} {1}
|
||||
}
|
||||
}
|
||||
} ;# run_solo
|
||||
|
@ -3,37 +3,70 @@ set testmodule [file normalize tests/modules/blockonkeys.so]
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule
|
||||
|
||||
test "Module client blocked on keys: Circular BPOPPUSH" {
|
||||
set rd1 [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
|
||||
r del src dst
|
||||
|
||||
$rd1 fsl.bpoppush src dst 0
|
||||
$rd2 fsl.bpoppush dst src 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {2}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
|
||||
r fsl.push src 42
|
||||
|
||||
assert_equal {42} [r fsl.getall src]
|
||||
assert_equal {} [r fsl.getall dst]
|
||||
}
|
||||
|
||||
test "Module client blocked on keys: Self-referential BPOPPUSH" {
|
||||
set rd1 [redis_deferring_client]
|
||||
|
||||
r del src
|
||||
|
||||
$rd1 fsl.bpoppush src src 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push src 42
|
||||
|
||||
assert_equal {42} [r fsl.getall src]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (no metadata): No block} {
|
||||
r del k
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
r fsl.bpop2 k 0
|
||||
} {34 33}
|
||||
r fsl.bpop k 0
|
||||
} {34}
|
||||
|
||||
test {Module client blocked on keys (no metadata): Timeout} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpop2 k 1
|
||||
$rd fsl.bpop k 1
|
||||
assert_equal {Request timedout} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (no metadata): Blocked, case 1} {
|
||||
test {Module client blocked on keys (no metadata): Blocked} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpop2 k 0
|
||||
$rd fsl.bpop k 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push k 34
|
||||
assert_equal {34 33} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (no metadata): Blocked, case 2} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
$rd fsl.bpop2 k 0
|
||||
assert_equal {34 33} [$rd read]
|
||||
assert_equal {34} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (with metadata): No block} {
|
||||
@ -60,6 +93,12 @@ start_server {tags {"modules"}} {
|
||||
set cid [$rd read]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpopgt k 33 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push k 34
|
||||
assert_equal {34} [$rd read]
|
||||
r client kill id $cid ;# try to smoke-out client-related memory leak
|
||||
@ -69,6 +108,12 @@ start_server {tags {"modules"}} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
r fsl.push k 35
|
||||
@ -82,6 +127,12 @@ start_server {tags {"modules"}} {
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r client kill id $cid ;# try to smoke-out client-related memory leak
|
||||
}
|
||||
|
||||
@ -91,6 +142,12 @@ start_server {tags {"modules"}} {
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r client unblock $cid timeout ;# try to smoke-out client-related memory leak
|
||||
assert_equal {Request timedout} [$rd read]
|
||||
}
|
||||
@ -101,6 +158,12 @@ start_server {tags {"modules"}} {
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r client unblock $cid error ;# try to smoke-out client-related memory leak
|
||||
assert_error "*unblocked*" {$rd read}
|
||||
}
|
||||
@ -108,13 +171,18 @@ start_server {tags {"modules"}} {
|
||||
test {Module client blocked on keys does not wake up on wrong type} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd fsl.bpop2 k 0
|
||||
$rd fsl.bpop k 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r lpush k 12
|
||||
r lpush k 13
|
||||
r lpush k 14
|
||||
r del k
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
assert_equal {34 33} [$rd read]
|
||||
assert_equal {34} [$rd read]
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +146,17 @@ start_server {tags {"scripting"}} {
|
||||
set e
|
||||
} {*not allowed*}
|
||||
|
||||
test {EVAL - Scripts can't run XREAD and XREADGROUP with BLOCK option} {
|
||||
r del s
|
||||
r xgroup create s g $ MKSTREAM
|
||||
set res [r eval {return redis.pcall('xread','STREAMS','s','$')} 1 s]
|
||||
assert {$res eq {}}
|
||||
assert_error "*xread command is not allowed with BLOCK option from scripts" {r eval {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s}
|
||||
set res [r eval {return redis.pcall('xreadgroup','group','g','c','STREAMS','s','>')} 1 s]
|
||||
assert {$res eq {}}
|
||||
assert_error "*xreadgroup command is not allowed with BLOCK option from scripts" {r eval {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s}
|
||||
}
|
||||
|
||||
test {EVAL - Scripts can't run certain commands} {
|
||||
set e {}
|
||||
r debug lua-always-replicate-commands 0
|
||||
|
@ -25,26 +25,6 @@ start_server {tags {"tls"}} {
|
||||
}
|
||||
|
||||
test {TLS: Verify tls-protocols behaves as expected} {
|
||||
r CONFIG SET tls-protocols TLSv1
|
||||
|
||||
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1 0}]
|
||||
catch {$s PING} e
|
||||
assert_match {*I/O error*} $e
|
||||
|
||||
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1 1}]
|
||||
catch {$s PING} e
|
||||
assert_match {PONG} $e
|
||||
|
||||
r CONFIG SET tls-protocols TLSv1.1
|
||||
|
||||
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.1 0}]
|
||||
catch {$s PING} e
|
||||
assert_match {*I/O error*} $e
|
||||
|
||||
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.1 1}]
|
||||
catch {$s PING} e
|
||||
assert_match {PONG} $e
|
||||
|
||||
r CONFIG SET tls-protocols TLSv1.2
|
||||
|
||||
set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.2 0}]
|
||||
|
@ -7,6 +7,9 @@ start_server {tags {"tracking"}} {
|
||||
$rd1 subscribe __redis__:invalidate
|
||||
$rd1 read ; # Consume the SUBSCRIBE reply.
|
||||
|
||||
# Create another client as well in order to test NOLOOP
|
||||
set rd2 [redis_deferring_client]
|
||||
|
||||
test {Clients are able to enable tracking and redirect it} {
|
||||
r CLIENT TRACKING on REDIRECT $redir
|
||||
} {*OK}
|
||||
@ -62,5 +65,69 @@ start_server {tags {"tracking"}} {
|
||||
assert {$keys eq {c:1234}}
|
||||
}
|
||||
|
||||
test {Tracking NOLOOP mode in standard mode works} {
|
||||
r CLIENT TRACKING off
|
||||
r CLIENT TRACKING on REDIRECT $redir NOLOOP
|
||||
r MGET otherkey1 loopkey otherkey2
|
||||
$rd2 SET otherkey1 1; # We should get this
|
||||
r SET loopkey 1 ; # We should not get this
|
||||
$rd2 SET otherkey2 1; # We should get this
|
||||
# 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 {otherkey1 otherkey2}}
|
||||
}
|
||||
|
||||
test {Tracking NOLOOP mode in BCAST mode works} {
|
||||
r CLIENT TRACKING off
|
||||
r CLIENT TRACKING on BCAST REDIRECT $redir NOLOOP
|
||||
$rd2 SET otherkey1 1; # We should get this
|
||||
r SET loopkey 1 ; # We should not get this
|
||||
$rd2 SET otherkey2 1; # We should get this
|
||||
# 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 {otherkey1 otherkey2}}
|
||||
}
|
||||
|
||||
test {Tracking gets notification of expired keys} {
|
||||
r CLIENT TRACKING off
|
||||
r CLIENT TRACKING on BCAST REDIRECT $redir NOLOOP
|
||||
r SET mykey myval px 1
|
||||
r SET mykeyotherkey myval ; # We should not get it
|
||||
after 1000
|
||||
# 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 keys [lsort [list {*}$keys1]]
|
||||
assert {$keys eq {mykey}}
|
||||
}
|
||||
|
||||
test {Tracking gets notification on tracking table key eviction} {
|
||||
r CLIENT TRACKING off
|
||||
r CLIENT TRACKING on REDIRECT $redir NOLOOP
|
||||
r MSET key1 1 key2 2
|
||||
# Let the server track the two keys for us
|
||||
r MGET key1 key2
|
||||
# Force the eviction of all the keys but one:
|
||||
r config set tracking-table-max-keys 1
|
||||
# Note that we may have other keys in the table for this client,
|
||||
# since we disabled/enabled tracking multiple time with the same
|
||||
# ID, and tracking does not do ID cleanups for performance reasons.
|
||||
# So we check that eventually we'll receive one or the other key,
|
||||
# otherwise the test will die for timeout.
|
||||
while 1 {
|
||||
set keys [lindex [$rd1 read] 2]
|
||||
if {$keys eq {key1} || $keys eq {key2}} break
|
||||
}
|
||||
# We should receive an expire notification for one of
|
||||
# the two keys (only one must remain)
|
||||
assert {$keys eq {key1} || $keys eq {key2}}
|
||||
}
|
||||
|
||||
$rd1 close
|
||||
}
|
||||
|
@ -151,4 +151,11 @@ start_server {tags {"incr"}} {
|
||||
catch {r incrbyfloat foo 1} err
|
||||
format $err
|
||||
} {ERR*valid*}
|
||||
|
||||
test {No negative zero} {
|
||||
r del foo
|
||||
r incrbyfloat foo [expr double(1)/41]
|
||||
r incrbyfloat foo [expr double(-1)/41]
|
||||
r get foo
|
||||
} {0}
|
||||
}
|
||||
|
@ -93,6 +93,18 @@ start_server {
|
||||
assert {[r XACK mystream mygroup $id1 $id2] eq 1}
|
||||
}
|
||||
|
||||
test {XACK should fail if got at least one invalid ID} {
|
||||
r del mystream
|
||||
r xgroup create s g $ MKSTREAM
|
||||
r xadd s * f1 v1
|
||||
set c [llength [lindex [r xreadgroup group g c streams s >] 0 1]]
|
||||
assert {$c == 1}
|
||||
set pending [r xpending s g - + 10 c]
|
||||
set id1 [lindex $pending 0 0]
|
||||
assert_error "*Invalid stream ID specified*" {r xack s g $id1 invalid-id}
|
||||
assert {[r xack s g $id1] eq 1}
|
||||
}
|
||||
|
||||
test {PEL NACK reassignment after XGROUP SETID event} {
|
||||
r del events
|
||||
r xadd events * f1 v1
|
||||
@ -282,6 +294,40 @@ start_server {
|
||||
assert {[lindex $reply 0 3] == 2}
|
||||
}
|
||||
|
||||
test {XINFO FULL output} {
|
||||
r del x
|
||||
r XADD x 100 a 1
|
||||
r XADD x 101 b 1
|
||||
r XADD x 102 c 1
|
||||
r XADD x 103 e 1
|
||||
r XADD x 104 f 1
|
||||
r XGROUP CREATE x g1 0
|
||||
r XGROUP CREATE x g2 0
|
||||
r XREADGROUP GROUP g1 Alice COUNT 1 STREAMS x >
|
||||
r XREADGROUP GROUP g1 Bob COUNT 1 STREAMS x >
|
||||
r XREADGROUP GROUP g1 Bob NOACK COUNT 1 STREAMS x >
|
||||
r XREADGROUP GROUP g2 Charlie COUNT 4 STREAMS x >
|
||||
r XDEL x 103
|
||||
|
||||
set reply [r XINFO STREAM x FULL]
|
||||
assert_equal [llength $reply] 12
|
||||
assert_equal [lindex $reply 1] 4 ;# stream length
|
||||
assert_equal [lindex $reply 9] "{100-0 {a 1}} {101-0 {b 1}} {102-0 {c 1}} {104-0 {f 1}}" ;# entries
|
||||
assert_equal [lindex $reply 11 0 1] "g1" ;# first group name
|
||||
assert_equal [lindex $reply 11 0 7 0 0] "100-0" ;# first entry in group's PEL
|
||||
assert_equal [lindex $reply 11 0 9 0 1] "Alice" ;# first consumer
|
||||
assert_equal [lindex $reply 11 0 9 0 7 0 0] "100-0" ;# first entry in first consumer's PEL
|
||||
assert_equal [lindex $reply 11 1 1] "g2" ;# second group name
|
||||
assert_equal [lindex $reply 11 1 9 0 1] "Charlie" ;# first consumer
|
||||
assert_equal [lindex $reply 11 1 9 0 7 0 0] "100-0" ;# first entry in first consumer's PEL
|
||||
assert_equal [lindex $reply 11 1 9 0 7 1 0] "101-0" ;# second entry in first consumer's PEL
|
||||
|
||||
set reply [r XINFO STREAM x FULL COUNT 1]
|
||||
assert_equal [llength $reply] 12
|
||||
assert_equal [lindex $reply 1] 4
|
||||
assert_equal [lindex $reply 9] "{100-0 {a 1}}"
|
||||
}
|
||||
|
||||
start_server {} {
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
|
@ -414,7 +414,7 @@ start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"xsetid"}} {
|
||||
start_server {tags {"stream xsetid"}} {
|
||||
test {XADD can CREATE an empty stream} {
|
||||
r XADD mystream MAXLEN 0 * a b
|
||||
assert {[dict get [r xinfo stream mystream] length] == 0}
|
||||
|
@ -419,4 +419,34 @@ start_server {tags {"string"}} {
|
||||
r set foo bar
|
||||
r getrange foo 0 4294967297
|
||||
} {bar}
|
||||
|
||||
set rna1 {CACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTCGTCCGGGTGTG}
|
||||
set rna2 {ATTAAAGGTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTT}
|
||||
set rnalcs {ACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTT}
|
||||
|
||||
test {STRALGO LCS string output with STRINGS option} {
|
||||
r STRALGO LCS STRINGS $rna1 $rna2
|
||||
} $rnalcs
|
||||
|
||||
test {STRALGO LCS len} {
|
||||
r STRALGO LCS LEN STRINGS $rna1 $rna2
|
||||
} [string length $rnalcs]
|
||||
|
||||
test {LCS with KEYS option} {
|
||||
r set virus1 $rna1
|
||||
r set virus2 $rna2
|
||||
r STRALGO LCS KEYS virus1 virus2
|
||||
} $rnalcs
|
||||
|
||||
test {LCS indexes} {
|
||||
dict get [r STRALGO LCS IDX KEYS virus1 virus2] matches
|
||||
} {{{238 238} {239 239}} {{236 236} {238 238}} {{229 230} {236 237}} {{224 224} {235 235}} {{1 222} {13 234}}}
|
||||
|
||||
test {LCS indexes with match len} {
|
||||
dict get [r STRALGO LCS IDX KEYS virus1 virus2 WITHMATCHLEN] matches
|
||||
} {{{238 238} {239 239} 1} {{236 236} {238 238} 1} {{229 230} {236 237} 2} {{224 224} {235 235} 1} {{1 222} {13 234} 222}}
|
||||
|
||||
test {LCS indexes with match len and minimum match len} {
|
||||
dict get [r STRALGO LCS IDX KEYS virus1 virus2 WITHMATCHLEN MINMATCHLEN 5] matches
|
||||
} {{{1 222} {13 234} 222}}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Settings
|
||||
BIN_PATH="../../src/"
|
||||
CLUSTER_HOST=127.0.0.1
|
||||
PORT=30000
|
||||
TIMEOUT=2000
|
||||
@ -25,7 +26,11 @@ then
|
||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||
PORT=$((PORT+1))
|
||||
echo "Starting $PORT"
|
||||
<<<<<<< HEAD
|
||||
../../src/keydb-pro-server --port $PORT --protected-mode $PROTECTED_MODE --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes ${ADDITIONAL_OPTIONS}
|
||||
=======
|
||||
$BIN_PATH/keydb-server --port $PORT --protected-mode $PROTECTED_MODE --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes ${ADDITIONAL_OPTIONS}
|
||||
>>>>>>> unstable
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
@ -37,7 +42,7 @@ then
|
||||
PORT=$((PORT+1))
|
||||
HOSTS="$HOSTS $CLUSTER_HOST:$PORT"
|
||||
done
|
||||
../../src/keydb-cli --cluster create $HOSTS --cluster-replicas $REPLICAS
|
||||
$BIN_PATH/keydb-cli --cluster create $HOSTS --cluster-replicas $REPLICAS
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@ -46,7 +51,7 @@ then
|
||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||
PORT=$((PORT+1))
|
||||
echo "Stopping $PORT"
|
||||
../../src/keydb-cli -p $PORT shutdown nosave
|
||||
$BIN_PATH/keydb-cli -p $PORT shutdown nosave
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
@ -57,7 +62,7 @@ then
|
||||
while [ 1 ]; do
|
||||
clear
|
||||
date
|
||||
../../src/keydb-cli -p $PORT cluster nodes | head -30
|
||||
$BIN_PATH/keydb-cli -p $PORT cluster nodes | head -30
|
||||
sleep 1
|
||||
done
|
||||
exit 0
|
||||
@ -81,7 +86,7 @@ if [ "$1" == "call" ]
|
||||
then
|
||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||
PORT=$((PORT+1))
|
||||
../../src/keydb-cli -p $PORT $2 $3 $4 $5 $6 $7 $8 $9
|
||||
$BIN_PATH/keydb-cli -p $PORT $2 $3 $4 $5 $6 $7 $8 $9
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
@ -101,7 +106,7 @@ then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Usage: $0 [start|create|stop|watch|tail|clean]"
|
||||
echo "Usage: $0 [start|create|stop|watch|tail|clean|call]"
|
||||
echo "start -- Launch Redis Cluster instances."
|
||||
echo "create -- Create a cluster using keydb-cli --cluster create."
|
||||
echo "stop -- Stop Redis Cluster instances."
|
||||
@ -110,3 +115,4 @@ echo "tail <id> -- Run tail -f of instance at base port + ID."
|
||||
echo "tailall -- Run tail -f for all the log files at once."
|
||||
echo "clean -- Remove all instances data, logs, configs."
|
||||
echo "clean-logs -- Remove just instances logs."
|
||||
echo "call <cmd> -- Call a command (up to 7 arguments) on all nodes."
|
||||
|
Loading…
x
Reference in New Issue
Block a user