diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml new file mode 100644 index 000000000..e03715d5c --- /dev/null +++ b/.github/workflows/daily.yml @@ -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 diff --git a/00-RELEASENOTES b/00-RELEASENOTES index 290158efb..7c5a75412 100644 --- a/00-RELEASENOTES +++ b/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 . +* 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 . + 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 ================================================================================ diff --git a/README.md b/README.md index 078dc6934..aa10a47dc 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [![Join the chat at https://gitter.im/KeyDB/community](https://badges.gitter.im/KeyDB/community.svg)](https://gitter.im/KeyDB/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![StackShare](http://img.shields.io/badge/tech-stack-0690fa.svg?style=flat)](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 diff --git a/keydb.conf b/keydb.conf index 63a4d75a2..03db7b6d0 100644 --- a/keydb.conf +++ b/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. diff --git a/src/Makefile b/src/Makefile index b5be5299a..5735da009 100644 --- a/src/Makefile +++ b/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 diff --git a/src/acl.cpp b/src/acl.cpp index 24d1e9f64..1453d1fa2 100644 --- a/src/acl.cpp +++ b/src/acl.cpp @@ -32,6 +32,7 @@ extern "C" { #include "sha256.h" } #include +#include /* ============================================================================= * 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 ... acl rules ... * ACL DELUSER [...] * ACL GETUSER - * ACL GENPASS + * ACL GENPASS [] * ACL WHOAMI * ACL LOG [ | 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 [attribs ...] -- Create or modify a user.", -"GETUSER -- Get the user details.", -"DELUSER [...] -- Delete a list of users.", -"CAT -- List available categories.", -"CAT -- List commands inside category.", -"GENPASS -- Generate a secure user password.", -"WHOAMI -- Return the current connection username.", -"LOG [ | 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 [attribs ...] -- Create or modify a user.", +"GETUSER -- Get the user details.", +"DELUSER [...] -- Delete a list of users.", +"CAT -- List available categories.", +"CAT -- List commands inside category.", +"GENPASS [] -- Generate a secure user password.", +"WHOAMI -- Return the current connection username.", +"LOG [ | RESET] -- Show the ACL log entries.", NULL }; addReplyHelp(c,help); diff --git a/src/adlist.c b/src/adlist.c index 359262521..7b7b012ce 100644 --- a/src/adlist.c +++ b/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) { diff --git a/src/adlist.h b/src/adlist.h index 996a2d83f..d1d34a10f 100644 --- a/src/adlist.h +++ b/src/adlist.h @@ -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 */ diff --git a/src/ae.cpp b/src/ae.cpp index ea2f68c24..33b7b184e 100644 --- a/src/ae.cpp +++ b/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 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); diff --git a/src/ae.h b/src/ae.h index e2dbcb754..156c219ef 100644 --- a/src/ae.h +++ b/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 */ diff --git a/src/aof.cpp b/src/aof.cpp index f866ee2a8..c678dcdde 100644 --- a/src/aof.cpp +++ b/src/aof.cpp @@ -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"); diff --git a/src/bio.cpp b/src/bio.cpp index 97fa7cf18..b3e9d0927 100644 --- a/src/bio.cpp +++ b/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", diff --git a/src/bitops.cpp b/src/bitops.cpp index 5bc156882..3643a4f49 100644 --- a/src/bitops.cpp +++ b/src/bitops.cpp @@ -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; } diff --git a/src/blocked.cpp b/src/blocked.cpp index 69ba0ec36..b71209296 100644 --- a/src/blocked.cpp +++ b/src/blocked.cpp @@ -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_locklock)> 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); diff --git a/src/cluster.cpp b/src/cluster.cpp index b6e9f87e4..72f12a375 100644 --- a/src/cluster.cpp +++ b/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(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++; diff --git a/src/cluster.h b/src/cluster.h index 59b3dc343..c6d714cf3 100644 --- a/src/cluster.h +++ b/src/cluster.h @@ -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 */ diff --git a/src/config.cpp b/src/config.cpp index 2def8b20a..a759c025b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -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), diff --git a/src/config.h b/src/config.h index d6bcc11d9..022cb0033 100644 --- a/src/config.h +++ b/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 +#define redis_set_thread_title(name) pthread_set_name_np(pthread_self(), name) +#elif defined __NetBSD__ +#include +#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 +#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 diff --git a/src/connection.h b/src/connection.h index ff8010f5c..f5624b596 100644 --- a/src/connection.h +++ b/src/connection.h @@ -31,6 +31,8 @@ #ifndef __REDIS_CONNECTION_H #define __REDIS_CONNECTION_H +#include + #define CONN_INFO_LEN 32 struct aeEventLoop; @@ -70,7 +72,7 @@ typedef struct ConnectionType { struct connection { ConnectionType *type; - ConnectionState state; + std::atomic 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 */ diff --git a/src/crc64.c b/src/crc64.c index f1f764922..4cbc019f6 100644 --- a/src/crc64.c +++ b/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 +/* Copyright (c) 2014, Matt Stancliff + * 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 +#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 diff --git a/src/crc64.h b/src/crc64.h index e63cbc2e3..08ac9f7b2 100644 --- a/src/crc64.h +++ b/src/crc64.h @@ -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 diff --git a/src/crcspeed.c b/src/crcspeed.c new file mode 100644 index 000000000..d2d97a8c7 --- /dev/null +++ b/src/crcspeed.c @@ -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 : + * - 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); +} diff --git a/src/crcspeed.h b/src/crcspeed.h new file mode 100644 index 000000000..d7ee95ebb --- /dev/null +++ b/src/crcspeed.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2014, Matt Stancliff + * 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 +#include + +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 diff --git a/src/cron.cpp b/src/cron.cpp index 9bc38fd70..584407fac 100644 --- a/src/cron.cpp +++ b/src/cron.cpp @@ -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); diff --git a/src/db.cpp b/src/db.cpp index 18687305d..7fde78da1 100644 --- a/src/db.cpp +++ b/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 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 ] ... */ +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 */ 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); } diff --git a/src/debug.cpp b/src/debug.cpp index 6de92df49..871d6c5a6 100644 --- a/src/debug.cpp +++ b/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 [prefix] [size] -- Create string keys named key:. 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 -- 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(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); } diff --git a/src/dict.cpp b/src/dict.cpp index fe495dc7b..4f28c0a5c 100644 --- a/src/dict.cpp +++ b/src/dict.cpp @@ -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); diff --git a/src/evict.cpp b/src/evict.cpp index afd42a9d9..e15ddb6cc 100644 --- a/src/evict.cpp +++ b/src/evict.cpp @@ -33,6 +33,7 @@ #include "server.h" #include "bio.h" #include "atomicvar.h" +#include /* ---------------------------------------------------------------------------- * 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(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 diff --git a/src/expire.cpp b/src/expire.cpp index 38e479821..c999ce708 100644 --- a/src/expire.cpp +++ b/src/expire.cpp @@ -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 { diff --git a/src/fastlock.cpp b/src/fastlock.cpp index bb8621906..cc6eea99a 100644 --- a/src/fastlock.cpp +++ b/src/fastlock.cpp @@ -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 m_mapwait; - fastlock m_lock { "deadlock detector" }; + public: void registerwait(fastlock *lock, pid_t thispid) { diff --git a/src/geo.cpp b/src/geo.cpp index 9a84b821f..c2edbcbbd 100644 --- a/src/geo.cpp +++ b/src/geo.cpp @@ -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++; } diff --git a/src/geohash.c b/src/geohash.c index db5ae025a..de9620b7a 100644 --- a/src/geohash.c +++ b/src/geohash.c @@ -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; } diff --git a/src/help.h b/src/help.h index 000499505..eae4e579b 100644 --- a/src/help.h +++ b/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" }, diff --git a/src/hyperloglog.cpp b/src/hyperloglog.cpp index dede1ffa0..dcc0efe3e 100644 --- a/src/hyperloglog.cpp +++ b/src/hyperloglog.cpp @@ -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); diff --git a/src/lazyfree.cpp b/src/lazyfree.cpp index 1ddc62a58..2939f9515 100644 --- a/src/lazyfree.cpp +++ b/src/lazyfree.cpp @@ -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; diff --git a/src/lolwut.h b/src/lolwut.h index 38c0de423..682d00531 100644 --- a/src/lolwut.h +++ b/src/lolwut.h @@ -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 diff --git a/src/module.cpp b/src/module.cpp index 682dc1830..799bbcb01 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -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; } diff --git a/src/multi.cpp b/src/multi.cpp index 50beb979b..7faad7cb5 100644 --- a/src/multi.cpp +++ b/src/multi.cpp @@ -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; } diff --git a/src/networking.cpp b/src/networking.cpp index ff07727bb..7a7eaa379 100644 --- a/src/networking.cpp +++ b/src/networking.cpp @@ -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