Compare commits

...
143 Commits
Author SHA1 Message Date
John Sully f83a89f82a Acquire the lock for modules that don't acquire it before calling like they are supposed to 2019-08-29 15:41:03 -04:00
John Sully bf905a78f0 Bump version 2019-08-27 15:32:24 -04:00
John Sully fad9483fc9 Fix race condition in PUB/SUB and other async reply commands where the client can be freed before our handler is executed on the client thread. When this occurs the client pointer is dangling 2019-08-27 15:31:47 -04:00
John Sully 6b6e6f6c1e Fix crash in RediSearch 2019-07-29 15:08:57 -04:00
John Sully b0a0f03b96 RREPLAY failures should be logged 2019-07-24 22:49:55 -04:00
John Sully f1982ca63d Issue #64 RREPLAY isn't binary safe. Add fix and test. 2019-07-24 22:31:44 -04:00
John Sully 25e18a7de9 Release version 5 2019-07-18 20:50:40 -04:00
John Sully bb090b6d20 Active Replicas should always use their own slaveseldb variable when sending/saving rdbs - never those of their masters. This is because the Active Replica is also simultaneously a master 2019-07-18 20:50:20 -04:00
John Sully a9c4e3970f remove unnecessary variables 2019-07-18 19:37:49 -04:00
John Sully d5ecc2347a Fix issue #59 - Test replicaof config variable 2019-07-18 19:22:49 -04:00
John Sully 58971a430d Add support for sanitizers and fix issues they have found 2019-07-18 18:39:42 -04:00
John Sully 1512274f4c Resolve race on neterr variable 2019-07-18 17:11:28 -04:00
John Sully e310d33c12 Make object refcounts atomic, technically it doesn't need to be currently but the semantics are complicated and error prone. This is much safer with little performance impact 2019-07-18 15:49:12 -04:00
John Sully ad32ba0343 Suppress initialized data sanitizer warnings 2019-07-18 15:49:03 -04:00
John Sully 4e30a5b4e5 Another RESP3 regression 2019-07-17 22:57:52 -04:00
John Sully c8516f3dc4 Fix Issue #58 2019-07-17 02:34:02 -04:00
John Sully aae0fdc114 Shared objects should be marked as such 2019-07-16 18:32:01 -04:00
John Sully a19d0f67b9 Crash when aborting SYNC with a master on a thread other than main 2019-07-15 14:55:41 -04:00
John Sully 971f69fe1f Implement test mode to make finding bugs easier 2019-07-15 14:55:18 -04:00
John Sully 7468c691ad Blocked clients can stall when under low load 2019-07-13 16:44:11 -04:00
John Sully 620c2ec141 Give the active-rep more time to sync for test reliability 2019-07-12 23:52:07 -04:00
John Sully 3081b6f98b Fix more locking deadlocks 2019-07-12 23:51:45 -04:00
John Sully 8630339e43 Fix deadlock with client and ae locks 2019-07-12 20:46:50 -04:00
John Sully 528d10091f Add Active Replication tests 2019-07-12 03:54:41 -04:00
John Sully 149c3cf3dd FIX: The dabase count configuration is not respected 2019-07-12 02:13:37 -04:00
John Sully f12c3cf9dd Update README.md 2019-07-11 20:29:47 -04:00
John Sully 1f08a0efb3 Fix compile errors on GCC v5 2019-07-11 20:20:01 -04:00
John Sully 6cf85d1d01 Fix more accounting errors with active replication and the replication offset. Specifically we don't update repl_ack_off as frequently as we do reploff_skipped 2019-07-11 19:20:12 -04:00
John Sully b5101a460d Active Replicas are not slaves 2019-07-11 18:51:20 -04:00
John Sully a5784ef09e Fix active replication offset synchronization accounting, and enable the wait command 2019-07-11 17:00:23 -04:00
John Sully 122ec25bf0 lazyfree needs to consume more memory to be detectable 2019-07-06 00:53:40 -04:00
John Sully d013ad9eba Redis only does this in beforeSleep function - doing extra calls like we do causes test issues 2019-07-06 00:53:20 -04:00
John Sully d918ca6fa5 Add back user space buffering of RDB save 2019-07-06 00:36:23 -04:00
John Sully 852819180f We can use less memory in the client output buffers. Its OK 2019-07-05 23:43:01 -04:00
John Sully 218111eb72 Fix additional warning related to new() overloads 2019-07-05 23:42:39 -04:00
John Sully 1531cfb7ee Fix additional warnings 2019-06-27 16:29:36 -04:00
John Sully 16a89f21dc Fix mac build breaks 2019-06-27 15:04:09 -04:00
John Sully 79e5160e37 Fix libcxxabi clang compile failure in travis, as well as bool issue due to old clang 2019-06-18 00:42:01 -04:00
John Sully 510e966dc0 Bump travis compiler version 2019-06-18 00:42:01 -04:00
John Sully 8238d3207e Fix Travis CI issue 2019-06-17 23:56:23 -04:00
John Sully e37bc4ad40 Fix issues with relaxed memory model architectures 2019-06-17 23:00:12 -04:00
John Sully 0f78b8b917 Remove unnecessary recursive lock 2019-06-17 23:00:12 -04:00
John Sully 19061fe04f missing file :) 2019-06-17 23:00:12 -04:00
John Sully 9c901eb65f Fix test failures due to assert 2019-06-17 23:00:12 -04:00
John Sully ae33ff3a6e convert the pending write list to a vector for better performance and less cache thrashing 2019-06-17 23:00:12 -04:00
John Sully 59dbd625c1 Move more code out of the GIL 2019-06-17 23:00:12 -04:00
John Sully 8eb4e355b9 Remove an unnecessary lock that eats performance 2019-06-17 23:00:12 -04:00
John Sully 8b76b01bfe Even the next up thread should sleep in the futex 2019-06-17 23:00:12 -04:00
John Sully ec57b4b024 Fallback to a futex if we spin for a long time 2019-06-17 23:00:12 -04:00
John Sully 46d6caa47c Enable load balancing of UNIX sockets across threads 2019-06-11 01:34:36 -04:00
John Sully 97b65ce19c Fix crash adding duplicate replica. Just send an error instead 2019-06-02 15:50:50 -04:00
John Sully 55d0ef40b0 Merge branch 'unstable' of https://github.com/JohnSully/redis2 into unstable 2019-06-01 03:23:21 -04:00
John Sully a6c56416fc Implement MOTD feature in keydb-cli 2019-05-21 15:12:12 -04:00
John Sully cd3c482d5e Update README.md 2019-05-21 00:04:12 -04:00
John Sully c5cbb77fc7 Fix regressions from RESP3 changes in commit 317f8b9d38 2019-05-20 23:39:44 -04:00
John Sully d7f21f02b3 The unstable branch doesn't have a version # 2019-05-20 16:12:26 -04:00
John Sully 87dc8ce9b3 Fix double unlock 2019-05-20 15:52:04 -04:00
John Sully 2e22aa1a9c Merge branch 'unstable' of https://github.com/JohnSully/redis2 into unstable 2019-05-20 10:57:59 -04:00
John Sully 8c56dbf971 Protocol regression from RESP3 work 2019-05-20 10:57:37 -04:00
Qix a78169e9e0 cast void data pointer to const char * to fix gcc 2019-05-19 16:24:39 -04:00
John Sully 39a60c8120 Fix build breaks on some older compilers 2019-05-18 20:58:48 -04:00
John Sully c57d8760bd Fix build break on some compilers 2019-05-18 20:41:05 -04:00
John Sully 3eed2a8780 Merge branch 'unstable' of https://github.com/JohnSully/redis2 into unstable 2019-05-18 20:25:23 -04:00
John Sully 23b0268a7f Print nicer error messages for uncaught exceptions 2019-05-18 20:24:29 -04:00
John Sully 3d0de946bb Fix: Active replication falls behind under high load 2019-05-18 20:23:20 -04:00
John Sully 866b8ead91 Update Active Replication wiki links 2019-05-11 21:53:30 -04:00
John Sully aa274960ff Fix a few issues from the merge 2019-05-11 19:26:59 -04:00
John Sully 54d6eb851b Make lazy free test more reliable 2019-05-11 17:57:35 -04:00
John Sully 86d7276f24 Merge branch 'unstable' of https://github.com/antirez/redis into MergeRedis
Note: some tests failing
2019-05-11 02:20:34 -04:00
John Sully 0918651438 Add byline to logo 2019-05-10 23:48:14 -04:00
Salvatore Sanfilippo e633254ccf Merge pull request #6053 from soloestoy/enhance-aof-fsync-everysec
aof: enhance AOF_FSYNC_EVERYSEC, more details in #5985
2019-05-10 18:06:40 +02:00
Salvatore Sanfilippo 34f6d94701 Merge pull request #6074 from trevor211/fixActiveExpire
Do not active expire keys in the background when the switch is off.
2019-05-10 12:39:35 +02:00
Salvatore Sanfilippo 1e9b8d853d Merge pull request #6080 from soloestoy/fix-makefile
Makefile: 1TD -> STD
2019-05-10 12:37:07 +02:00
Salvatore Sanfilippo 2903c56bda Merge pull request #6073 from yossigo/fix/blocked-client-id
Preserve client->id for blocked clients.
2019-05-10 12:32:47 +02:00
Salvatore Sanfilippo 6b490e33c4 Merge pull request #6081 from Happy4Code/mod_comments
change the comments there may have a mis type
2019-05-10 12:28:12 +02:00
Salvatore Sanfilippo 891f4a38f6 Merge pull request #6077 from zltl/patch-7
delete sdsTest() from REDIS_TEST in server.c to fix build failed
2019-05-10 12:27:47 +02:00
Salvatore Sanfilippo 433f763814 Merge pull request #6083 from tangquanawj/fix-inappropriate-add-operation-code
fix-inappropriate-add-operation-code
2019-05-10 12:25:18 +02:00
Salvatore Sanfilippo 402b0d0bd1 Merge pull request #6086 from yongman/fix-uint64-in-defrag
Fix uint64_t hash value in active defrag
2019-05-10 12:23:59 +02:00
Salvatore Sanfilippo 19c3d5b743 Merge pull request #6087 from AngusP/linux-build-warnings
[Minor] Fix build warnings on Linux
2019-05-10 12:23:42 +02:00
Salvatore Sanfilippo 1212f22858 Merge pull request #6091 from soloestoy/fix-test-skip-till
test cases: skiptill -> skip-till
2019-05-10 12:22:19 +02:00
zhaozhao.zz c64aec9ce7 test cases: skiptill -> skip-till 2019-05-10 16:27:25 +08:00
Angus Pearson a1fb0be1d7 Enlarge error buffer in redis-check-aof.c to remove compiler warning of output truncation through snprintf format string 2019-05-08 12:13:45 +01:00
Angus Pearson bea09a7fa6 Add <strings.h> include to deps/hiredis/read.c to fix Implicit Declaration of strcasecmp warning 2019-05-08 11:36:31 +01:00
yongman fb4ee7f0c5 Fix uint64_t hash value in active defrag 2019-05-08 16:29:16 +08:00
唐权 842dd85b26 Update ziplist.c
Hi, @antirez

In the code, to get the size of ziplist, "unsigned int bytes = ZIPLIST_HEADER_SIZE+1;" is correct, 
but why not make it more readable and easy to understand
2019-05-08 12:53:56 +08:00
Salvatore Sanfilippo f674e83215 Merge pull request #6082 from soloestoy/fix-config-rewrite-memleak
fix memory leak when rewrite config file
2019-05-07 10:25:13 +02:00
zhaozhao.zz 48d591a010 fix memory leak when rewrite config file 2019-05-07 15:59:16 +08:00
stan011 d9d3d3065b change the comments there may have a mis type 2019-05-07 14:22:40 +08:00
zhaozhao.zz 340a723b87 Makefile: 1TD -> STD 2019-05-07 13:35:27 +08:00
Salvatore Sanfilippo a72ab70fee Merge pull request #6070 from oranagra/stabilize_repl_tests
make replication tests more stable on slow machines
2019-05-06 18:05:33 +02:00
antirez 3d053dbb6d Threaded IO: handleClientsWithPendingReadsUsingThreads top comment. 2019-05-06 18:02:51 +02:00
antirez 5baeb14cf3 Threaded IO: configuration directive for turning on/off reads. 2019-05-06 18:02:51 +02:00
antirez 1c0c436757 Threaded IO: ability to disable reads from threaded path. 2019-05-06 18:02:51 +02:00
antirez 90d720e7a5 Threaded IO: put fflush() inside tio_debug conditional. 2019-05-06 18:02:51 +02:00
antirez 6ab6a97fe6 Threaded IO: parsing WIP 2: refactoring to parse from thread. 2019-05-06 18:02:51 +02:00
antirez 647a66ebba Threaded IO: parsing WIP 1: set current_client in a better scoped way. 2019-05-06 18:02:51 +02:00
antirez 463ccf8664 Threaded IO: logging should be safe in I/O threads.
Potentially it is possible that we get interleaved writes, even if
serverLog() makes sure to write into a buffer and then use printf(), so
even this should be ok. However in general POSIX guarantees that writing
to the same file pointer object from multiple threads is safe. Anyway
currently we *reopen* the file at each call, but for the standard output
logging.

The logging functions actually also access global configuration while
performing the log (for instance in order to check the log level, the
log filename and so forth), however dunring the I/O threads execution
we cannot alter such shared state in any way.
2019-05-06 18:02:51 +02:00
antirez 8d7d2be24f Threaded IO: process read queue before stopping threads. 2019-05-06 18:02:51 +02:00
antirez 63a0ffd36a Threaded IO: read side WIP 3. 2019-05-06 18:02:51 +02:00
antirez a2245f8ff1 Threaded IO: read side WIP 2. 2019-05-06 18:02:51 +02:00
antirez dd5b105c73 Threaded IO: read side WIP. 2019-05-06 18:02:51 +02:00
antirez 74591fb5bd Threaded IO: hide more debugging printfs under conditional. 2019-05-06 18:02:51 +02:00
antirez 9814b2a5f3 Threaded IO: make num of I/O threads configurable. 2019-05-06 18:02:51 +02:00
antirez 30091dc29f Threaded IO: use main thread if num of threads is 1. 2019-05-06 18:02:51 +02:00
Ubuntu 9bf7f302a7 Threaded IO: stop threads when no longer needed + C11 in Makefile.
Now threads are stopped even when the connections drop immediately to
zero, not allowing the networking code to detect the condition and stop
the threads. serverCron() will handle that.
2019-05-06 18:02:51 +02:00
antirez ea35a81c42 Threaded IO: 3rd version: use the mutex only to stop the thread. 2019-05-06 18:02:51 +02:00
antirez 6f4f36c0fb Threaded IO: second attempt without signaling conditions. 2019-05-06 18:02:51 +02:00
antirez a2dbd9bd97 Threaded IO: allow to disable debug printf. 2019-05-06 18:02:51 +02:00
antirez f468e653b5 Threaded IO: implement handleClientsWithPendingWritesUsingThreads().
This is just an experiment for now, there are a couple of race
conditions, mostly harmless for the performance gain experiment that
this commit represents so far.

The general idea here is to take Redis single threaded and instead
fan-out on expansive kernel calls: write(2) in this case, but the same
concept could be easily implemented for read(2) and protcol parsing.

However just threading writes like in this commit, is enough to evaluate
if the approach is sounding.
2019-05-06 18:02:51 +02:00
liaotonglang b1c7e3393d delete sdsTest() from REDIS_TEST
sdsTest() defined in sds.c dit not match the call in server.c.
remove it from REDIS_TEST, since test-sds defined in Makefile.
2019-05-06 19:24:00 +08:00
WuYunlong 1c0913dc4e Do not active expire keys in the background when the switch is off. 2019-05-06 11:46:07 +08:00
Yossi Gottlieb 9f3679880a Preserve client->id for blocked clients. 2019-05-05 20:32:53 +03:00
Oran Agra ba809f26d4 make replication tests more stable on slow machines
solving few replication related tests race conditions which fail on slow machines

bugfix in slave buffers test: since the test is executed twice, each time with
a different commands count, the threshold for the delta can't be a constant.
2019-05-05 08:25:01 +03:00
Salvatore Sanfilippo 0a6090bfd8 Merge pull request #6051 from vattezhang/unstable
fix: benchmark auth fails when server have requirepass
2019-05-02 09:48:02 +02:00
zhaozhao.zz bcac165fab aof: enhance AOF_FSYNC_EVERYSEC, more details in #5985 2019-04-29 14:38:28 +08:00
Salvatore Sanfilippo 843de8b786 Merge pull request #5971 from devnexen/unstable
build fix
2019-04-26 17:32:21 +02:00
Salvatore Sanfilippo 5ba908fa5f Merge pull request #6002 from yongman/fix-memleak-in-bitfield
Fix memleak in bitfieldCommand
2019-04-26 17:31:42 +02:00
Salvatore Sanfilippo 5c5197fe4f Merge pull request #6013 from rouzier/fix/stream_iterator_init
Fix stream interator start and end key initialize
2019-04-26 17:25:20 +02:00
Salvatore Sanfilippo 2d450d1315 Merge pull request #6039 from git-hulk/fix/benchmark-coredump-in-arguments
FIX: coredump in redis-benchmark
2019-04-26 17:17:55 +02:00
abhay 162208f94d removed obsolete warning as per - https://github.com/antirez/redis/issues/5291 2019-04-26 17:03:16 +02:00
vattezhang 4e38ced488 fix: benchmark auth fails when server have requirepass 2019-04-26 19:47:07 +08:00
vattezhang bc36404c79 update 2019-04-26 18:50:51 +08:00
vattezhang d3d96b041a Merge github.com:antirez/redis into unstable 2019-04-26 18:47:11 +08:00
git-hulk fa97ef34ca FIX: core dump in redis-benchmark when the -r is the last arg 2019-04-24 20:05:35 +08:00
antirez 4a5057000a Merge branch 'unstable' of github.com:/antirez/redis into unstable 2019-04-15 16:50:48 +02:00
antirez 487601d85d Test: disable module testing for now. 2019-04-15 16:50:26 +02:00
James Rouzier 3ccdcbc088 Fix start and end key initialize 2019-04-11 12:19:02 -04:00
Salvatore Sanfilippo d044e33c20 Merge pull request #5961 from yossigo/modules-tests
Modules tests
2019-04-11 07:56:23 -07:00
antirez 9e67691ffb Aesthetic change to #5962 to conform to Redis style. 2019-04-10 18:53:35 +02:00
Salvatore Sanfilippo 6de5d25062 Merge pull request #5962 from oranagra/module_blocked_reply
slave corrupts replication stream when module blocked client uses large reply (or POSTPONED_ARRAY)
2019-04-10 09:41:36 -07:00
yongman d490752d58 Fix memleak in bitfieldCommand 2019-04-09 09:24:22 +08:00
antirez c24e32041b ACL: Fix memory leak in ACLResetSubcommandsForCommand().
This commit fixes bug reported at #5998. Thanks to @tomcat1102.
2019-04-08 18:08:37 +02:00
antirez f8a9708aa7 ACL: regression test for #5998. 2019-04-08 18:06:50 +02:00
antirez 75648f99a5 Fix assert comparison in fetchClusterSlotsConfiguration(). 2019-04-08 17:39:26 +02:00
David Carlier 4de88828d9 build fix 2019-03-28 06:38:16 +00:00
Oran Agra acba2fc9b4 slave corrupts replication stream when module blocked client uses large reply (or POSTPONED_ARRAY)
when redis appends the blocked client reply list to the real client, it didn't
bother to check if it is in fact the master client. so a slave executing that
module command will send replies to the master, causing the master to send the
slave error responses, which will mess up the replication offset
(slave will advance it's replication offset, and the master does not)
2019-03-24 14:17:37 +02:00
Yossi Gottlieb ec0b6bd2c3 Add runtest-moduleapi with commandfilter coverage. 2019-03-24 12:03:03 +02:00
Yossi Gottlieb 822a992f91 fix: missing initialization. 2019-03-24 12:00:33 +02:00
vattezhang ad223e2042 fix: fix benchmark cannot exit when NOAUTH err happens 2019-03-13 20:46:33 +08:00
vattezhang 31110f1caf Merge branch 'unstable' of github.com:antirez/redis into unstable 2019-03-13 20:26:54 +08:00
vattezhang 270a111433 Merge branch 'unstable' of github.com:antirez/redis into unstable 2019-03-12 21:52:20 +08:00
vattezhang 0f0f787a37 fix: fix sentinel command table and new flags format 2019-02-27 21:20:00 +08:00
vattezhang 4cc43a96f6 benchmark: add auth check in benchmark
When we run benchmark but forget to set the right requirepass, benchmark should return error.
2019-02-18 22:48:55 +08:00
66 changed files with 1549 additions and 699 deletions

No files matched your search

+4 -3
View File
@@ -4,11 +4,11 @@ matrix:
include:
- os: linux
script: make
env: COMPILER_NAME=gcc CXX=g++-5 CC=gcc-5
env: COMPILER_NAME=g++-6 CXX=g++-6 CC=gcc-6
addons:
apt:
packages:
- g++-5
- g++-6
- nasm
- uuid-dev
sources: &sources
@@ -16,10 +16,11 @@ matrix:
- ubuntu-toolchain-r-test
- os: linux
script: make MALLOC=libc
env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8 CXXFLAGS="-stdlib=libc++" LDFLAGS="-stdlib=libc++"
env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8 CXXFLAGS="-I/usr/include/libcxxabi/" LDFLAGS="-lc++"
addons:
apt:
packages:
- libc++abi-dev
- clang-3.8
- libc++-dev
- libc++abi-dev
-56
View File
@@ -1,56 +0,0 @@
{
"files.associations": {
"zmalloc.h": "c",
"stat.h": "c",
"array": "cpp",
"atomic": "cpp",
"*.tcc": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"fstream": "cpp",
"functional": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"memory": "cpp",
"mutex": "cpp",
"new": "cpp",
"numeric": "cpp",
"optional": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"scoped_allocator": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeinfo": "cpp",
"utility": "cpp"
}
}
+5 -3
View File
@@ -1,10 +1,11 @@
![Current Release](https://img.shields.io/github/release/JohnSully/KeyDB.svg)
[![Build Status](https://travis-ci.org/JohnSully/KeyDB.svg?branch=unstable)](https://travis-ci.org/JohnSully/KeyDB) [![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)
What is KeyDB?
--------------
KeyDB is a high performance fork of Redis focusing on multithreading, memory efficiency, and high throughput. In addition to multithreading KeyDB also has features only available in Redis Enterprise such as [Active Replication](https://github.com/JohnSully/KeyDB/wiki/KeyDB-(Redis-Fork):-Active-Replica-Support), [FLASH storage](https://github.com/JohnSully/KeyDB/wiki/FLASH-Storage) support, and some not available at all such as direct backup to AWS S3.
KeyDB is a high performance fork of Redis focusing on multithreading, memory efficiency, and high throughput. In addition to multithreading KeyDB also has features only available in Redis Enterprise such as [Active Replication](https://github.com/JohnSully/KeyDB/wiki/Active-Replication), [FLASH storage](https://github.com/JohnSully/KeyDB/wiki/FLASH-Storage) support, and some not available at all such as direct backup to AWS S3.
On the same hardware KeyDB can perform twice as many queries per second as Redis, with 60% lower latency.
@@ -14,12 +15,14 @@ Try our docker container: https://hub.docker.com/r/eqalpha/keydb
Talk on Gitter: https://gitter.im/KeyDB
[Subscribe to the KeyDB mailing list](https://eqalpha.us20.list-manage.com/subscribe/post?u=978f486c2f95589b24591a9cc&id=4ab9220500)
Management GUI: We recommend [FastoNoSQL](https://fastonosql.com/) which has official KeyDB support.
New: Active Replica Support
---------------------------
New! KeyDB now has support for Active Replicas. This feature greatly simplifies hot-spare failover and allows you to distribute writes over replicas instead of just a single master. For more information [see the wiki page](https://github.com/JohnSully/KeyDB/wiki/KeyDB-(Redis-Fork):-Active-Replica-Support).
New! KeyDB now has support for Active Replicas. This feature greatly simplifies hot-spare failover and allows you to distribute writes over replicas instead of just a single master. For more information [see the wiki page](https://github.com/JohnSully/KeyDB/wiki/Active-Replication).
Why fork Redis?
---------------
@@ -249,4 +252,3 @@ source distribution.
Please see the CONTRIBUTING file in this source distribution for more
information.
+1
View File
@@ -31,6 +31,7 @@
#include "fmacros.h"
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
+1 -7
View File
@@ -942,13 +942,7 @@ aof-use-rdb-preamble yes
lua-time-limit 5000
################################ REDIS CLUSTER ###############################
#
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# WARNING EXPERIMENTAL: Redis Cluster is considered to be stable code, however
# in order to mark it as "mature" we need to wait for a non trivial percentage
# of users to deploy it in production.
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
# started as cluster nodes can. In order to start a Redis instance as a
# cluster node enable the cluster support uncommenting the following:
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
TCL_VERSIONS="8.5 8.6"
TCLSH=""
for VERSION in $TCL_VERSIONS; do
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
done
if [ -z $TCLSH ]
then
echo "You need tcl 8.5 or newer in order to run the Redis test"
exit 1
fi
make -C tests/modules && \
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter "${@}"
+18 -6
View File
@@ -20,8 +20,8 @@ DEPENDENCY_TARGETS=hiredis linenoise lua
NODEPS:=clean distclean
# Default settings
STD=-std=c99 -pedantic -DREDIS_STATIC=''
CXX_STD=-std=c++14 -pedantic -fno-rtti -fno-exceptions -D__STDC_FORMAT_MACROS
STD=-std=c11 -pedantic -DREDIS_STATIC=''
CXX_STD=-std=c++14 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS
ifneq (,$(findstring clang,$(CC)))
ifneq (,$(findstring FreeBSD,$(uname_S)))
STD+=-Wno-c11-extensions
@@ -45,6 +45,17 @@ endif
endif
USEASM?=true
ifneq ($(SANITIZE),)
CC=clang
CXX=clang++
CFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
CXXFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
LDFLAGS+= -fsanitize=$(SANITIZE)
MALLOC=libc
USEASM=false
endif
# Do we use our assembly spinlock? X64 only
ifeq ($(uname_S),Linux)
ifeq ($(uname_M),x86_64)
@@ -140,6 +151,7 @@ else
# All the other OSes (notably Linux)
FINAL_LDFLAGS+= -rdynamic
FINAL_LIBS+=-ldl -pthread -lrt -luuid
FINAL_CFLAGS += -DMOTD
endif
endif
endif
@@ -197,11 +209,11 @@ endif
REDIS_SERVER_NAME=keydb-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 $(ASM_OBJ)
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o $(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 $(ASM_OBJ)
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o $(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 $(ASM_OBJ)
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
REDIS_CHECK_AOF_NAME=keydb-check-aof
@@ -264,7 +276,7 @@ $(REDIS_CHECK_AOF_NAME): $(REDIS_SERVER_NAME)
# keydb-cli
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS) -lcurl
# keydb-benchmark
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
+2
View File
@@ -542,6 +542,8 @@ struct redisCommand *ACLLookupCommand(const char *name) {
* and command ID. */
void ACLResetSubcommandsForCommand(user *u, unsigned long id) {
if (u->allowed_subcommands && u->allowed_subcommands[id]) {
for (int i = 0; u->allowed_subcommands[id][i]; i++)
sdsfree(u->allowed_subcommands[id][i]);
zfree(u->allowed_subcommands[id]);
u->allowed_subcommands[id] = NULL;
}
+37 -6
View File
@@ -191,6 +191,36 @@ void aeProcessCmd(aeEventLoop *eventLoop, int fd, void *, int )
}
}
// Unlike write() this is an all or nothing thing. We will block if a partial write is hit
ssize_t safe_write(int fd, const void *pv, size_t cb)
{
const char *pcb = (const char*)pv;
ssize_t written = 0;
do
{
ssize_t rval = write(fd, pcb, cb);
if (rval > 0)
{
pcb += rval;
cb -= rval;
written += rval;
}
else if (errno == EAGAIN)
{
if (written == 0)
break;
// if we've already written something then we're committed so keep trying
}
else
{
if (rval == 0)
return written;
return rval;
}
} while (cb);
return written;
}
int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData, int fSynchronous)
{
@@ -199,7 +229,7 @@ int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
int ret = AE_OK;
aeCommand cmd;
aeCommand cmd = {};
cmd.op = AE_ASYNC_OP::CreateFileEvent;
cmd.fd = fd;
cmd.mask = mask;
@@ -212,9 +242,10 @@ int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
if (fSynchronous)
cmd.pctl->mutexcv.lock();
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
auto size = safe_write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
if (size != sizeof(cmd))
{
AE_ASSERT(size == sizeof(cmd) || size <= 0);
AE_ASSERT(errno == EAGAIN);
ret = AE_ERR;
}
@@ -236,7 +267,7 @@ int aePostFunction(aeEventLoop *eventLoop, aePostFunctionProc *proc, void *arg)
proc(arg);
return AE_OK;
}
aeCommand cmd;
aeCommand cmd = {};
cmd.op = AE_ASYNC_OP::PostFunction;
cmd.proc = proc;
cmd.clientData = arg;
@@ -253,7 +284,7 @@ int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fSynch
return AE_OK;
}
aeCommand cmd;
aeCommand cmd = {};
cmd.op = AE_ASYNC_OP::PostCppFunction;
cmd.pfn = new (MALLOC_LOCAL) std::function<void()>(fn);
cmd.pctl = nullptr;
@@ -803,9 +834,9 @@ void aeAcquireLock()
g_lock.lock();
}
int aeTryAcquireLock()
int aeTryAcquireLock(int fWeak)
{
return g_lock.try_lock();
return g_lock.try_lock(!!fWeak);
}
void aeReleaseLock()
+1 -1
View File
@@ -159,7 +159,7 @@ int aeGetSetSize(aeEventLoop *eventLoop);
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
void aeAcquireLock();
int aeTryAcquireLock();
int aeTryAcquireLock(int fWeak);
void aeReleaseLock();
int aeThreadOwnsLock();
+69
View File
@@ -0,0 +1,69 @@
#pragma once
class AeLocker
{
bool m_fArmed = false;
public:
AeLocker()
{
}
void arm(client *c) // if a client is passed, then the client is already locked
{
if (c != nullptr)
{
serverAssert(!m_fArmed);
serverAssert(c->lock.fOwnLock());
if (!aeTryAcquireLock(true /*fWeak*/)) // avoid locking the client if we can
{
bool fOwnClientLock = true;
int clientNesting = 1;
for (;;)
{
if (fOwnClientLock)
{
clientNesting = c->lock.unlock_recursive();
fOwnClientLock = false;
}
aeAcquireLock();
if (!c->lock.try_lock(false)) // ensure a strong try because aeAcquireLock is expensive
{
aeReleaseLock();
}
else
{
break;
}
}
c->lock.lock_recursive(clientNesting);
}
m_fArmed = true;
}
else if (!m_fArmed)
{
m_fArmed = true;
aeAcquireLock();
}
}
void disarm()
{
serverAssert(m_fArmed);
m_fArmed = false;
aeReleaseLock();
}
bool isArmed() const
{
return m_fArmed;
}
~AeLocker()
{
if (m_fArmed)
aeReleaseLock();
}
};
+32 -5
View File
@@ -199,6 +199,12 @@ ssize_t aofRewriteBufferWrite(int fd) {
* AOF file implementation
* ------------------------------------------------------------------------- */
/* Return true if an AOf fsync is currently already in progress in a
* BIO thread. */
int aofFsyncInProgress(void) {
return bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
}
/* Starts a background task that performs fsync() against the specified
* file descriptor (the one of the AOF file) in another thread. */
void aof_background_fsync(int fd) {
@@ -337,10 +343,24 @@ void flushAppendOnlyFile(int force) {
int sync_in_progress = 0;
mstime_t latency;
if (sdslen(g_pserver->aof_buf) == 0) return;
if (sdslen(g_pserver->aof_buf) == 0) {
/* Check if we need to do fsync even the aof buffer is empty,
* because previously in AOF_FSYNC_EVERYSEC mode, fsync is
* called only when aof buffer is not empty, so if users
* stop write commands before fsync called in one second,
* the data in page cache cannot be flushed in time. */
if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC &&
g_pserver->aof_fsync_offset != g_pserver->aof_current_size &&
g_pserver->unixtime > g_pserver->aof_last_fsync &&
!(sync_in_progress = aofFsyncInProgress())) {
goto try_fsync;
} else {
return;
}
}
if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC)
sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
sync_in_progress = aofFsyncInProgress();
if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
/* With this append fsync policy we do background fsyncing.
@@ -472,6 +492,7 @@ void flushAppendOnlyFile(int force) {
g_pserver->aof_buf = sdsempty();
}
try_fsync:
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
* children doing I/O in the background. */
if (g_pserver->aof_no_fsync_on_rewrite &&
@@ -486,10 +507,14 @@ void flushAppendOnlyFile(int force) {
redis_fsync(g_pserver->aof_fd); /* Let's try to get this data on the disk */
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("aof-fsync-always",latency);
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
g_pserver->aof_last_fsync = g_pserver->unixtime;
} else if ((g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC &&
g_pserver->unixtime > g_pserver->aof_last_fsync)) {
if (!sync_in_progress) aof_background_fsync(g_pserver->aof_fd);
if (!sync_in_progress) {
aof_background_fsync(g_pserver->aof_fd);
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
}
g_pserver->aof_last_fsync = g_pserver->unixtime;
}
}
@@ -703,6 +728,7 @@ int loadAppendOnlyFile(char *filename) {
* operation is received. */
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
g_pserver->aof_current_size = 0;
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
fclose(fp);
return C_ERR;
}
@@ -726,7 +752,7 @@ int loadAppendOnlyFile(char *filename) {
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
rioInitWithFile(&rdb,fileno(fp));
rioInitWithFile(&rdb,fp);
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
if (rdbLoadRio(&rdb,&rsi,1) != C_OK) {
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
@@ -842,6 +868,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
stopLoading();
aofUpdateCurrentSize();
g_pserver->aof_rewrite_base_size = g_pserver->aof_current_size;
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
return C_OK;
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
@@ -1373,7 +1400,7 @@ int rewriteAppendOnlyFile(char *filename) {
}
g_pserver->aof_child_diff = sdsempty();
rioInitWithFile(&aof,fileno(fp));
rioInitWithFile(&aof,fp);
if (g_pserver->aof_rewrite_incremental_fsync)
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
+3
View File
@@ -36,4 +36,7 @@ const char *ascii_logo =
" Port: %d\n"
" PID: %ld\n"
" \n"
" Like KeyDB? Star us on GitHub! \n"
" \n"
" https://github.com/JohnSully/KeyDB \n"
" \n\n";
+8 -2
View File
@@ -995,12 +995,18 @@ void bitfieldCommand(client *c) {
/* Lookup for read is ok if key doesn't exit, but errors
* if it's not a string. */
o = lookupKeyRead(c->db,c->argv[1]);
if (o != nullptr && checkType(c,o,OBJ_STRING)) return;
if (o != nullptr && checkType(c,o,OBJ_STRING)) {
zfree(ops);
return;
}
} else {
/* Lookup by making room up to the farest bit reached by
* this operation. */
if ((o = lookupStringForBitCommand(c,
highest_write_offset)) == nullptr) return;
highest_write_offset)) == nullptr) {
zfree(ops);
return;
}
}
addReplyArrayLen(c,numops);
+3
View File
@@ -64,6 +64,7 @@
*/
#include "server.h"
#include <mutex>
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
@@ -180,6 +181,7 @@ void queueClientForReprocessing(client *c) {
* of operation the client is blocking for. */
void unblockClient(client *c) {
serverAssert(GlobalLocksAcquired());
serverAssert(c->lock.fOwnLock());
if (c->btype == BLOCKED_LIST ||
c->btype == BLOCKED_ZSET ||
c->btype == BLOCKED_STREAM) {
@@ -301,6 +303,7 @@ void handleClientsBlockedOnKeys(void) {
while(numclients--) {
listNode *clientnode = listFirst(clients);
client *receiver = (client*)clientnode->value;
std::unique_lock<decltype(client::lock)> lock(receiver->lock);
if (receiver->btype != BLOCKED_LIST) {
/* Put at the tail, so that at the next call
+7 -7
View File
@@ -643,11 +643,11 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
if (listLength(g_pserver->masters) == 0 && g_pserver->loading) return;
while(max--) {
cfd = anetTcpAccept(g_pserver->neterr, fd, cip, sizeof(cip), &cport);
cfd = anetTcpAccept(serverTL->neterr, fd, cip, sizeof(cip), &cport);
if (cfd == ANET_ERR) {
if (errno != EWOULDBLOCK)
serverLog(LL_VERBOSE,
"Error accepting cluster node: %s", g_pserver->neterr);
"Error accepting cluster node: %s", serverTL->neterr);
return;
}
anetNonBlock(NULL,cfd);
@@ -3407,7 +3407,7 @@ void clusterCron(void) {
mstime_t old_ping_sent;
clusterLink *link;
fd = anetTcpNonBlockBindConnect(g_pserver->neterr, node->ip,
fd = anetTcpNonBlockBindConnect(serverTL->neterr, node->ip,
node->cport, NET_FIRST_BIND_ADDR);
if (fd == -1) {
/* We got a synchronous error from connect before
@@ -3418,7 +3418,7 @@ void clusterCron(void) {
if (node->ping_sent == 0) node->ping_sent = mstime();
serverLog(LL_DEBUG, "Unable to connect to "
"Cluster Node [%s]:%d -> %s", node->ip,
node->cport, g_pserver->neterr);
node->cport, serverTL->neterr);
continue;
}
link = createClusterLink(node);
@@ -5010,15 +5010,15 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
}
/* Create the socket */
fd = anetTcpNonBlockConnect(g_pserver->neterr,szFromObj(c->argv[1]),
fd = anetTcpNonBlockConnect(serverTL->neterr,szFromObj(c->argv[1]),
atoi(szFromObj(c->argv[2])));
if (fd == -1) {
sdsfree(name);
addReplyErrorFormat(c,"Can't connect to target node: %s",
g_pserver->neterr);
serverTL->neterr);
return NULL;
}
anetEnableTcpNoDelay(g_pserver->neterr,fd);
anetEnableTcpNoDelay(serverTL->neterr,fd);
/* Check if it connects within the specified timeout. */
if ((aeWait(fd,AE_WRITABLE,timeout) & AE_WRITABLE) == 0) {
+2
View File
@@ -864,6 +864,8 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0], "version-override") && argc == 2) {
KEYDB_SET_VERSION = zstrdup(argv[1]);
serverLog(LL_WARNING, "Warning version is overriden to: %s\n", KEYDB_SET_VERSION);
} else if (!strcasecmp(argv[0],"testmode") && argc == 2){
g_fTestMode = yesnotoi(argv[1]);
} else {
err = "Bad directive or wrong number of arguments"; goto loaderr;
}
+7 -4
View File
@@ -55,6 +55,8 @@ typedef ucontext_t sigcontext_t;
#endif
#endif
bool g_fInCrash = false;
/* ================================= Debugging ============================== */
/* Compute the sha1 of string at 's' with 'len' bytes long.
@@ -436,7 +438,7 @@ NULL
"Value at:%p refcount:%d "
"encoding:%s serializedlength:%zu "
"lru:%d lru_seconds_idle:%llu%s",
(void*)val, val->refcount,
(void*)val, static_cast<int>(val->refcount),
strenc, rdbSavedObjectLen(val),
val->lru, estimateObjectIdleTime(val)/1000, extra);
} else if (!strcasecmp(szFromObj(c->argv[1]),"sdslen") && c->argc == 3) {
@@ -706,7 +708,7 @@ void _serverAssertPrintClientInfo(const client *c) {
bugReportStart();
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
serverLog(LL_WARNING,"client->flags = %d", c->flags);
serverLog(LL_WARNING,"client->flags = %d", static_cast<int>(c->flags));
serverLog(LL_WARNING,"client->fd = %d", c->fd);
serverLog(LL_WARNING,"client->argc = %d", c->argc);
for (j=0; j < c->argc; j++) {
@@ -721,14 +723,14 @@ void _serverAssertPrintClientInfo(const client *c) {
arg = buf;
}
serverLog(LL_WARNING,"client->argv[%d] = \"%s\" (refcount: %d)",
j, arg, c->argv[j]->refcount);
j, arg, static_cast<int>(c->argv[j]->refcount));
}
}
void serverLogObjectDebugInfo(robj_roptr o) {
serverLog(LL_WARNING,"Object type: %d", o->type);
serverLog(LL_WARNING,"Object encoding: %d", o->encoding);
serverLog(LL_WARNING,"Object refcount: %d", o->refcount);
serverLog(LL_WARNING,"Object refcount: %d", static_cast<int>(o->refcount));
if (o->type == OBJ_STRING && sdsEncodedObject(o)) {
serverLog(LL_WARNING,"Object raw string len: %zu", sdslen(szFromObj(o)));
if (sdslen(szFromObj(o)) < 4096) {
@@ -1356,6 +1358,7 @@ void dumpX86Calls(void *addr, size_t len) {
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
ucontext_t *uc = (ucontext_t*) secret;
g_fInCrash = true;
void *eip = getMcontextEip(uc);
sds infostring, clients;
struct sigaction act;
+3 -3
View File
@@ -47,7 +47,7 @@ extern "C" int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
/* forward declarations*/
void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, long *defragged);
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
/* Defrag helper for generic allocations.
*
@@ -353,7 +353,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
sdsele = (sds)ln->value;
if ((newsds = activeDefragSds(sdsele))) {
/* When defragging an sds value, we need to update the dict key */
unsigned int hash = dictGetHash(d, sdsele);
uint64_t hash = dictGetHash(d, sdsele);
replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
ln->value = newsds;
defragged++;
@@ -390,7 +390,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
* moved. Return value is the the dictEntry if found, or NULL if not found.
* NOTE: this is very ugly code, but it let's us avoid the complication of
* doing a scan on another dict. */
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, long *defragged) {
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged) {
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, oldkey, hash);
if (deref) {
dictEntry *de = *deref;
+1 -1
View File
@@ -78,7 +78,7 @@ unsigned int getLRUClock(void) {
unsigned int LRU_CLOCK(void) {
unsigned int lruclock;
if (1000/g_pserver->hz <= LRU_CLOCK_RESOLUTION) {
atomicGet(g_pserver->lruclock,lruclock);
lruclock = g_pserver->lruclock;
} else {
lruclock = getLRUClock();
}
+178 -23
View File
@@ -36,6 +36,10 @@
#include <assert.h>
#include <pthread.h>
#include <limits.h>
#ifdef __linux__
#include <linux/futex.h>
#endif
#include <string.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
@@ -49,6 +53,11 @@
#endif
#endif
#ifndef UNUSED
#define UNUSED(x) ((void)x)
#endif
/****************************************************
*
* Implementation of a fair spinlock. To promote fairness we
@@ -56,14 +65,86 @@
*
****************************************************/
#if !defined(__has_feature)
#define __has_feature(x) 0
#endif
#if __has_feature(thread_sanitizer)
/* Report that a lock has been created at address "lock". */
#define ANNOTATE_RWLOCK_CREATE(lock) \
AnnotateRWLockCreate(__FILE__, __LINE__, lock)
/* Report that the lock at address "lock" is about to be destroyed. */
#define ANNOTATE_RWLOCK_DESTROY(lock) \
AnnotateRWLockDestroy(__FILE__, __LINE__, lock)
/* Report that the lock at address "lock" has been acquired.
is_w=1 for writer lock, is_w=0 for reader lock. */
#define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) \
AnnotateRWLockAcquired(__FILE__, __LINE__, lock, is_w)
/* Report that the lock at address "lock" is about to be released. */
#define ANNOTATE_RWLOCK_RELEASED(lock, is_w) \
AnnotateRWLockReleased(__FILE__, __LINE__, lock, is_w)
#if defined(DYNAMIC_ANNOTATIONS_WANT_ATTRIBUTE_WEAK)
#if defined(__GNUC__)
#define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK __attribute__((weak))
#else
/* TODO(glider): for Windows support we may want to change this macro in order
to prepend __declspec(selectany) to the annotations' declarations. */
#error weak annotations are not supported for your compiler
#endif
#else
#define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK
#endif
extern "C" {
void AnnotateRWLockCreate(
const char *file, int line,
const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
void AnnotateRWLockDestroy(
const char *file, int line,
const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
void AnnotateRWLockAcquired(
const char *file, int line,
const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
void AnnotateRWLockReleased(
const char *file, int line,
const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK;
}
#else
#define ANNOTATE_RWLOCK_CREATE(lock)
#define ANNOTATE_RWLOCK_DESTROY(lock)
#define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w)
#define ANNOTATE_RWLOCK_RELEASED(lock, is_w)
#endif
static_assert(sizeof(pid_t) <= sizeof(fastlock::m_pidOwner), "fastlock::m_pidOwner not large enough");
uint64_t g_longwaits = 0;
uint64_t fastlock_getlongwaitcount()
{
return g_longwaits;
uint64_t rval;
__atomic_load(&g_longwaits, &rval, __ATOMIC_RELAXED);
return rval;
}
#ifndef ASM_SPINLOCK
#ifdef __linux__
static int futex(volatile unsigned *uaddr, int futex_op, int val,
const struct timespec *timeout, int val3)
{
return syscall(SYS_futex, uaddr, futex_op, val,
timeout, uaddr, val3);
}
#endif
#endif
extern "C" pid_t gettid()
{
@@ -88,72 +169,129 @@ extern "C" void fastlock_init(struct fastlock *lock)
lock->m_ticket.m_avail = 0;
lock->m_depth = 0;
lock->m_pidOwner = -1;
lock->futex = 0;
ANNOTATE_RWLOCK_CREATE(lock);
}
#ifndef ASM_SPINLOCK
extern "C" void fastlock_lock(struct fastlock *lock)
{
if ((int)__atomic_load_4(&lock->m_pidOwner, __ATOMIC_ACQUIRE) == gettid())
int pidOwner;
__atomic_load(&lock->m_pidOwner, &pidOwner, __ATOMIC_ACQUIRE);
if (pidOwner == gettid())
{
++lock->m_depth;
return;
}
unsigned myticket = __atomic_fetch_add(&lock->m_ticket.m_avail, 1, __ATOMIC_RELEASE);
#ifdef __linux__
unsigned mask = (1U << (myticket % 32));
#endif
int cloops = 0;
while (__atomic_load_2(&lock->m_ticket.m_active, __ATOMIC_ACQUIRE) != myticket)
ticket ticketT;
for (;;)
{
if ((++cloops % 1024*1024) == 0)
{
sched_yield();
++g_longwaits;
}
__atomic_load(&lock->m_ticket.u, &ticketT.u, __ATOMIC_ACQUIRE);
if ((ticketT.u & 0xffff) == myticket)
break;
#if defined(__i386__) || defined(__amd64__)
__asm__ ("pause");
#endif
if ((++cloops % 1024*1024) == 0)
{
#ifdef __linux__
__atomic_fetch_or(&lock->futex, mask, __ATOMIC_ACQUIRE);
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, ticketT.u, nullptr, mask);
__atomic_fetch_and(&lock->futex, ~mask, __ATOMIC_RELEASE);
#endif
__atomic_fetch_add(&g_longwaits, 1, __ATOMIC_RELAXED);
}
}
lock->m_depth = 1;
__atomic_store_4(&lock->m_pidOwner, gettid(), __ATOMIC_RELEASE);
int tid = gettid();
__atomic_store(&lock->m_pidOwner, &tid, __ATOMIC_RELEASE);
ANNOTATE_RWLOCK_ACQUIRED(lock, true);
std::atomic_thread_fence(std::memory_order_acquire);
}
extern "C" int fastlock_trylock(struct fastlock *lock)
extern "C" int fastlock_trylock(struct fastlock *lock, int fWeak)
{
if ((int)__atomic_load_4(&lock->m_pidOwner, __ATOMIC_ACQUIRE) == gettid())
int tid;
__atomic_load(&lock->m_pidOwner, &tid, __ATOMIC_ACQUIRE);
if (tid == gettid())
{
++lock->m_depth;
return true;
}
// cheap test
if (lock->m_ticket.m_active != lock->m_ticket.m_avail)
struct ticket ticketT;
__atomic_load(&lock->m_ticket.u, &ticketT.u, __ATOMIC_ACQUIRE);
if (ticketT.m_active != ticketT.m_avail)
return false;
uint16_t active = __atomic_load_2(&lock->m_ticket.m_active, __ATOMIC_RELAXED);
uint16_t active = ticketT.m_active;
uint16_t next = active + 1;
struct ticket ticket_expect { active, active };
struct ticket ticket_setiflocked { active, next };
if (__atomic_compare_exchange(&lock->m_ticket, &ticket_expect, &ticket_setiflocked, false /*weak*/, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE))
struct ticket ticket_expect { { { active, active } } };
struct ticket ticket_setiflocked { { { active, next } } };
if (__atomic_compare_exchange(&lock->m_ticket, &ticket_expect, &ticket_setiflocked, fWeak /*weak*/, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
{
lock->m_depth = 1;
__atomic_store_4(&lock->m_pidOwner, gettid(), __ATOMIC_RELEASE);
tid = gettid();
__atomic_store(&lock->m_pidOwner, &tid, __ATOMIC_RELEASE);
ANNOTATE_RWLOCK_ACQUIRED(lock, true);
return true;
}
return false;
}
#ifdef __linux__
#define ROL32(v, shift) ((v << shift) | (v >> (32-shift)))
void unlock_futex(struct fastlock *lock, uint16_t ifutex)
{
unsigned mask = (1U << (ifutex % 32));
unsigned futexT;
__atomic_load(&lock->futex, &futexT, __ATOMIC_RELAXED);
futexT &= mask;
if (futexT == 0)
return;
for (;;)
{
__atomic_load(&lock->futex, &futexT, __ATOMIC_ACQUIRE);
futexT &= mask;
if (!futexT)
break;
if (futex(&lock->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask) == 1)
break;
}
}
#endif
extern "C" void fastlock_unlock(struct fastlock *lock)
{
--lock->m_depth;
if (lock->m_depth == 0)
{
assert((int)__atomic_load_4(&lock->m_pidOwner, __ATOMIC_RELAXED) >= 0); // unlock after free
lock->m_pidOwner = -1;
std::atomic_thread_fence(std::memory_order_acquire);
__atomic_fetch_add(&lock->m_ticket.m_active, 1, __ATOMIC_ACQ_REL); // on x86 the atomic is not required here, but ASM handles that case
int pidT;
__atomic_load(&lock->m_pidOwner, &pidT, __ATOMIC_RELAXED);
assert(pidT >= 0); // unlock after free
int t = -1;
__atomic_store(&lock->m_pidOwner, &t, __ATOMIC_RELEASE);
std::atomic_thread_fence(std::memory_order_release);
ANNOTATE_RWLOCK_RELEASED(lock, true);
uint16_t activeNew = __atomic_add_fetch(&lock->m_ticket.m_active, 1, __ATOMIC_RELEASE); // on x86 the atomic is not required here, but ASM handles that case
#ifdef __linux__
unlock_futex(lock, activeNew);
#else
UNUSED(activeNew);
#endif
}
}
#endif
@@ -164,10 +302,27 @@ extern "C" void fastlock_free(struct fastlock *lock)
assert((lock->m_ticket.m_active == lock->m_ticket.m_avail) // Asser the lock is unlocked
|| (lock->m_pidOwner == gettid() && (lock->m_ticket.m_active == lock->m_ticket.m_avail-1))); // OR we own the lock and nobody else is waiting
lock->m_pidOwner = -2; // sentinal value indicating free
ANNOTATE_RWLOCK_DESTROY(lock);
}
bool fastlock::fOwnLock()
{
return gettid() == m_pidOwner;
int tid;
__atomic_load(&m_pidOwner, &tid, __ATOMIC_RELAXED);
return gettid() == tid;
}
int fastlock_unlock_recursive(struct fastlock *lock)
{
int rval = lock->m_depth;
lock->m_depth = 1;
fastlock_unlock(lock);
return rval;
}
void fastlock_lock_recursive(struct fastlock *lock, int nesting)
{
fastlock_lock(lock);
lock->m_depth = nesting;
}
+29 -5
View File
@@ -9,9 +9,11 @@ extern "C" {
struct fastlock;
void fastlock_init(struct fastlock *lock);
void fastlock_lock(struct fastlock *lock);
int fastlock_trylock(struct fastlock *lock);
int fastlock_trylock(struct fastlock *lock, int fWeak);
void fastlock_unlock(struct fastlock *lock);
void fastlock_free(struct fastlock *lock);
int fastlock_unlock_recursive(struct fastlock *lock);
void fastlock_lock_recursive(struct fastlock *lock, int nesting);
uint64_t fastlock_getlongwaitcount(); // this is a global value
@@ -20,17 +22,29 @@ uint64_t fastlock_getlongwaitcount(); // this is a global value
}
#endif
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
struct ticket
{
uint16_t m_active;
uint16_t m_avail;
union
{
struct
{
uint16_t m_active;
uint16_t m_avail;
};
unsigned u;
};
};
#pragma GCC diagnostic pop
struct fastlock
{
volatile struct ticket m_ticket;
volatile int m_pidOwner;
volatile int m_depth;
unsigned futex;
#ifdef __cplusplus
fastlock()
@@ -43,9 +57,9 @@ struct fastlock
fastlock_lock(this);
}
bool try_lock()
bool try_lock(bool fWeak = false)
{
return !!fastlock_trylock(this);
return !!fastlock_trylock(this, fWeak);
}
void unlock()
@@ -53,6 +67,16 @@ struct fastlock
fastlock_unlock(this);
}
int unlock_recursive()
{
return fastlock_unlock_recursive(this);
}
void lock_recursive(int nesting)
{
fastlock_lock_recursive(this, nesting);
}
bool fOwnLock(); // true if this thread owns the lock, NOTE: not 100% reliable, use for debugging only
#endif
};
+46 -11
View File
@@ -22,7 +22,7 @@ fastlock_lock:
push rdi ; we need our struct pointer (also balance the stack for the call)
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
mov esi, eax ; back it up in esi
mov rdi, [rsp] ; get our pointer back
pop rdi ; get our pointer back
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
je .LLocked ; Don't spin in that case
@@ -30,11 +30,11 @@ fastlock_lock:
xor eax, eax ; eliminate partial register dependency
inc eax ; we want to add one
lock xadd [rdi+2], ax ; do the xadd, ax contains the value before the addition
; eax now contains the ticket
xor ecx, ecx
; ax now contains the ticket
ALIGN 16
.LLoop:
cmp [rdi], ax ; is our ticket up?
mov edx, [rdi]
cmp dx, ax ; is our ticket up?
je .LLocked ; leave the loop
pause
add ecx, 1000h ; Have we been waiting a long time? (oflow if we have)
@@ -44,22 +44,34 @@ ALIGN 16
; But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
; taking a long time to be released anyways. We optimize for the common case of short
; lock intervals. That's why we're using a spinlock in the first place
; If we get here we're going to sleep in the kernel with a futex
push rsi
push rax
mov rax, 24 ; sys_sched_yield
syscall ; give up our timeslice we'll be here a while
pop rax
pop rsi
; Setup the syscall args
; rdi ARG1 futex (already in rdi)
mov esi, (9 | 128) ; rsi ARG2 FUTEX_WAIT_BITSET_PRIVATE
; rdx ARG3 ticketT.u (already in edx)
xor r10d, r10d ; r10 ARG4 NULL
mov r8, rdi ; r8 ARG5 dup rdi
xor r9d, r9d
bts r9d, eax ; r9 ARG6 mask
mov eax, 202 ; sys_futex
; Do the syscall
lock or [rdi+12], r9d ; inform the unlocking thread we're waiting
syscall ; wait for the futex
not r9d ; convert our flag into a mask of bits not to touch
lock and [rdi+12], r9d ; clear the flag in the futex control mask
; cleanup and continue
mov rcx, g_longwaits
inc qword [rcx] ; increment our long wait counter
mov rdi, [rsp] ; our struct pointer is on the stack already
pop rax
pop rsi
xor ecx, ecx ; Reset our loop counter
jmp .LLoop ; Get back in the game
ALIGN 16
.LLocked:
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
inc dword [rdi+8] ; lock->m_depth++
add rsp, 8 ; fix stack
ret
ALIGN 16
@@ -114,9 +126,32 @@ fastlock_unlock:
; uint16_t avail
; int32_t m_pidOwner
; int32_t m_depth
push r11
sub dword [rdi+8], 1 ; decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
jnz .LDone ; if depth is non-zero this is a recursive unlock, and we still hold it
mov dword [rdi+4], -1 ; pidOwner = -1 (we don't own it anymore)
inc word [rdi] ; give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
mov ecx, [rdi] ; get current active (this one)
inc ecx ; bump it to the next thread
mov [rdi], cx ; give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
; At this point the lock is removed, however we must wake up any pending futexs
mov r9d, 1 ; eax is the bitmask for 2 threads
rol r9d, cl ; place the mask in the right spot for the next 2 threads
ALIGN 16
.LRetryWake:
mov r11d, [rdi+12] ; load the futex mask
and r11d, r9d ; are any threads waiting on a futex?
jz .LDone ; if not we're done.
; we have to wake the futexs
; rdi ARG1 futex (already in rdi)
mov esi, (10 | 128) ; rsi ARG2 FUTEX_WAKE_BITSET_PRIVATE
mov edx, 0x7fffffff ; rdx ARG3 INT_MAX (number of threads to wake)
xor r10d, r10d ; r10 ARG4 NULL
mov r8, rdi ; r8 ARG5 dup rdi
; r9 ARG6 mask (already set above)
mov eax, 202 ; sys_futex
syscall
cmp eax, 1 ; did we wake as many as we expected?
jnz .LRetryWake
.LDone:
pop r11
ret
+17 -10
View File
@@ -763,6 +763,7 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
module->usedby = listCreate();
module->usingMods = listCreate();
module->filters = listCreate();
module->in_call = 0;
ctx->module = module;
}
@@ -3786,14 +3787,7 @@ void moduleHandleBlockedClients(int iel) {
* replies to send to the client in a thread safe context.
* We need to glue such replies to the client output buffer and
* free the temporary client we just used for the replies. */
if (c) {
if (bc->reply_client->bufpos)
addReplyProto(c,bc->reply_client->buf,
bc->reply_client->bufpos);
if (listLength(bc->reply_client->reply))
listJoin(c->reply,bc->reply_client->reply);
c->reply_bytes += bc->reply_client->reply_bytes;
}
if (c) AddReplyFromClient(c, bc->reply_client);
freeClient(bc->reply_client);
if (c != NULL) {
@@ -3812,7 +3806,7 @@ void moduleHandleBlockedClients(int iel) {
AssertCorrectThread(c);
fastlock_lock(&g_pserver->rgthreadvar[c->iel].lockPendingWrite);
listAddNodeHead(g_pserver->rgthreadvar[c->iel].clients_pending_write,c);
g_pserver->rgthreadvar[c->iel].clients_pending_write.push_back(c);
fastlock_unlock(&g_pserver->rgthreadvar[c->iel].lockPendingWrite);
}
}
@@ -3917,13 +3911,18 @@ RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) {
* in order to keep things like the currently selected database and similar
* things. */
ctx->client = createClient(-1, IDX_EVENT_LOOP_MAIN);
if (bc) selectDb(ctx->client,bc->dbid);
if (bc) {
selectDb(ctx->client,bc->dbid);
ctx->client->id = bc->client->id;
}
return ctx;
}
/* Release a thread safe context. */
void RM_FreeThreadSafeContext(RedisModuleCtx *ctx) {
moduleAcquireGIL(false /*fServerThread*/);
moduleFreeContext(ctx);
moduleReleaseGIL(false /*fServerThread*/);
zfree(ctx);
}
@@ -3933,6 +3932,8 @@ void RM_FreeThreadSafeContext(RedisModuleCtx *ctx) {
void RM_ThreadSafeContextLock(RedisModuleCtx *ctx) {
UNUSED(ctx);
moduleAcquireGIL(FALSE /*fServerThread*/);
if (serverTL == nullptr)
serverTL = &g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN]; // arbitrary module threads get the main thread context
}
/* Release the server lock after a thread safe API call was executed. */
@@ -4991,6 +4992,12 @@ int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *fi
return REDISMODULE_OK;
}
int moduleHasCommandFilters()
{
// Note: called outside the global lock
return listLength(moduleCommandFilters);
}
void moduleCallCommandFilters(client *c) {
if (listLength(moduleCommandFilters) == 0) return;
+1 -6
View File
@@ -13,7 +13,7 @@ endif
.SUFFIXES: .c .so .xo .o
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so hellofilter.so
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so
.c.xo:
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
@@ -47,11 +47,6 @@ hellodict.xo: ../redismodule.h
hellodict.so: hellodict.xo
hellofilter.xo: ../redismodule.h
hellofilter.so: hellofilter.xo
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
testmodule.xo: ../redismodule.h
testmodule.so: testmodule.xo
+1 -1
View File
@@ -40,7 +40,7 @@
/* Timer callback. */
void timerHandler(RedisModuleCtx *ctx, void *data) {
REDISMODULE_NOT_USED(ctx);
printf("Fired %s!\n", data);
printf("Fired %s!\n", (const char *) data);
RedisModule_Free(data);
}
-1
View File
@@ -82,7 +82,6 @@ void discardTransaction(client *c) {
/* Flag the transacation as DIRTY_EXEC so that EXEC will fail.
* Should be called every time there is an error while queueing a command. */
void flagTransaction(client *c) {
serverAssert(GlobalLocksAcquired());
if (c->flags & CLIENT_MULTI)
c->flags |= CLIENT_DIRTY_EXEC;
}
+258 -182
View File
@@ -30,71 +30,18 @@
#include "server.h"
#include "atomicvar.h"
#include <sys/socket.h>
#include <sys/uio.h>
#include <math.h>
#include <ctype.h>
#include <vector>
#include <mutex>
#include "aelocker.h"
static void setProtocolError(const char *errstr, client *c);
void addReplyLongLongWithPrefixCore(client *c, long long ll, char prefix, bool fAsync);
void addReplyBulkCStringCore(client *c, const char *s, bool fAsync);
class AeLocker
{
bool m_fArmed = false;
public:
AeLocker()
{
}
void arm(client *c) // if a client is passed, then the client is already locked
{
if (c != nullptr)
{
serverAssert(!m_fArmed);
serverAssert(c->lock.fOwnLock());
bool fClientLocked = true;
while (!aeTryAcquireLock())
{
if (fClientLocked) c->lock.unlock();
fClientLocked = false;
aeAcquireLock();
if (!c->lock.try_lock())
{
aeReleaseLock();
}
else
{
break;
}
}
m_fArmed = true;
}
else if (!m_fArmed)
{
m_fArmed = true;
aeAcquireLock();
}
}
void disarm()
{
serverAssert(m_fArmed);
m_fArmed = false;
aeReleaseLock();
}
~AeLocker()
{
if (m_fArmed)
aeReleaseLock();
}
};
/* Return the size consumed from the allocator, for the specified SDS string,
* including internal fragmentation. This function is used in order to compute
* the client output buffer size. */
@@ -167,7 +114,7 @@ client *createClient(int fd, int iel) {
selectDb(c,0);
uint64_t client_id;
atomicGetIncr(g_pserver->next_client_id,client_id,1);
client_id = g_pserver->next_client_id.fetch_add(1);
c->iel = iel;
fastlock_init(&c->lock);
c->id = client_id;
@@ -197,6 +144,7 @@ client *createClient(int fd, int iel) {
c->replstate = REPL_STATE_NONE;
c->repl_put_online_on_ack = 0;
c->reploff = 0;
c->reploff_skipped = 0;
c->read_reploff = 0;
c->repl_ack_off = 0;
c->repl_ack_time = 0;
@@ -226,6 +174,7 @@ client *createClient(int fd, int iel) {
c->bufAsync = NULL;
c->buflenAsync = 0;
c->bufposAsync = 0;
c->casyncOpsPending = 0;
memset(c->uuid, 0, UUID_BINARY_LEN);
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
@@ -261,7 +210,7 @@ void clientInstallWriteHandler(client *c) {
* we'll not be able to write the whole reply at once. */
c->flags |= CLIENT_PENDING_WRITE;
std::unique_lock<fastlock> lockf(g_pserver->rgthreadvar[c->iel].lockPendingWrite);
listAddNodeHead(g_pserver->rgthreadvar[c->iel].clients_pending_write,c);
g_pserver->rgthreadvar[c->iel].clients_pending_write.push_back(c);
}
}
@@ -675,7 +624,7 @@ void setDeferredPushLen(client *c, void *node, long length) {
/* Add a double as a bulk reply */
void addReplyDoubleCore(client *c, double d, bool fAsync) {
if (isinf(d)) {
if (std::isinf(d)) {
/* Libc in odd systems (Hi Solaris!) will format infinite in a
* different way, so better to handle it in an explicit way. */
if (c->resp == 2) {
@@ -838,8 +787,11 @@ void addReplyNullCore(client *c, bool fAsync) {
}
}
void addReplyNull(client *c) {
addReplyNullCore(c, false);
void addReplyNull(client *c, robj_roptr objOldProtocol) {
if (c->resp < 3 && objOldProtocol != nullptr)
addReply(c, objOldProtocol);
else
addReplyNullCore(c, false);
}
void addReplyNullAsync(client *c) {
@@ -931,7 +883,10 @@ void addReplyBulkSdsAsync(client *c, sds s) {
/* Add a C null term string as bulk reply */
void addReplyBulkCStringCore(client *c, const char *s, bool fAsync) {
if (s == NULL) {
addReplyNullCore(c,fAsync);
if (c->resp < 3)
addReplyCore(c,shared.nullbulk, fAsync);
else
addReplyNullCore(c,fAsync);
} else {
addReplyBulkCBufferCore(c,s,strlen(s),fAsync);
}
@@ -1011,6 +966,19 @@ void addReplySubcommandSyntaxError(client *c) {
sdsfree(cmd);
}
/* Append 'src' client output buffers into 'dst' client output buffers.
* This function clears the output buffers of 'src' */
void AddReplyFromClient(client *dst, client *src) {
if (prepareClientToWrite(dst, false) != C_OK)
return;
addReplyProto(dst,src->buf, src->bufpos);
if (listLength(src->reply))
listJoin(dst->reply,src->reply);
dst->reply_bytes += src->reply_bytes;
src->reply_bytes = 0;
src->bufpos = 0;
}
/* Copy 'src' client output buffers into 'dst' client output buffers.
* The function takes care of freeing the old output buffers of the
* destination client. */
@@ -1036,7 +1004,6 @@ static void acceptCommonHandler(int fd, int flags, char *ip, int iel) {
serverLog(LL_WARNING,
"Error registering fd event for the new client: %s (fd=%d)",
strerror(errno),fd);
close(fd); /* May be already closed, just ignore errors */
return;
}
@@ -1120,20 +1087,40 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
UNUSED(privdata);
while(max--) {
cfd = anetTcpAccept(g_pserver->neterr, fd, cip, sizeof(cip), &cport);
cfd = anetTcpAccept(serverTL->neterr, fd, cip, sizeof(cip), &cport);
if (cfd == ANET_ERR) {
if (errno != EWOULDBLOCK)
serverLog(LL_WARNING,
"Accepting client connection: %s", g_pserver->neterr);
"Accepting client connection: %s", serverTL->neterr);
return;
}
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
int ielCur = ielFromEventLoop(el);
// We always accept on the same thread
aeAcquireLock();
acceptCommonHandler(cfd,0,cip, ielCur);
aeReleaseLock();
if (!g_fTestMode)
{
// We always accept on the same thread
LLocalThread:
aeAcquireLock();
acceptCommonHandler(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);
aePostFunction(g_pserver->rgthreadvar[iel].el, [cfd, iel, szT]{
acceptCommonHandler(cfd,0,szT, iel);
zfree(szT);
});
}
}
}
@@ -1144,18 +1131,28 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
UNUSED(privdata);
while(max--) {
cfd = anetUnixAccept(g_pserver->neterr, fd);
cfd = anetUnixAccept(serverTL->neterr, fd);
if (cfd == ANET_ERR) {
if (errno != EWOULDBLOCK)
serverLog(LL_WARNING,
"Accepting client connection: %s", g_pserver->neterr);
"Accepting client connection: %s", serverTL->neterr);
return;
}
int ielCur = ielFromEventLoop(el);
serverLog(LL_VERBOSE,"Accepted connection to %s", g_pserver->unixsocket);
aeAcquireLock();
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL, ielCur);
int ielTarget = rand() % cserver.cthreads;
if (ielTarget == ielCur)
{
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL, ielCur);
}
else
{
aePostFunction(g_pserver->rgthreadvar[ielTarget].el, [cfd, ielTarget]{
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL, ielTarget);
});
}
aeReleaseLock();
}
@@ -1236,9 +1233,10 @@ void unlinkClient(client *c) {
/* Remove from the list of pending writes if needed. */
if (c->flags & CLIENT_PENDING_WRITE) {
std::unique_lock<fastlock> lockf(g_pserver->rgthreadvar[c->iel].lockPendingWrite);
ln = listSearchKey(g_pserver->rgthreadvar[c->iel].clients_pending_write,c);
serverAssert(ln != NULL);
listDelNode(g_pserver->rgthreadvar[c->iel].clients_pending_write,ln);
auto itr = std::find(g_pserver->rgthreadvar[c->iel].clients_pending_write.begin(),
g_pserver->rgthreadvar[c->iel].clients_pending_write.end(), c);
serverAssert(itr != g_pserver->rgthreadvar[c->iel].clients_pending_write.end());
g_pserver->rgthreadvar[c->iel].clients_pending_write.erase(itr);
c->flags &= ~CLIENT_PENDING_WRITE;
}
@@ -1268,7 +1266,7 @@ void unlinkClient(client *c) {
}
}
void freeClient(client *c) {
bool freeClient(client *c) {
listNode *ln;
serverAssert(c->fd == -1 || GlobalLocksAcquired());
AssertCorrectThread(c);
@@ -1276,9 +1274,9 @@ void freeClient(client *c) {
/* If a client is protected, yet we need to free it right now, make sure
* to at least use asynchronous freeing. */
if (c->flags & CLIENT_PROTECTED) {
if (c->flags & CLIENT_PROTECTED || c->casyncOpsPending) {
freeClientAsync(c);
return;
return false;
}
/* If it is our master that's beging disconnected we should make sure
@@ -1293,7 +1291,7 @@ void freeClient(client *c) {
CLIENT_BLOCKED)))
{
replicationCacheMaster(MasterInfoFromClient(c), c);
return;
return false;
}
}
@@ -1372,6 +1370,7 @@ void freeClient(client *c) {
ulock.unlock();
fastlock_free(&c->lock);
zfree(c);
return true;
}
/* Schedule a client to free it at a safe time in the serverCron() function.
@@ -1379,28 +1378,42 @@ void freeClient(client *c) {
* 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) {
if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return;
AeLocker lock;
lock.arm(nullptr);
/* We need to handle concurrent access to the server.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
* idle. */
if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return; // check without the lock first
std::lock_guard<decltype(c->lock)> clientlock(c->lock);
AeLocker lock;
lock.arm(c);
if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return; // race condition after we acquire the lock
c->flags |= CLIENT_CLOSE_ASAP;
listAddNodeTail(g_pserver->clients_to_close,c);
}
void freeClientsInAsyncFreeQueue(int iel) {
serverAssert(GlobalLocksAcquired());
listIter li;
listNode *ln;
listRewind(g_pserver->clients_to_close,&li);
while((ln = listNext(&li))) {
// Store the clients in a temp vector since freeClient will modify this list
std::vector<client*> vecclientsFree;
while((ln = listNext(&li)))
{
client *c = (client*)listNodeValue(ln);
if (c->iel != iel)
continue; // wrong thread
if (c->iel == iel)
{
vecclientsFree.push_back(c);
listDelNode(g_pserver->clients_to_close, ln);
}
}
for (client *c : vecclientsFree)
{
c->flags &= ~CLIENT_CLOSE_ASAP;
freeClient(c);
listDelNode(g_pserver->clients_to_close,ln);
listRewind(g_pserver->clients_to_close,&li);
}
}
@@ -1414,7 +1427,12 @@ client *lookupClientByID(uint64_t id) {
}
/* Write data in output buffers to client. Return C_OK if the client
* is still valid after the call, C_ERR if it was freed. */
* is still valid after the call, C_ERR if it was freed because of some
* error.
*
* This function is called by threads, but always with handler_installed
* set to 0. So when handler_installed is set to 0 the function must be
* thread safe. */
int writeToClient(int fd, client *c, int handler_installed) {
ssize_t nwritten = 0, totwritten = 0;
clientReplyBlock *o;
@@ -1480,7 +1498,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
!(c->flags & CLIENT_SLAVE)) break;
}
__atomic_fetch_add(&g_pserver->stat_net_output_bytes, totwritten, __ATOMIC_RELAXED);
g_pserver->stat_net_output_bytes += totwritten;
if (nwritten == -1) {
if (errno == EAGAIN) {
nwritten = 0;
@@ -1488,15 +1506,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
serverLog(LL_VERBOSE,
"Error writing to client: %s", strerror(errno));
lock.unlock();
if (aeTryAcquireLock())
{
freeClient(c);
aeReleaseLock();
}
else
{
freeClientAsync(c);
}
freeClientAsync(c);
return C_ERR;
}
@@ -1515,16 +1525,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
/* Close connection after entire reply has been sent. */
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
lock.unlock();
if (aeTryAcquireLock())
{
freeClient(c);
aeReleaseLock();
}
else
{
lock.unlock();
freeClientAsync(c);
}
freeClientAsync(c);
return C_ERR;
}
}
@@ -1537,7 +1538,14 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
client *c = (client*)privdata;
serverAssert(ielFromEventLoop(el) == c->iel);
writeToClient(fd,c,1);
if (writeToClient(fd,c,1) == C_ERR)
{
AeLocker ae;
c->lock.lock();
ae.arm(c);
if (c->flags & CLIENT_CLOSE_ASAP)
freeClient(c);
}
}
void ProcessPendingAsyncWrites()
@@ -1553,6 +1561,15 @@ void ProcessPendingAsyncWrites()
std::lock_guard<decltype(c->lock)> lock(c->lock);
serverAssert(c->fPendingAsyncWrite);
if (c->flags & (CLIENT_CLOSE_ASAP | CLIENT_CLOSE_AFTER_REPLY))
{
c->bufposAsync = 0;
c->buflenAsync = 0;
zfree(c->bufAsync);
c->bufAsync = nullptr;
c->fPendingAsyncWrite = FALSE;
continue;
}
// TODO: Append to end of reply block?
@@ -1589,8 +1606,36 @@ void ProcessPendingAsyncWrites()
continue;
asyncCloseClientOnOutputBufferLimitReached(c);
if (aeCreateRemoteFileEvent(g_pserver->rgthreadvar[c->iel].el, c->fd, ae_flags, sendReplyToClient, c, FALSE) == AE_ERR)
continue; // We can retry later in the cron
if (c->flags & CLIENT_CLOSE_ASAP)
continue; // we will never write this so don't post an op
std::atomic_thread_fence(std::memory_order_seq_cst);
if (c->casyncOpsPending == 0)
{
if (FCorrectThread(c))
{
prepareClientToWrite(c, false); // queue an event
}
else
{
// We need to start the write on the client's thread
if (aePostFunction(g_pserver->rgthreadvar[c->iel].el, [c]{
// Install a write handler. Don't do the actual write here since we don't want
// to duplicate the throttling and safety mechanisms of the normal write code
std::lock_guard<decltype(c->lock)> lock(c->lock);
serverAssert(c->casyncOpsPending > 0);
c->casyncOpsPending--;
aeCreateFileEvent(g_pserver->rgthreadvar[c->iel].el, c->fd, AE_WRITABLE|AE_WRITE_THREADSAFE, sendReplyToClient, c);
}, false) == AE_ERR
)
{
// Posting the function failed
continue; // We can retry later in the cron
}
++c->casyncOpsPending; // race is handled by the client lock in the lambda
}
}
}
}
@@ -1599,56 +1644,64 @@ void ProcessPendingAsyncWrites()
* need to use a syscall in order to install the writable event handler,
* get it called, and so forth. */
int handleClientsWithPendingWrites(int iel) {
listIter li;
listNode *ln;
std::unique_lock<fastlock> lockf(g_pserver->rgthreadvar[iel].lockPendingWrite);
list *list = g_pserver->rgthreadvar[iel].clients_pending_write;
int processed = listLength(list);
auto &vec = g_pserver->rgthreadvar[iel].clients_pending_write;
int processed = (int)vec.size();
serverAssert(iel == (serverTL - g_pserver->rgthreadvar));
listRewind(list,&li);
while((ln = listNext(&li))) {
client *c = (client*)listNodeValue(ln);
std::unique_lock<decltype(c->lock)> lock(c->lock);
int ae_flags = AE_WRITABLE|AE_WRITE_THREADSAFE;
/* For the fsync=always policy, we want that a given FD is never
* served for reading and writing in the same event loop iteration,
* so that in the middle of receiving the query, and serving it
* to the client, we'll call beforeSleep() that will do the
* actual fsync of AOF to disk. AE_BARRIER ensures that. */
if (g_pserver->aof_state == AOF_ON &&
g_pserver->aof_fsync == AOF_FSYNC_ALWAYS)
{
ae_flags |= AE_BARRIER;
}
while(!vec.empty()) {
client *c = vec.back();
AssertCorrectThread(c);
c->flags &= ~CLIENT_PENDING_WRITE;
listDelNode(list,ln);
AssertCorrectThread(c);
vec.pop_back();
/* If a client is protected, don't do anything,
* that may trigger write error or recreate handler. */
if (c->flags & CLIENT_PROTECTED) continue;
std::unique_lock<decltype(c->lock)> lock(c->lock);
/* Try to write buffers to the client socket. */
if (writeToClient(c->fd,c,0) == C_ERR) {
lock.release(); // client is free'd
if (writeToClient(c->fd,c,0) == C_ERR)
{
if (c->flags & CLIENT_CLOSE_ASAP)
{
lock.release(); // still locked
AeLocker ae;
ae.arm(c);
if (!freeClient(c)) // writeToClient will only async close, but there's no need to wait
c->lock.unlock(); // if we just got put on the async close list, then we need to remove the lock
}
continue;
}
/* If after the synchronous writes above we still have data to
* output to the client, we need to install the writable handler. */
if (clientHasPendingReplies(c)) {
int ae_flags = AE_WRITABLE|AE_WRITE_THREADSAFE;
/* For the fsync=always policy, we want that a given FD is never
* served for reading and writing in the same event loop iteration,
* so that in the middle of receiving the query, and serving it
* to the client, we'll call beforeSleep() that will do the
* actual fsync of AOF to disk. AE_BARRIER ensures that. */
if (g_pserver->aof_state == AOF_ON &&
g_pserver->aof_fsync == AOF_FSYNC_ALWAYS)
{
ae_flags |= AE_BARRIER;
}
if (aeCreateFileEvent(g_pserver->rgthreadvar[c->iel].el, c->fd, ae_flags, sendReplyToClient, c) == AE_ERR)
freeClientAsync(c);
}
}
AeLocker locker;
locker.arm(nullptr);
ProcessPendingAsyncWrites();
if (listLength(serverTL->clients_pending_asyncwrite))
{
AeLocker locker;
locker.arm(nullptr);
ProcessPendingAsyncWrites();
}
return processed;
}
@@ -1951,13 +2004,48 @@ int processMultibulkBuffer(client *c) {
return C_ERR;
}
/* This function calls processCommand(), but also performs a few sub tasks
* 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.
*
* 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);
}
}
if (serverTL->current_client == NULL) deadclient = 1;
serverTL->current_client = NULL;
/* freeMemoryIfNeeded may flush slave output buffers. This may
* result into a slave, that may be the active client, to be
* freed. */
return deadclient ? C_ERR : C_OK;
}
/* This function is called every time, in the client structure 'c', there is
* more query buffer to process, because we read more data from the socket
* or because a client was blocked and later reactivated, so there could be
* pending query buffer, already representing a full command, to process. */
void processInputBuffer(client *c, int callFlags) {
AssertCorrectThread(c);
bool fFreed = false;
/* Keep processing while there is something in the input buffer */
while(c->qb_pos < sdslen(c->querybuf)) {
@@ -2001,60 +2089,47 @@ void processInputBuffer(client *c, int callFlags) {
if (c->argc == 0) {
resetClient(c);
} else {
AeLocker locker;
locker.arm(c);
serverTL->current_client = c;
/* Only reset the client when the command was executed. */
if (processCommand(c, callFlags) == 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);
/* We are finally ready to execute the command. */
if (processCommandAndResetClient(c, callFlags) == C_ERR) {
/* If the client is no longer valid, we avoid exiting this
* loop and trimming the client buffer later. So we return
* ASAP in that case. */
return;
}
/* freeMemoryIfNeeded may flush slave output buffers. This may
* result into a slave, that may be the active client, to be
* freed. */
if (serverTL->current_client == NULL) {
fFreed = true;
break;
}
serverTL->current_client = NULL;
}
}
/* Trim to pos */
if (!fFreed && c->qb_pos) {
if (c->qb_pos) {
sdsrange(c->querybuf,c->qb_pos,-1);
c->qb_pos = 0;
}
}
/* This is a wrapper for processInputBuffer that also cares about handling
* the replication forwarding to the sub-slaves, in case the client 'c'
* 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)
{
aeAcquireLock();
AeLocker ae;
ae.arm(c);
replicationFeedSlavesFromMasterStream(g_pserver->slaves,
c->pending_querybuf, applied);
aeReleaseLock();
}
sdsrange(c->pending_querybuf,applied,-1);
}
@@ -2104,16 +2179,12 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
} else {
serverLog(LL_VERBOSE, "Reading from client: %s",strerror(errno));
lock.unlock();
aelock.arm(nullptr);
freeClient(c);
freeClientAsync(c);
return;
}
} else if (nread == 0) {
serverLog(LL_VERBOSE, "Client closed connection");
lock.unlock();
aelock.arm(nullptr);
freeClient(c);
freeClientAsync(c);
return;
} else if (c->flags & CLIENT_MASTER) {
/* Append the query buffer to the pending (not applied) buffer
@@ -2133,10 +2204,8 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
bytes = sdscatrepr(bytes,c->querybuf,64);
serverLog(LL_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);
sdsfree(ci);
sdsfree(bytes);
lock.unlock();
aelock.arm(nullptr);
freeClient(c);
sdsfree(bytes);
freeClientAsync(c);
return;
}
@@ -2147,8 +2216,11 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
* corresponding part of the replication stream, will be propagated to
* the sub-slaves and to the replication backlog. */
processInputBufferAndReplicate(c);
aelock.arm(c);
ProcessPendingAsyncWrites();
if (listLength(serverTL->clients_pending_asyncwrite))
{
aelock.arm(c);
ProcessPendingAsyncWrites();
}
}
void getClientsMaxBuffers(unsigned long *longest_output_list,
@@ -2267,6 +2339,7 @@ sds getAllClientsInfoString(int type) {
listRewind(g_pserver->clients,&li);
while ((ln = listNext(&li)) != NULL) {
client = reinterpret_cast<struct client*>(listNodeValue(ln));
std::unique_lock<decltype(client->lock)> lock(client->lock);
if (type != -1 && getClientType(client) != type) continue;
o = catClientInfoString(o,client);
o = sdscatlen(o,"\n",1);
@@ -2498,7 +2571,7 @@ NULL
if (c->name)
addReplyBulk(c,c->name);
else
addReplyNull(c);
addReplyNull(c, shared.nullbulk);
} else if (!strcasecmp((const char*)ptrFromObj(c->argv[1]),"pause") && c->argc == 3) {
long long duration;
@@ -2572,7 +2645,9 @@ void helloCommand(client *c) {
if (!g_pserver->sentinel_mode) {
addReplyBulkCString(c,"role");
addReplyBulkCString(c,listLength(g_pserver->masters) ? "replica" : "master");
addReplyBulkCString(c,listLength(g_pserver->masters) ?
g_pserver->fActiveReplica ? "active-replica" : "replica"
: "master");
}
addReplyBulkCString(c,"modules");
@@ -2900,3 +2975,4 @@ int processEventsWhileBlocked(int iel) {
aeAcquireLock();
return count;
}
+34
View File
@@ -0,0 +1,34 @@
#include <cstddef> // std::size_t
#include "server.h"
#include "new.h"
#ifdef SANITIZE
void *operator new(size_t size, enum MALLOC_CLASS mclass)
{
(void)mclass;
return ::operator new(size);
}
#else
[[deprecated]]
void *operator new(size_t size)
{
return zmalloc(size, MALLOC_LOCAL);
}
void *operator new(size_t size, enum MALLOC_CLASS mclass)
{
return zmalloc(size, mclass);
}
void operator delete(void * p) noexcept
{
zfree(p);
}
void operator delete(void *p, std::size_t) noexcept
{
zfree(p);
}
#endif
+7 -18
View File
@@ -1,23 +1,12 @@
#pragma once
#include <cstddef> // std::size_t
void *operator new(size_t size, enum MALLOC_CLASS mclass);
#ifndef SANITIZE
[[deprecated]]
inline void *operator new(size_t size)
{
return zmalloc(size, MALLOC_LOCAL);
}
void *operator new(size_t size);
inline void *operator new(size_t size, enum MALLOC_CLASS mclass)
{
return zmalloc(size, mclass);
}
inline void operator delete(void * p)
{
zfree(p);
}
inline void operator delete(void *p, std::size_t)
{
zfree(p);
}
void operator delete(void * p) noexcept;
void operator delete(void *p, std::size_t) noexcept;
#endif
+9 -9
View File
@@ -43,7 +43,7 @@ robj *createObject(int type, void *ptr) {
o->type = type;
o->encoding = OBJ_ENCODING_RAW;
o->m_ptr = ptr;
o->refcount = 1;
o->refcount.store(1, std::memory_order_relaxed);
o->mvcc_tstamp = OBJ_MVCC_INVALID;
/* Set the LRU to the current lruclock (minutes resolution), or
@@ -69,7 +69,7 @@ robj *createObject(int type, void *ptr) {
*/
robj *makeObjectShared(robj *o) {
serverAssert(o->refcount == 1);
o->refcount = OBJ_SHARED_REFCOUNT;
o->refcount.store(OBJ_SHARED_REFCOUNT, std::memory_order_relaxed);
return o;
}
@@ -91,7 +91,7 @@ robj *createEmbeddedStringObject(const char *ptr, size_t len) {
o->type = OBJ_STRING;
o->encoding = OBJ_ENCODING_EMBSTR;
o->refcount = 1;
o->refcount.store(1, std::memory_order_relaxed);
o->mvcc_tstamp = OBJ_MVCC_INVALID;
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
@@ -352,11 +352,11 @@ void freeStreamObject(robj_roptr o) {
}
void incrRefCount(robj_roptr o) {
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount++;
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount.fetch_add(1, std::memory_order_acquire);
}
void decrRefCount(robj_roptr o) {
if (o->refcount == 1) {
if (o->refcount.load(std::memory_order_acquire) == 1) {
switch(o->type) {
case OBJ_STRING: freeStringObject(o); break;
case OBJ_LIST: freeListObject(o); break;
@@ -370,7 +370,7 @@ void decrRefCount(robj_roptr o) {
zfree(o.unsafe_robjcast());
} else {
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount.fetch_sub(1, std::memory_order_acquire);
}
}
@@ -624,7 +624,7 @@ int getDoubleFromObject(const robj *o, double *target) {
(size_t)(eptr-(char*)szFromObj(o)) != sdslen(szFromObj(o)) ||
(errno == ERANGE &&
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
isnan(value))
std::isnan(value))
return C_ERR;
} else if (o->encoding == OBJ_ENCODING_INT) {
value = (long)ptrFromObj(o);
@@ -666,7 +666,7 @@ int getLongDoubleFromObject(robj *o, long double *target) {
(size_t)(eptr-(char*)szFromObj(o)) != sdslen(szFromObj(o)) ||
(errno == ERANGE &&
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
isnan(value))
std::isnan(value))
return C_ERR;
} else if (o->encoding == OBJ_ENCODING_INT) {
value = (long)szFromObj(o);
@@ -1341,7 +1341,7 @@ NULL
}
}
if ((de = dictFind(c->db->pdict,ptrFromObj(c->argv[2]))) == NULL) {
addReplyNull(c);
addReplyNull(c, shared.nullbulk);
return;
}
size_t usage = objectComputeSize((robj*)dictGetVal(de),samples);
+12 -2
View File
@@ -84,7 +84,7 @@ void addReplyPubsubUnsubscribed(client *c, robj *channel) {
if (channel)
addReplyBulk(c,channel);
else
addReplyNull(c);
addReplyNull(c, shared.nullbulk);
addReplyLongLong(c,clientSubscriptionsCount(c));
}
@@ -112,7 +112,7 @@ void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
if (pattern)
addReplyBulk(c,pattern);
else
addReplyNull(c);
addReplyNull(c, shared.nullbulk);
addReplyLongLong(c,clientSubscriptionsCount(c));
}
@@ -143,6 +143,8 @@ int clientSubscriptionsCount(client *c) {
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
* 0 if the client was already subscribed to that channel. */
int pubsubSubscribeChannel(client *c, robj *channel) {
serverAssert(GlobalLocksAcquired());
serverAssert(c->lock.fOwnLock());
dictEntry *de;
list *clients = NULL;
int retval = 0;
@@ -202,6 +204,7 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
int pubsubSubscribePattern(client *c, robj *pattern) {
serverAssert(GlobalLocksAcquired());
int retval = 0;
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
@@ -244,6 +247,7 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
/* Unsubscribe from all the channels. Return the number of channels the
* client was subscribed to. */
int pubsubUnsubscribeAllChannels(client *c, int notify) {
serverAssert(GlobalLocksAcquired());
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
dictEntry *de;
int count = 0;
@@ -262,6 +266,7 @@ int pubsubUnsubscribeAllChannels(client *c, int notify) {
/* Unsubscribe from all the patterns. Return the number of patterns the
* client was subscribed from. */
int pubsubUnsubscribeAllPatterns(client *c, int notify) {
serverAssert(GlobalLocksAcquired());
listNode *ln;
listIter li;
int count = 0;
@@ -278,6 +283,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
/* Publish a message */
int pubsubPublishMessage(robj *channel, robj *message) {
serverAssert(GlobalLocksAcquired());
int receivers = 0;
dictEntry *de;
listNode *ln;
@@ -293,6 +299,8 @@ int pubsubPublishMessage(robj *channel, robj *message) {
listRewind(list,&li);
while ((ln = listNext(&li)) != NULL) {
client *c = reinterpret_cast<client*>(ln->value);
if (c->flags & CLIENT_CLOSE_ASAP) // avoid blocking if the write will be ignored
continue;
fastlock_lock(&c->lock);
addReplyPubsubMessage(c,channel,message);
fastlock_unlock(&c->lock);
@@ -311,6 +319,8 @@ int pubsubPublishMessage(robj *channel, robj *message) {
(char*)ptrFromObj(channel),
sdslen(szFromObj(channel)),0))
{
if (pat->pclient->flags & CLIENT_CLOSE_ASAP)
continue;
fastlock_lock(&pat->pclient->lock);
addReplyPubsubPatMessage(pat->pclient,
pat->pattern,channel,message);
+11 -4
View File
@@ -33,12 +33,19 @@ int rdbSaveS3(char *s3bucket, rdbSaveInfo *rsi)
else
{
close(fd[0]);
if (rdbSaveFd(fd[1], rsi) != C_OK)
FILE *fp = fdopen(fd[1], "w");
if (fp == NULL)
{
close(fd[1]);
close (fd[1]);
return C_ERR;
}
close(fd[1]);
if (rdbSaveFp(fp, rsi) != C_OK)
{
fclose(fp);
return C_ERR;
}
fclose(fp);
waitpid(pid, &status, 0);
}
@@ -59,7 +66,7 @@ int rdbLoadS3Core(int fd, rdbSaveInfo *rsi)
if ((fp = fdopen(fd, "rb")) == NULL) return C_ERR;
startLoading(fp);
rioInitWithFile(&rdb,fileno(fp));
rioInitWithFile(&rdb,fp);
retval = rdbLoadRio(&rdb,rsi,0);
fclose(fp);
stopLoading();
+7 -7
View File
@@ -543,10 +543,10 @@ int rdbSaveDoubleValue(rio *rdb, double val) {
unsigned char buf[128];
int len;
if (isnan(val)) {
if (std::isnan(val)) {
buf[0] = 253;
len = 1;
} else if (!isfinite(val)) {
} else if (!std::isfinite(val)) {
len = 1;
buf[0] = (val < 0) ? 255 : 254;
} else {
@@ -1220,12 +1220,12 @@ werr: /* Write error. */
return C_ERR;
}
int rdbSaveFd(int fd, rdbSaveInfo *rsi)
int rdbSaveFp(FILE *fp, rdbSaveInfo *rsi)
{
int error = 0;
rio rdb;
rioInitWithFile(&rdb,fd);
rioInitWithFile(&rdb,fp);
if (g_pserver->rdb_save_incremental_fsync)
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
@@ -1267,7 +1267,7 @@ int rdbSaveFile(char *filename, rdbSaveInfo *rsi) {
return C_ERR;
}
if (rdbSaveFd(fileno(fp), rsi) == C_ERR){
if (rdbSaveFp(fp, rsi) == C_ERR){
goto werr;
}
@@ -2151,7 +2151,7 @@ int rdbLoadFile(char *filename, rdbSaveInfo *rsi) {
if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
startLoading(fp);
rioInitWithFile(&rdb,fileno(fp));
rioInitWithFile(&rdb,fp);
retval = rdbLoadRio(&rdb,rsi,0);
fclose(fp);
stopLoading();
@@ -2547,7 +2547,7 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
* connects to us, the NULL repl_backlog will trigger a full
* synchronization, at the same time we will use a new replid and clear
* replid2. */
if (!listLength(g_pserver->masters) && g_pserver->repl_backlog) {
if (g_pserver->fActiveReplica || (!listLength(g_pserver->masters) && g_pserver->repl_backlog)) {
/* Note that when g_pserver->slaveseldb is -1, it means that this master
* didn't apply any write commands after a full synchronization.
* So we can let repl_stream_db be 0, this allows a restarted slave
+1 -1
View File
@@ -141,7 +141,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
void rdbRemoveTempFile(pid_t childpid);
int rdbSave(rdbSaveInfo *rsi);
int rdbSaveFile(char *filename, rdbSaveInfo *rsi);
int rdbSaveFd(int fd, rdbSaveInfo *rsi);
int rdbSaveFp(FILE *pf, rdbSaveInfo *rsi);
int rdbSaveS3(char *path, rdbSaveInfo *rsi);
int rdbLoadS3(char *path, rdbSaveInfo *rsi);
ssize_t rdbSaveObject(rio *rdb, robj_roptr o, robj *key);
+15 -2
View File
@@ -258,6 +258,19 @@ static redisConfig *getRedisConfig(const char *ip, int port,
else fprintf(stderr,"%s: %s\n",hostsocket,err);
goto fail;
}
if(config.auth){
void *authReply = NULL;
redisAppendCommand(c, "AUTH %s", config.auth);
if (REDIS_OK != redisGetReply(c, &authReply)) goto fail;
if (reply) freeReplyObject(reply);
reply = ((redisReply *) authReply);
if (reply->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "ERROR: %s\n", reply->str);
goto fail;
}
}
redisAppendCommand(c, "CONFIG GET %s", "save");
redisAppendCommand(c, "CONFIG GET %s", "appendonly");
@@ -1196,7 +1209,7 @@ static int fetchClusterSlotsConfiguration(client c) {
assert(reply->type == REDIS_REPLY_ARRAY);
for (i = 0; i < reply->elements; i++) {
redisReply *r = reply->element[i];
assert(r->type = REDIS_REPLY_ARRAY);
assert(r->type == REDIS_REPLY_ARRAY);
assert(r->elements >= 3);
int from, to, slot;
from = r->element[0]->integer;
@@ -1298,7 +1311,7 @@ int parseOptions(int argc, const char **argv) {
if (*p < '0' || *p > '9') goto invalid;
}
config.randomkeys = 1;
config.randomkeys_keyspacelen = atoi(argv[++i]);
config.randomkeys_keyspacelen = atoi(next);
if (config.randomkeys_keyspacelen < 0)
config.randomkeys_keyspacelen = 0;
} else if (!strcmp(argv[i],"-q")) {
+1 -1
View File
@@ -37,7 +37,7 @@
snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
}
static char error[1024];
static char error[1044];
static off_t epos;
int consumeNewline(char *buf) {
+1 -1
View File
@@ -186,7 +186,7 @@ int redis_check_rdb(const char *rdbfilename, FILE *fp) {
int closefile = (fp == NULL);
if (fp == NULL && (fp = fopen(rdbfilename,"r")) == NULL) return 1;
rioInitWithFile(&rdb,fileno(fp));
rioInitWithFile(&rdb,fp);
rdbstate.rio = &rdb;
rdb.update_cksum = rdbLoadProgressCallback;
if (rioRead(&rdb,buf,9) == 0) goto eoferr;
+122
View File
@@ -45,6 +45,8 @@
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <sys/types.h>
#include <pwd.h>
#include <hiredis.h>
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
@@ -6532,6 +6534,116 @@ static void intrinsicLatencyMode(void) {
}
}
/*------------------------------------------------------------------------------
* Message of the day
*--------------------------------------------------------------------------- */
#ifdef MOTD
#include <curl/curl.h>
static const char *szMotdCachePath()
{
static sds sdsMotdCachePath = NULL;
if (sdsMotdCachePath != NULL)
return sdsMotdCachePath;
struct passwd *pw = getpwuid(getuid());
if (pw == NULL)
return "";
const char *homedir = pw->pw_dir;
sdsMotdCachePath = sdsnew(homedir);
sdsMotdCachePath = sdscat(sdsMotdCachePath, "/.keydb-cli-motd");
return sdsMotdCachePath;
}
static size_t motd_write_callback(void *ptr, size_t size, size_t nmemb, sds *str)
{
*str = sdscatlen(*str, ptr, size*nmemb);
return (size*nmemb);
}
static char *fetchMOTDFromCache()
{
struct stat attrib;
if (stat(szMotdCachePath(), &attrib) != 0)
return NULL;
time_t t = attrib.st_mtim.tv_sec;
time_t now = time(NULL);
if ((now - t) < 14400)
{
// If our cache was updated no more than 4 hours ago use it instead of fetching the MOTD
FILE *pf = fopen(szMotdCachePath(), "rb");
if (pf == NULL)
return NULL;
fseek(pf, 0L, SEEK_END);
long cb = ftell(pf);
fseek(pf, 0L, SEEK_SET); // rewind
sds str = sdsnewlen(NULL, cb);
size_t cbRead = fread(str, 1, cb, pf);
fclose(pf);
if ((long)cbRead != cb)
{
sdsfree(str);
return NULL;
}
return str;
}
return NULL;
}
static void setMOTDCache(const char *sz)
{
FILE *pf = fopen(szMotdCachePath(), "wb");
size_t celem = fwrite(sz, strlen(sz), 1, pf);
(void)celem; // best effort
fclose(pf);
}
static char *fetchMOTD()
{
sds str;
CURL *curl;
CURLcode res;
/* First try and get the string from the cache */
str = fetchMOTDFromCache();
if (str != NULL)
return str;
str = sdsnew("");
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "http://api.keydb.dev/motd/motd.txt");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // follow redirects
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2); // take no more than two seconds
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, motd_write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
{
sdsfree(str);
str = NULL;
}
/* always cleanup */
curl_easy_cleanup(curl);
if (str != NULL)
setMOTDCache(str);
}
return str;
}
#else
static char *fetchMOTD()
{
return NULL;
}
#endif
/*------------------------------------------------------------------------------
* Program main()
*--------------------------------------------------------------------------- */
@@ -6700,6 +6812,15 @@ int main(int argc, char **argv) {
/* Start interactive mode when no command is provided */
if (argc == 0 && !config.eval) {
/* Show the message of the day if we are interactive */
if (config.output == OUTPUT_STANDARD) {
char *szMotd = fetchMOTD();
if (szMotd != NULL) {
printf("Message of the day:\n %s\n", szMotd);
sdsfree(szMotd);
}
}
/* Ignore SIGPIPE in interactive mode to force a reconnect */
signal(SIGPIPE, SIG_IGN);
@@ -6711,6 +6832,7 @@ int main(int argc, char **argv) {
/* Otherwise, we have some arguments to execute */
if (cliConnect(0) != REDIS_OK) exit(1);
if (config.eval) {
return evalMode(argc,argv);
} else {
+2 -1
View File
@@ -276,4 +276,5 @@ redisReply *sendScan(unsigned long long *it);
#ifdef __cplusplus
}
#endif
#endif
+99 -67
View File
@@ -229,7 +229,7 @@ void feedReplicationBacklogWithObject(robj *o) {
feedReplicationBacklog(p,len);
}
void replicationFeedSlave(client *slave, int dictid, robj **argv, int argc)
void replicationFeedSlave(client *slave, int dictid, robj **argv, int argc, bool fSendRaw)
{
char llstr[LONG_STR_SIZE];
std::unique_lock<decltype(slave->lock)> lock(slave->lock);
@@ -252,7 +252,8 @@ void replicationFeedSlave(client *slave, int dictid, robj **argv, int argc)
}
/* Add the SELECT command into the backlog. */
if (g_pserver->repl_backlog) feedReplicationBacklogWithObject(selectcmd);
/* We don't do this for advanced replication because this will be done later when it adds the whole RREPLAY command */
if (g_pserver->repl_backlog && fSendRaw) feedReplicationBacklogWithObject(selectcmd);
/* Send it to slaves */
addReply(slave,selectcmd);
@@ -302,34 +303,9 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
client *fake = createClient(-1, serverTL - g_pserver->rgthreadvar);
fake->flags |= CLIENT_FORCE_REPLY;
replicationFeedSlave(fake, dictid, argv, argc); // Note: updates the repl log, keep above the repl update code below
bool fSendRaw = !g_pserver->fActiveReplica;
replicationFeedSlave(fake, dictid, argv, argc, fSendRaw); // Note: updates the repl log, keep above the repl update code below
/* Write the command to the replication backlog if any. */
if (g_pserver->repl_backlog) {
char aux[LONG_STR_SIZE+3];
/* Add the multi bulk reply length. */
aux[0] = '*';
len = ll2string(aux+1,sizeof(aux)-1,argc);
aux[len+1] = '\r';
aux[len+2] = '\n';
feedReplicationBacklog(aux,len+3);
for (j = 0; j < argc; j++) {
long objlen = stringObjectLen(argv[j]);
/* We need to feed the buffer with the object as a bulk reply
* not just as a plain string, so create the $..CRLF payload len
* and add the final CRLF */
aux[0] = '$';
len = ll2string(aux+1,sizeof(aux)-1,objlen);
aux[len+1] = '\r';
aux[len+2] = '\n';
feedReplicationBacklog(aux,len+3);
feedReplicationBacklogWithObject(argv[j]);
feedReplicationBacklog(aux+len+1,2);
}
}
long long cchbuf = fake->bufpos;
listRewind(fake->reply, &liReply);
@@ -339,8 +315,6 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
cchbuf += reply->used;
}
bool fSendRaw = !g_pserver->fActiveReplica;
serverAssert(argc > 0);
serverAssert(cchbuf > 0);
@@ -348,7 +322,52 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
uuid_unparse(cserver.uuid, uuid);
char proto[1024];
int cchProto = snprintf(proto, sizeof(proto), "*3\r\n$7\r\nRREPLAY\r\n$%d\r\n%s\r\n$%lld\r\n", (int)strlen(uuid), uuid, cchbuf);
cchProto = std::min((int)sizeof(proto), cchProto);
cchProto = std::min((int)sizeof(proto), cchProto);
long long master_repl_offset_start = g_pserver->master_repl_offset;
/* Write the command to the replication backlog if any. */
if (g_pserver->repl_backlog)
{
if (fSendRaw)
{
char aux[LONG_STR_SIZE+3];
/* Add the multi bulk reply length. */
aux[0] = '*';
len = ll2string(aux+1,sizeof(aux)-1,argc);
aux[len+1] = '\r';
aux[len+2] = '\n';
feedReplicationBacklog(aux,len+3);
for (j = 0; j < argc; j++) {
long objlen = stringObjectLen(argv[j]);
/* We need to feed the buffer with the object as a bulk reply
* not just as a plain string, so create the $..CRLF payload len
* and add the final CRLF */
aux[0] = '$';
len = ll2string(aux+1,sizeof(aux)-1,objlen);
aux[len+1] = '\r';
aux[len+2] = '\n';
feedReplicationBacklog(aux,len+3);
feedReplicationBacklogWithObject(argv[j]);
feedReplicationBacklog(aux+len+1,2);
}
}
else
{
feedReplicationBacklog(proto, cchProto);
feedReplicationBacklog(fake->buf, fake->bufpos);
listRewind(fake->reply, &liReply);
while ((lnReply = listNext(&liReply)))
{
clientReplyBlock* reply = (clientReplyBlock*)listNodeValue(lnReply);
feedReplicationBacklog(reply->buf(), reply->used);
}
const char *crlf = "\r\n";
feedReplicationBacklog(crlf, 2);
}
}
/* Write the command to every slave. */
listRewind(slaves,&li);
@@ -357,8 +376,12 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
/* Don't feed slaves that are still waiting for BGSAVE to start */
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
if (serverTL->current_client && FSameHost(serverTL->current_client, slave)) continue;
std::unique_lock<decltype(slave->lock)> lock(slave->lock);
if (serverTL->current_client && FSameHost(serverTL->current_client, slave))
{
slave->reploff_skipped += g_pserver->master_repl_offset - master_repl_offset_start;
continue;
}
if (!fSendRaw)
addReplyProtoAsync(slave, proto, cchProto);
@@ -1272,6 +1295,7 @@ void replicationCreateMasterClient(redisMaster *mi, int fd, int dbid) {
mi->master->flags |= CLIENT_MASTER;
mi->master->authenticated = 1;
mi->master->reploff = mi->master_initial_offset;
mi->master->reploff_skipped = 0;
mi->master->read_reploff = mi->master->reploff;
mi->master->puser = NULL; /* This client can do everything. */
@@ -2174,8 +2198,10 @@ int connectWithMaster(redisMaster *mi) {
void undoConnectWithMaster(redisMaster *mi) {
int fd = mi->repl_transfer_s;
aeDeleteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,fd,AE_READABLE|AE_WRITABLE);
close(fd);
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fd]{
aeDeleteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,fd,AE_READABLE|AE_WRITABLE);
close(fd);
});
mi->repl_transfer_s = -1;
}
@@ -2222,7 +2248,8 @@ struct redisMaster *replicationAddMaster(char *ip, int port) {
while ((ln = listNext(&li)))
{
redisMaster *miCheck = (redisMaster*)listNodeValue(ln);
serverAssert(strcasecmp(miCheck->masterhost, ip) || miCheck->masterport != port);
if (strcasecmp(miCheck->masterhost, ip)==0 && miCheck->masterport == port)
return nullptr;
}
// Pre-req satisfied, lets continue
@@ -2317,12 +2344,15 @@ void replicationUnsetMaster(redisMaster *mi) {
/* This function is called when the slave lose the connection with the
* master into an unexpected way. */
void replicationHandleMasterDisconnection(redisMaster *mi) {
mi->master = NULL;
mi->repl_state = REPL_STATE_CONNECT;
mi->repl_down_since = g_pserver->unixtime;
/* We lost connection with our master, don't disconnect slaves yet,
* maybe we'll be able to PSYNC with our master later. We'll disconnect
* the slaves only if we'll have to do a full resync with our master. */
if (mi != nullptr)
{
mi->master = NULL;
mi->repl_state = REPL_STATE_CONNECT;
mi->repl_down_since = g_pserver->unixtime;
/* We lost connection with our master, don't disconnect slaves yet,
* maybe we'll be able to PSYNC with our master later. We'll disconnect
* the slaves only if we'll have to do a full resync with our master. */
}
}
void replicaofCommand(client *c) {
@@ -2362,26 +2392,18 @@ void replicaofCommand(client *c) {
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK))
return;
/* Check if we are already attached to the specified slave */
listIter li;
listNode *ln;
listRewind(g_pserver->masters, &li);
while ((ln = listNext(&li)))
{
redisMaster *mi = (redisMaster*)listNodeValue(ln);
if (!strcasecmp(mi->masterhost,(const char*)ptrFromObj(c->argv[1]))
&& mi->masterport == port) {
serverLog(LL_NOTICE,"REPLICAOF would result into synchronization "
"with the master we are already connected "
"with. No operation performed.");
addReplySds(c,sdsnew("+OK Already connected to specified "
"master\r\n"));
return;
}
}
/* There was no previous master or the user specified a different one,
* we can continue. */
redisMaster *miNew = replicationAddMaster((char*)ptrFromObj(c->argv[1]), port);
if (miNew == nullptr)
{
// We have a duplicate
serverLog(LL_NOTICE,"REPLICAOF would result into synchronization "
"with the master we are already connected "
"with. No operation performed.");
addReplySds(c,sdsnew("+OK Already connected to specified "
"master\r\n"));
return;
}
sds client = catClientInfoString(sdsempty(),c);
serverLog(LL_NOTICE,"REPLICAOF %s:%d enabled (user request from '%s')",
miNew->masterhost, miNew->masterport, client);
@@ -2418,7 +2440,7 @@ void roleCommand(client *c) {
addReplyArrayLen(c,3);
addReplyBulkCString(c,slaveip);
addReplyBulkLongLong(c,slave->slave_listening_port);
addReplyBulkLongLong(c,slave->repl_ack_off);
addReplyBulkLongLong(c,slave->repl_ack_off+slave->reploff_skipped);
slaves++;
}
setDeferredArrayLen(c,mbcount,slaves);
@@ -2432,7 +2454,10 @@ void roleCommand(client *c) {
redisMaster *mi = (redisMaster*)listNodeValue(ln);
const char *slavestate = NULL;
addReplyArrayLen(c,5);
addReplyBulkCBuffer(c,"slave",5);
if (g_pserver->fActiveReplica)
addReplyBulkCBuffer(c,"active-replica",14);
else
addReplyBulkCBuffer(c,"slave",5);
addReplyBulkCString(c,mi->masterhost);
addReplyLongLong(c,mi->masterport);
if (slaveIsInHandshakeState(mi)) {
@@ -2763,7 +2788,7 @@ int replicationCountAcksByOffset(long long offset) {
client *slave = (client*)ln->value;
if (slave->replstate != SLAVE_STATE_ONLINE) continue;
if (slave->repl_ack_off >= offset) count++;
if ((slave->repl_ack_off + slave->reploff_skipped) >= offset) count++;
}
return count;
}
@@ -2775,7 +2800,7 @@ void waitCommand(client *c) {
long numreplicas, ackreplicas;
long long offset = c->woff;
if (listLength(g_pserver->masters)) {
if (listLength(g_pserver->masters) && !g_pserver->fActiveReplica) {
addReplyError(c,"WAIT cannot be used with replica instances. Please also note that since Redis 4.0 if a replica is configured to be writable (which is not the default) writes to replicas are just local and are not propagated.");
return;
}
@@ -3235,16 +3260,23 @@ void replicaReplayCommand(client *c)
return;
// OK We've recieved a command lets execute
client *current_clientSave = serverTL->current_client;
client *cFake = createClient(-1, c->iel);
cFake->lock.lock();
cFake->authenticated = c->authenticated;
cFake->puser = c->puser;
cFake->querybuf = sdscat(cFake->querybuf,(sds)ptrFromObj(c->argv[2]));
cFake->querybuf = sdscatsds(cFake->querybuf,(sds)ptrFromObj(c->argv[2]));
selectDb(cFake, c->db->id);
auto ccmdPrev = serverTL->commandsExecuted;
processInputBuffer(cFake, (CMD_CALL_FULL & (~CMD_CALL_PROPAGATE)));
bool fExec = ccmdPrev != serverTL->commandsExecuted;
cFake->lock.unlock();
addReply(c, shared.ok);
if (fExec)
addReply(c, shared.ok);
else
addReplyError(c, "command did not execute");
freeClient(cFake);
serverTL->current_client = current_clientSave;
// call() will not propogate this for us, so we do so here
if (!s_pstate->FCancelled() && s_pstate->FFirst())
+8 -7
View File
@@ -109,13 +109,14 @@ void rioInitWithBuffer(rio *r, sds s) {
static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
size_t retval;
retval = write(r->io.file.fd,buf,len);
retval = fwrite(buf,len,1,r->io.file.fp);
r->io.file.buffered += len;
if (r->io.file.autosync &&
r->io.file.buffered >= r->io.file.autosync)
{
redis_fsync(r->io.file.fd);
fflush(r->io.file.fp);
redis_fsync(fileno(r->io.file.fp));
r->io.file.buffered = 0;
}
return retval;
@@ -123,18 +124,18 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
/* Returns 1 or 0 for success/failure. */
static size_t rioFileRead(rio *r, void *buf, size_t len) {
return read(r->io.file.fd,buf,len);
return fread(buf,len,1,r->io.file.fp);
}
/* Returns read/write position in file. */
static off_t rioFileTell(rio *r) {
return lseek(r->io.file.fd, 0, SEEK_CUR);
return ftello(r->io.file.fp);
}
/* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */
static int rioFileFlush(rio *r) {
return (fsync(r->io.file.fd) == 0) ? 1 : 0;
return (fflush(r->io.file.fp) == 0) ? 1 : 0;
}
static const rio rioFileIO = {
@@ -149,9 +150,9 @@ static const rio rioFileIO = {
{ { NULL, 0 } } /* union for io-specific vars */
};
void rioInitWithFile(rio *r, int fd) {
void rioInitWithFile(rio *r, FILE *fp) {
*r = rioFileIO;
r->io.file.fd = fd;
r->io.file.fp = fp;
r->io.file.buffered = 0;
r->io.file.autosync = 0;
}
+2 -2
View File
@@ -73,7 +73,7 @@ struct _rio {
} buffer;
/* Stdio file pointer target. */
struct {
int fd;
FILE *fp;
off_t buffered; /* Bytes written since last fsync. */
off_t autosync; /* fsync after 'autosync' bytes written. */
} file;
@@ -128,7 +128,7 @@ static inline int rioFlush(rio *r) {
return r->flush(r);
}
void rioInitWithFile(rio *r, int fd);
void rioInitWithFile(rio *r, FILE *fp);
void rioInitWithBuffer(rio *r, sds s);
void rioInitWithFdset(rio *r, int *fds, int numfds);
+8 -14
View File
@@ -371,7 +371,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
int acl_retval = 0;
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
struct redisCommand *cmd;
client *c = g_pserver->lua_client;
client *c = serverTL->lua_client;
sds reply;
// Ensure our client is on the right thread
@@ -966,8 +966,11 @@ void scriptingInit(int setup) {
lua_State *lua = lua_open();
if (setup) {
g_pserver->lua_client = NULL;
g_pserver->lua_caller = NULL;
for (int iel = 0; iel < cserver.cthreads; ++iel)
{
g_pserver->rgthreadvar[iel].lua_client = createClient(-1, iel);
g_pserver->rgthreadvar[iel].lua_client->flags |= CLIENT_LUA;
}
g_pserver->lua_timedout = 0;
ldbInit();
}
@@ -1117,15 +1120,6 @@ void scriptingInit(int setup) {
lua_pcall(lua,0,0,0);
}
/* Create the (non connected) client that we use to execute Redis commands
* inside the Lua interpreter.
* Note: there is no need to create it again when this function is called
* by scriptingReset(). */
if (g_pserver->lua_client == NULL) {
g_pserver->lua_client = createClient(-1, IDX_EVENT_LOOP_MAIN);
g_pserver->lua_client->flags |= CLIENT_LUA;
}
/* Lua beginners often don't use "local", this is likely to introduce
* subtle bugs in their code. To prevent problems we protect accesses
* to global variables. */
@@ -1272,7 +1266,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
* so that we can replicate / write in the AOF all the
* EVALSHA commands as EVAL using the original script. */
int retval = dictAdd(g_pserver->lua_scripts,sha,body);
serverAssertWithInfo(c ? c : g_pserver->lua_client,NULL,retval == DICT_OK);
serverAssertWithInfo(c ? c : serverTL->lua_client,NULL,retval == DICT_OK);
g_pserver->lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body);
incrRefCount(body);
return sha;
@@ -1393,7 +1387,7 @@ void evalGenericCommand(client *c, int evalsha) {
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
/* Select the right DB in the context of the Lua client */
selectDb(g_pserver->lua_client,c->db->id);
selectDb(serverTL->lua_client,c->db->id);
/* Set a hook in order to be able to stop the script execution if it
* is running for too much time.
+192 -133
View File
@@ -59,6 +59,9 @@
#include <sys/socket.h>
#include <algorithm>
#include <uuid/uuid.h>
#include "aelocker.h"
int g_fTestMode = false;
/* Our shared "common" objects */
@@ -1020,7 +1023,7 @@ extern "C" void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst);
* serverLog() is to prefer. */
void serverLogRaw(int level, const char *msg) {
const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING };
const char *c = ".-*#";
const char *c = ".-*# ";
FILE *fp;
char buf[64];
int rawmode = (level & LL_RAW);
@@ -1745,16 +1748,18 @@ void databasesCron(void) {
* every object access, and accuracy is not needed. To access a global var is
* a lot faster than calling time(NULL) */
void updateCachedTime(void) {
time_t unixtime = time(NULL);
atomicSet(g_pserver->unixtime,unixtime);
g_pserver->mstime = mstime();
g_pserver->unixtime = time(NULL);
long long ms = mstime();
__atomic_store(&g_pserver->mstime, &ms, __ATOMIC_RELAXED);
/* To get information about daylight saving time, we need to call localtime_r
* and cache the result. However calling localtime_r in this context is safe
* since we will never fork() while here, in the main thread. The logging
* function will call a thread safe version of localtime that has no locks. */
/* To get information about daylight saving time, we need to call
* localtime_r and cache the result. However calling localtime_r in this
* context is safe since we will never fork() while here, in the main
* thread. The logging function will call a thread safe version of
* localtime that has no locks. */
struct tm tm;
localtime_r(&g_pserver->unixtime,&tm);
time_t ut = g_pserver->unixtime;
localtime_r(&ut,&tm);
g_pserver->daylight_active = tm.tm_isdst;
}
@@ -1783,6 +1788,15 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
UNUSED(id);
UNUSED(clientData);
/* If another threads unblocked one of our clients, and this thread has been idle
then beforeSleep won't have a chance to process the unblocking. So we also
process them here in the cron job to ensure they don't starve.
*/
if (listLength(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].unblocked_clients))
{
processUnblockedClients(IDX_EVENT_LOOP_MAIN);
}
ProcessPendingAsyncWrites(); // This is really a bug, but for now catch any laggards that didn't clean up
/* Software watchdog: deliver the SIGALRM that will reach the signal
@@ -1826,8 +1840,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
*
* Note that you can change the resolution altering the
* LRU_CLOCK_RESOLUTION define. */
unsigned long lruclock = getLRUClock();
atomicSet(g_pserver->lruclock,lruclock);
g_pserver->lruclock = getLRUClock();
/* Record the max memory used since the server was started. */
if (zmalloc_used_memory() > g_pserver->stat_peak_memory)
@@ -2000,9 +2013,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
flushAppendOnlyFile(0);
}
/* Close clients that need to be closed asynchronous */
freeClientsInAsyncFreeQueue(IDX_EVENT_LOOP_MAIN);
/* Clear the paused clients flag if needed. */
clientsArePaused(); /* Don't check return value, just use the side effect.*/
@@ -2053,14 +2063,19 @@ int serverCronLite(struct aeEventLoop *eventLoop, long long id, void *clientData
int iel = ielFromEventLoop(eventLoop);
serverAssert(iel != IDX_EVENT_LOOP_MAIN);
/* If another threads unblocked one of our clients, and this thread has been idle
then beforeSleep won't have a chance to process the unblocking. So we also
process them here in the cron job to ensure they don't starve.
*/
if (listLength(g_pserver->rgthreadvar[iel].unblocked_clients))
{
processUnblockedClients(iel);
}
aeAcquireLock();
ProcessPendingAsyncWrites(); // A bug but leave for now, events should clean up after themselves
clientsCron(iel);
freeClientsInAsyncFreeQueue(iel);
aeReleaseLock();
return 1000/g_pserver->hz;
}
@@ -2119,6 +2134,9 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
handleClientsWithPendingWrites(IDX_EVENT_LOOP_MAIN);
aeAcquireLock();
/* Close clients that need to be closed asynchronous */
freeClientsInAsyncFreeQueue(IDX_EVENT_LOOP_MAIN);
/* Before we are going to sleep, let the threads access the dataset by
* releasing the GIL. Redis main thread will not touch anything at this
* time. */
@@ -2143,6 +2161,11 @@ void beforeSleepLite(struct aeEventLoop *eventLoop)
/* Handle writes with pending output buffers. */
handleClientsWithPendingWrites(iel);
aeAcquireLock();
/* Close clients that need to be closed asynchronous */
freeClientsInAsyncFreeQueue(iel);
aeReleaseLock();
/* Before we are going to sleep, let the threads access the dataset by
* releasing the GIL. Redis main thread will not touch anything at this
* time. */
@@ -2162,97 +2185,99 @@ void afterSleep(struct aeEventLoop *eventLoop) {
void createSharedObjects(void) {
int j;
shared.crlf = createObject(OBJ_STRING,sdsnew("\r\n"));
shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"));
shared.err = createObject(OBJ_STRING,sdsnew("-ERR\r\n"));
shared.emptybulk = createObject(OBJ_STRING,sdsnew("$0\r\n\r\n"));
shared.czero = createObject(OBJ_STRING,sdsnew(":0\r\n"));
shared.cone = createObject(OBJ_STRING,sdsnew(":1\r\n"));
shared.emptyarray = createObject(OBJ_STRING,sdsnew("*0\r\n"));
shared.pong = createObject(OBJ_STRING,sdsnew("+PONG\r\n"));
shared.queued = createObject(OBJ_STRING,sdsnew("+QUEUED\r\n"));
shared.emptyscan = createObject(OBJ_STRING,sdsnew("*2\r\n$1\r\n0\r\n*0\r\n"));
shared.wrongtypeerr = createObject(OBJ_STRING,sdsnew(
"-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"));
shared.nokeyerr = createObject(OBJ_STRING,sdsnew(
"-ERR no such key\r\n"));
shared.syntaxerr = createObject(OBJ_STRING,sdsnew(
"-ERR syntax error\r\n"));
shared.sameobjecterr = createObject(OBJ_STRING,sdsnew(
"-ERR source and destination objects are the same\r\n"));
shared.outofrangeerr = createObject(OBJ_STRING,sdsnew(
"-ERR index out of range\r\n"));
shared.noscripterr = createObject(OBJ_STRING,sdsnew(
"-NOSCRIPT No matching script. Please use EVAL.\r\n"));
shared.loadingerr = createObject(OBJ_STRING,sdsnew(
"-LOADING Redis is loading the dataset in memory\r\n"));
shared.slowscripterr = createObject(OBJ_STRING,sdsnew(
"-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n"));
shared.masterdownerr = createObject(OBJ_STRING,sdsnew(
"-MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.\r\n"));
shared.bgsaveerr = createObject(OBJ_STRING,sdsnew(
"-MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.\r\n"));
shared.roslaveerr = createObject(OBJ_STRING,sdsnew(
"-READONLY You can't write against a read only replica.\r\n"));
shared.noautherr = createObject(OBJ_STRING,sdsnew(
"-NOAUTH Authentication required.\r\n"));
shared.oomerr = createObject(OBJ_STRING,sdsnew(
"-OOM command not allowed when used memory > 'maxmemory'.\r\n"));
shared.execaborterr = createObject(OBJ_STRING,sdsnew(
"-EXECABORT Transaction discarded because of previous errors.\r\n"));
shared.noreplicaserr = createObject(OBJ_STRING,sdsnew(
"-NOREPLICAS Not enough good replicas to write.\r\n"));
shared.busykeyerr = createObject(OBJ_STRING,sdsnew(
"-BUSYKEY Target key name already exists.\r\n"));
shared.space = createObject(OBJ_STRING,sdsnew(" "));
shared.colon = createObject(OBJ_STRING,sdsnew(":"));
shared.plus = createObject(OBJ_STRING,sdsnew("+"));
shared.crlf = makeObjectShared(createObject(OBJ_STRING,sdsnew("\r\n")));
shared.ok = makeObjectShared(createObject(OBJ_STRING,sdsnew("+OK\r\n")));
shared.err = makeObjectShared(createObject(OBJ_STRING,sdsnew("-ERR\r\n")));
shared.emptybulk = makeObjectShared(createObject(OBJ_STRING,sdsnew("$0\r\n\r\n")));
shared.emptymultibulk = makeObjectShared(createObject(OBJ_STRING,sdsnew("*0\r\n")));
shared.nullbulk = makeObjectShared(createObject(OBJ_STRING,sdsnew("$0\r\n\r\n")));
shared.czero = makeObjectShared(createObject(OBJ_STRING,sdsnew(":0\r\n")));
shared.cone = makeObjectShared(createObject(OBJ_STRING,sdsnew(":1\r\n")));
shared.emptyarray = makeObjectShared(createObject(OBJ_STRING,sdsnew("*0\r\n")));
shared.pong = makeObjectShared(createObject(OBJ_STRING,sdsnew("+PONG\r\n")));
shared.queued = makeObjectShared(createObject(OBJ_STRING,sdsnew("+QUEUED\r\n")));
shared.emptyscan = makeObjectShared(createObject(OBJ_STRING,sdsnew("*2\r\n$1\r\n0\r\n*0\r\n")));
shared.wrongtypeerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-WRONGTYPE Operation against a key holding the wrong kind of value\r\n")));
shared.nokeyerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-ERR no such key\r\n")));
shared.syntaxerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-ERR syntax error\r\n")));
shared.sameobjecterr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-ERR source and destination objects are the same\r\n")));
shared.outofrangeerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-ERR index out of range\r\n")));
shared.noscripterr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-NOSCRIPT No matching script. Please use EVAL.\r\n")));
shared.loadingerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-LOADING Redis is loading the dataset in memory\r\n")));
shared.slowscripterr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n")));
shared.masterdownerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.\r\n")));
shared.bgsaveerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.\r\n")));
shared.roslaveerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-READONLY You can't write against a read only replica.\r\n")));
shared.noautherr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-NOAUTH Authentication required.\r\n")));
shared.oomerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-OOM command not allowed when used memory > 'maxmemory'.\r\n")));
shared.execaborterr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-EXECABORT Transaction discarded because of previous errors.\r\n")));
shared.noreplicaserr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-NOREPLICAS Not enough good replicas to write.\r\n")));
shared.busykeyerr = makeObjectShared(createObject(OBJ_STRING,sdsnew(
"-BUSYKEY Target key name already exists.\r\n")));
shared.space = makeObjectShared(createObject(OBJ_STRING,sdsnew(" ")));
shared.colon = makeObjectShared(createObject(OBJ_STRING,sdsnew(":")));
shared.plus = makeObjectShared(createObject(OBJ_STRING,sdsnew("+")));
/* The shared NULL depends on the protocol version. */
shared.null[0] = NULL;
shared.null[1] = NULL;
shared.null[2] = createObject(OBJ_STRING,sdsnew("$-1\r\n"));
shared.null[3] = createObject(OBJ_STRING,sdsnew("_\r\n"));
shared.null[2] = makeObjectShared(createObject(OBJ_STRING,sdsnew("$-1\r\n")));
shared.null[3] = makeObjectShared(createObject(OBJ_STRING,sdsnew("_\r\n")));
shared.nullarray[0] = NULL;
shared.nullarray[1] = NULL;
shared.nullarray[2] = createObject(OBJ_STRING,sdsnew("*-1\r\n"));
shared.nullarray[3] = createObject(OBJ_STRING,sdsnew("_\r\n"));
shared.nullarray[2] = makeObjectShared(createObject(OBJ_STRING,sdsnew("*-1\r\n")));
shared.nullarray[3] = makeObjectShared(createObject(OBJ_STRING,sdsnew("_\r\n")));
for (j = 0; j < PROTO_SHARED_SELECT_CMDS; j++) {
char dictid_str[64];
int dictid_len;
dictid_len = ll2string(dictid_str,sizeof(dictid_str),j);
shared.select[j] = createObject(OBJ_STRING,
shared.select[j] = makeObjectShared(createObject(OBJ_STRING,
sdscatprintf(sdsempty(),
"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
dictid_len, dictid_str));
dictid_len, dictid_str)));
}
shared.messagebulk = createStringObject("$7\r\nmessage\r\n",13);
shared.pmessagebulk = createStringObject("$8\r\npmessage\r\n",14);
shared.subscribebulk = createStringObject("$9\r\nsubscribe\r\n",15);
shared.unsubscribebulk = createStringObject("$11\r\nunsubscribe\r\n",18);
shared.psubscribebulk = createStringObject("$10\r\npsubscribe\r\n",17);
shared.punsubscribebulk = createStringObject("$12\r\npunsubscribe\r\n",19);
shared.del = createStringObject("DEL",3);
shared.unlink = createStringObject("UNLINK",6);
shared.rpop = createStringObject("RPOP",4);
shared.lpop = createStringObject("LPOP",4);
shared.lpush = createStringObject("LPUSH",5);
shared.rpoplpush = createStringObject("RPOPLPUSH",9);
shared.zpopmin = createStringObject("ZPOPMIN",7);
shared.zpopmax = createStringObject("ZPOPMAX",7);
shared.messagebulk = makeObjectShared(createStringObject("$7\r\nmessage\r\n",13));
shared.pmessagebulk = makeObjectShared(createStringObject("$8\r\npmessage\r\n",14));
shared.subscribebulk = makeObjectShared(createStringObject("$9\r\nsubscribe\r\n",15));
shared.unsubscribebulk = makeObjectShared(createStringObject("$11\r\nunsubscribe\r\n",18));
shared.psubscribebulk = makeObjectShared(createStringObject("$10\r\npsubscribe\r\n",17));
shared.punsubscribebulk = makeObjectShared(createStringObject("$12\r\npunsubscribe\r\n",19));
shared.del = makeObjectShared(createStringObject("DEL",3));
shared.unlink = makeObjectShared(createStringObject("UNLINK",6));
shared.rpop = makeObjectShared(createStringObject("RPOP",4));
shared.lpop = makeObjectShared(createStringObject("LPOP",4));
shared.lpush = makeObjectShared(createStringObject("LPUSH",5));
shared.rpoplpush = makeObjectShared(createStringObject("RPOPLPUSH",9));
shared.zpopmin = makeObjectShared(createStringObject("ZPOPMIN",7));
shared.zpopmax = makeObjectShared(createStringObject("ZPOPMAX",7));
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
shared.integers[j] =
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
shared.integers[j]->encoding = OBJ_ENCODING_INT;
}
for (j = 0; j < OBJ_SHARED_BULKHDR_LEN; j++) {
shared.mbulkhdr[j] = createObject(OBJ_STRING,
sdscatprintf(sdsempty(),"*%d\r\n",j));
shared.bulkhdr[j] = createObject(OBJ_STRING,
sdscatprintf(sdsempty(),"$%d\r\n",j));
shared.mbulkhdr[j] = makeObjectShared(createObject(OBJ_STRING,
sdscatprintf(sdsempty(),"*%d\r\n",j)));
shared.bulkhdr[j] = makeObjectShared(createObject(OBJ_STRING,
sdscatprintf(sdsempty(),"$%d\r\n",j)));
}
/* The following two shared objects, minstring and maxstrings, are not
* actually used for their value but as a special object meaning
@@ -2287,10 +2312,6 @@ void initMasterInfo(redisMaster *master)
void initServerConfig(void) {
int j;
serverAssert(pthread_mutex_init(&g_pserver->next_client_id_mutex,NULL) == 0);
serverAssert(pthread_mutex_init(&g_pserver->lruclock_mutex,NULL) == 0);
serverAssert(pthread_mutex_init(&g_pserver->unixtime_mutex,NULL) == 0);
updateCachedTime();
getRandomHexChars(g_pserver->runid,CONFIG_RUN_ID_SIZE);
g_pserver->runid[CONFIG_RUN_ID_SIZE] = '\0';
@@ -2405,8 +2426,7 @@ void initServerConfig(void) {
g_pserver->lua_time_limit = LUA_SCRIPT_TIME_LIMIT;
g_pserver->fActiveReplica = CONFIG_DEFAULT_ACTIVE_REPLICA;
unsigned int lruclock = getLRUClock();
atomicSet(g_pserver->lruclock,lruclock);
g_pserver->lruclock = getLRUClock();
resetServerSaveParams();
appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */
@@ -2496,20 +2516,6 @@ void initServerConfig(void) {
/* Multithreading */
cserver.cthreads = CONFIG_DEFAULT_THREADS;
cserver.fThreadAffinity = CONFIG_DEFAULT_THREAD_AFFINITY;
g_pserver->db = (redisDb*)zmalloc(sizeof(redisDb)*cserver.dbnum, MALLOC_LOCAL);
/* Create the Redis databases, and initialize other internal state. */
for (int j = 0; j < cserver.dbnum; j++) {
g_pserver->db[j].pdict = dictCreate(&dbDictType,NULL);
g_pserver->db[j].expires = dictCreate(&keyptrDictType,NULL);
g_pserver->db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
g_pserver->db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
g_pserver->db[j].watched_keys = dictCreate(&keylistDictType,NULL);
g_pserver->db[j].id = j;
g_pserver->db[j].avg_ttl = 0;
g_pserver->db[j].defrag_later = listCreate();
}
}
extern char **environ;
@@ -2707,7 +2713,7 @@ int listenToPort(int port, int *fds, int *count, int fReusePort) {
int unsupported = 0;
/* Bind * for both IPv6 and IPv4, we enter here only if
* g_pserver->bindaddr_count == 0. */
fds[*count] = anetTcp6Server(g_pserver->neterr,port,NULL,
fds[*count] = anetTcp6Server(serverTL->neterr,port,NULL,
g_pserver->tcp_backlog, fReusePort);
if (fds[*count] != ANET_ERR) {
anetNonBlock(NULL,fds[*count]);
@@ -2719,7 +2725,7 @@ int listenToPort(int port, int *fds, int *count, int fReusePort) {
if (*count == 1 || unsupported) {
/* Bind the IPv4 address as well. */
fds[*count] = anetTcpServer(g_pserver->neterr,port,NULL,
fds[*count] = anetTcpServer(serverTL->neterr,port,NULL,
g_pserver->tcp_backlog, fReusePort);
if (fds[*count] != ANET_ERR) {
anetNonBlock(NULL,fds[*count]);
@@ -2735,18 +2741,18 @@ int listenToPort(int port, int *fds, int *count, int fReusePort) {
if (*count + unsupported == 2) break;
} else if (strchr(g_pserver->bindaddr[j],':')) {
/* Bind IPv6 address. */
fds[*count] = anetTcp6Server(g_pserver->neterr,port,g_pserver->bindaddr[j],
fds[*count] = anetTcp6Server(serverTL->neterr,port,g_pserver->bindaddr[j],
g_pserver->tcp_backlog, fReusePort);
} else {
/* Bind IPv4 address. */
fds[*count] = anetTcpServer(g_pserver->neterr,port,g_pserver->bindaddr[j],
fds[*count] = anetTcpServer(serverTL->neterr,port,g_pserver->bindaddr[j],
g_pserver->tcp_backlog, fReusePort);
}
if (fds[*count] == ANET_ERR) {
serverLog(LL_WARNING,
"Could not create server TCP listening socket %s:%d: %s",
g_pserver->bindaddr[j] ? g_pserver->bindaddr[j] : "*",
port, g_pserver->neterr);
port, serverTL->neterr);
if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
errno == EAFNOSUPPORT || errno == EADDRNOTAVAIL)
@@ -2832,10 +2838,10 @@ static void initNetworking(int fReusePort)
/* Open the listening Unix domain socket. */
if (g_pserver->unixsocket != NULL) {
unlink(g_pserver->unixsocket); /* don't care if this fails */
g_pserver->sofd = anetUnixServer(g_pserver->neterr,g_pserver->unixsocket,
g_pserver->sofd = anetUnixServer(serverTL->neterr,g_pserver->unixsocket,
g_pserver->unixsocketperm, g_pserver->tcp_backlog);
if (g_pserver->sofd == ANET_ERR) {
serverLog(LL_WARNING, "Opening Unix socket: %s", g_pserver->neterr);
serverLog(LL_WARNING, "Opening Unix socket: %s", serverTL->neterr);
exit(1);
}
anetNonBlock(NULL,g_pserver->sofd);
@@ -2853,7 +2859,6 @@ static void initNetworking(int fReusePort)
static void initServerThread(struct redisServerThreadVars *pvar, int fMain)
{
pvar->clients_pending_write = listCreate();
pvar->unblocked_clients = listCreate();
pvar->clients_pending_asyncwrite = listCreate();
pvar->ipfd_count = 0;
@@ -2916,6 +2921,32 @@ void initServer(void) {
fastlock_init(&g_pserver->flock);
g_pserver->db = (redisDb*)zmalloc(sizeof(redisDb)*cserver.dbnum, MALLOC_LOCAL);
/* Create the Redis databases, and initialize other internal state. */
for (int j = 0; j < cserver.dbnum; j++) {
g_pserver->db[j].pdict = dictCreate(&dbDictType,NULL);
g_pserver->db[j].expires = dictCreate(&keyptrDictType,NULL);
g_pserver->db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
g_pserver->db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
g_pserver->db[j].watched_keys = dictCreate(&keylistDictType,NULL);
g_pserver->db[j].id = j;
g_pserver->db[j].avg_ttl = 0;
g_pserver->db[j].defrag_later = listCreate();
}
/* Fixup Master Client Database */
listIter li;
listNode *ln;
listRewind(g_pserver->masters, &li);
while ((ln = listNext(&li)))
{
redisMaster *mi = (redisMaster*)listNodeValue(ln);
serverAssert(mi->master == nullptr);
if (mi->cached_master != nullptr)
selectDb(mi->cached_master, 0);
}
if (g_pserver->syslog_enabled) {
openlog(g_pserver->syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
g_pserver->syslog_facility);
@@ -3317,6 +3348,7 @@ void call(client *c, int flags) {
dirty = g_pserver->dirty;
start = ustime();
c->cmd->proc(c);
serverTL->commandsExecuted++;
duration = ustime()-start;
dirty = g_pserver->dirty-dirty;
if (dirty < 0) dirty = 0;
@@ -3429,8 +3461,14 @@ void call(client *c, int flags) {
* other operations can be performed by the caller. Otherwise
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
int processCommand(client *c, int callFlags) {
serverAssert(GlobalLocksAcquired());
moduleCallCommandFilters(c);
AeLocker locker;
AssertCorrectThread(c);
if (moduleHasCommandFilters())
{
locker.arm(c);
moduleCallCommandFilters(c);
}
/* The QUIT command is handled separately. Normal command procs will
* go through checking for replication and QUIT will cause trouble
@@ -3442,10 +3480,6 @@ int processCommand(client *c, int callFlags) {
return C_ERR;
}
AssertCorrectThread(c);
serverAssert(GlobalLocksAcquired());
incrementMvccTstamp();
/* Now lookup the command and check ASAP about trivial error conditions
* such as wrong arity, bad command name and so forth. */
c->cmd = c->lastcmd = lookupCommand((sds)ptrFromObj(c->argv[0]));
@@ -3482,6 +3516,8 @@ int processCommand(client *c, int callFlags) {
/* Check if the user can run this command according to the current
* ACLs. */
if (c->puser && !(c->puser->flags & USER_FLAG_ALLCOMMANDS))
locker.arm(c); // ACLs require the lock
int acl_retval = ACLCheckCommandPerm(c);
if (acl_retval != ACL_OK) {
flagTransaction(c);
@@ -3507,6 +3543,7 @@ int processCommand(client *c, int callFlags) {
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0 &&
c->cmd->proc != execCommand))
{
locker.arm(c);
int hashslot;
int error_code;
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,
@@ -3522,6 +3559,11 @@ int processCommand(client *c, int callFlags) {
}
}
incrementMvccTstamp();
if (!locker.isArmed())
locker.arm(c);
/* Handle the maxmemory directive.
*
* Note that we do not want to reclaim memory if we are here re-entering
@@ -3986,8 +4028,7 @@ sds genRedisInfoString(const char *section) {
call_uname = 0;
}
unsigned int lruclock;
atomicGet(g_pserver->lruclock,lruclock);
unsigned int lruclock = g_pserver->lruclock.load();
info = sdscatprintf(info,
"# Server\r\n"
"redis_version:%s\r\n"
@@ -4299,8 +4340,8 @@ sds genRedisInfoString(const char *section) {
g_pserver->stat_numconnections,
g_pserver->stat_numcommands,
getInstantaneousMetric(STATS_METRIC_COMMAND),
g_pserver->stat_net_input_bytes,
g_pserver->stat_net_output_bytes,
g_pserver->stat_net_input_bytes.load(),
g_pserver->stat_net_output_bytes.load(),
(float)getInstantaneousMetric(STATS_METRIC_NET_INPUT)/1024,
(float)getInstantaneousMetric(STATS_METRIC_NET_OUTPUT)/1024,
g_pserver->stat_rejected_conn,
@@ -4330,7 +4371,8 @@ sds genRedisInfoString(const char *section) {
info = sdscatprintf(info,
"# Replication\r\n"
"role:%s\r\n",
listLength(g_pserver->masters) == 0 ? "master" : "slave");
listLength(g_pserver->masters) == 0 ? "master"
: g_pserver->fActiveReplica ? "active-replica" : "slave");
if (listLength(g_pserver->masters)) {
listIter li;
listNode *ln;
@@ -4440,7 +4482,7 @@ sds genRedisInfoString(const char *section) {
"slave%d:ip=%s,port=%d,state=%s,"
"offset=%lld,lag=%ld\r\n",
slaveid,slaveip,slave->slave_listening_port,state,
slave->repl_ack_off, lag);
(slave->repl_ack_off + slave->reploff_skipped), lag);
slaveid++;
}
}
@@ -4901,22 +4943,39 @@ int redisIsSupervised(int mode) {
uint64_t getMvccTstamp()
{
return g_pserver->mvcc_tstamp;
uint64_t rval;
__atomic_load(&g_pserver->mvcc_tstamp, &rval, __ATOMIC_ACQUIRE);
return rval;
}
void incrementMvccTstamp()
{
uint64_t msPrev = g_pserver->mvcc_tstamp >> 20;
if (msPrev >= (uint64_t)g_pserver->mstime) // we can be greater if the count overflows
uint64_t msPrev;
__atomic_load(&g_pserver->mvcc_tstamp, &msPrev, __ATOMIC_ACQUIRE);
msPrev >>= 20; // convert to milliseconds
long long mst;
__atomic_load(&g_pserver->mstime, &mst, __ATOMIC_RELAXED);
if (msPrev >= (uint64_t)mst) // we can be greater if the count overflows
{
atomicIncr(g_pserver->mvcc_tstamp, 1);
}
else
{
g_pserver->mvcc_tstamp = ((uint64_t)g_pserver->mstime) << 20;
atomicSet(g_pserver->mvcc_tstamp, ((uint64_t)g_pserver->mstime) << 20);
}
}
void OnTerminate()
{
/* Any uncaught exception will call std::terminate().
We want this handled like a segfault (printing the stack trace etc).
The easiest way to achieve that is to acutally segfault, so we assert
here.
*/
serverAssert(false);
}
void *workerThreadMain(void *parg)
{
int iel = (int)((int64_t)parg);
@@ -4937,6 +4996,8 @@ int main(int argc, char **argv) {
struct timeval tv;
int j;
std::set_terminate(OnTerminate);
#ifdef USE_MEMKIND
storage_init(NULL, 0);
#endif
@@ -4955,8 +5016,6 @@ int main(int argc, char **argv) {
return sha1Test(argc, argv);
} else if (!strcasecmp(argv[2], "util")) {
return utilTest(argc, argv);
} else if (!strcasecmp(argv[2], "sds")) {
return sdsTest(argc, argv);
} else if (!strcasecmp(argv[2], "endianconv")) {
return endianconvTest(argc, argv);
} else if (!strcasecmp(argv[2], "crc64")) {
+37 -27
View File
@@ -40,6 +40,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <string.h>
#include <time.h>
#include <limits.h>
@@ -49,6 +50,9 @@
#include <pthread.h>
#include <syslog.h>
#include <netinet/in.h>
#include <atomic>
#include <vector>
#include <algorithm>
#ifdef __cplusplus
extern "C" {
#include <lua.h>
@@ -84,6 +88,8 @@ typedef long long mstime_t; /* millisecond time type. */
#include "endianconv.h"
#include "crc64.h"
extern int g_fTestMode;
struct redisObject;
class robj_roptr
{
@@ -703,13 +709,14 @@ typedef struct RedisModuleDigest {
#define OBJ_SHARED_REFCOUNT INT_MAX
#define OBJ_MVCC_INVALID (0xFFFFFFFFFFFFFFFFULL)
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
mutable int refcount;
mutable std::atomic<int> refcount;
uint64_t mvcc_tstamp;
void *m_ptr;
} robj;
@@ -917,7 +924,8 @@ typedef struct client {
time_t ctime; /* Client creation time. */
time_t lastinteraction; /* Time of the last interaction, used for timeout */
time_t obuf_soft_limit_reached_time;
int flags; /* Client flags: CLIENT_* macros. */
std::atomic<int> flags; /* Client flags: CLIENT_* macros. */
int casyncOpsPending;
int fPendingAsyncWrite; /* NOTE: Not a flag because it is written to outside of the client lock (locked by the global lock instead) */
int authenticated; /* Needed when the default user requires auth. */
int replstate; /* Replication state if this is a slave. */
@@ -928,6 +936,7 @@ typedef struct client {
sds replpreamble; /* Replication DB preamble. */
long long read_reploff; /* Read replication offset if this is a master. */
long long reploff; /* Applied replication offset if this is a master. */
long long reploff_skipped; /* Repl backlog we did not send to this client */
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
long long repl_ack_time;/* Replication ack time, if this is a slave. */
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
@@ -977,7 +986,7 @@ struct moduleLoadQueueEntry {
};
struct sharedObjectsStruct {
robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
robj *crlf, *ok, *err, *emptybulk, *emptymultibulk, *nullbulk, *czero, *cone, *pong, *space,
*colon, *queued, *null[4], *nullarray[4],
*emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
@@ -1140,7 +1149,7 @@ struct redisServerThreadVars {
aeEventLoop *el;
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
int ipfd_count; /* Used slots in ipfd[] */
list *clients_pending_write; /* There is to write or install handler. */
std::vector<client*> clients_pending_write; /* There is to write or install handler. */
list *unblocked_clients; /* list of clients to unblock before next loop NOT THREADSAFE */
list *clients_pending_asyncwrite;
int cclients;
@@ -1148,7 +1157,10 @@ struct redisServerThreadVars {
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
client blocked on a module command needs
to be processed. */
client *lua_client = nullptr; /* The "fake client" to query Redis from Lua */
struct fastlock lockPendingWrite;
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
long unsigned commandsExecuted = 0;
};
struct redisMaster {
@@ -1156,7 +1168,7 @@ struct redisMaster {
char *masterauth; /* AUTH with this password with master */
char *masterhost; /* Hostname of master */
int masterport; /* Port of master */
client *cached_master; /* Cached master to be reused for PSYNC. */
client *cached_master; /* Cached master to be reused for PSYNC. */
client *master;
/* The following two fields is where we store master PSYNC replid/offset
* while the PSYNC is in progress. At the end we'll copy the fields into
@@ -1236,7 +1248,7 @@ struct redisServer {
struct redisServerThreadVars rgthreadvar[MAX_EVENT_LOOPS];
unsigned int lruclock; /* Clock for LRU eviction */
std::atomic<unsigned int> lruclock; /* Clock for LRU eviction */
int shutdown_asap; /* SHUTDOWN needed ASAP */
int activerehashing; /* Incremental rehash in serverCron() */
int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */
@@ -1266,9 +1278,8 @@ struct redisServer {
rax *clients_index; /* Active clients dictionary by client ID. */
int clients_paused; /* True if clients are currently paused */
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
uint64_t next_client_id; /* Next client unique ID. Incremental. */
std::atomic<uint64_t> next_client_id; /* Next client unique ID. Incremental. */
int protected_mode; /* Don't accept external connections. */
/* RDB / AOF loading information */
int loading; /* We are loading data from disk if true */
@@ -1305,8 +1316,8 @@ struct redisServer {
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
struct malloc_stats cron_malloc_stats; /* sampled in serverCron(). */
long long stat_net_input_bytes; /* Bytes read from network. */
long long stat_net_output_bytes; /* Bytes written to network. */
std::atomic<long long> stat_net_input_bytes; /* Bytes read from network. */
std::atomic<long long> stat_net_output_bytes; /* Bytes written to network. */
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
/* The following two are used to track instantaneous metrics, like
@@ -1317,7 +1328,6 @@ struct redisServer {
long long samples[STATS_METRIC_SAMPLES];
int idx;
} inst_metric[STATS_METRIC_COUNT];
/* AOF persistence */
int aof_state; /* AOF_(ON|OFF|WAIT_REWRITE) */
int aof_fsync; /* Kind of fsync() policy */
@@ -1327,6 +1337,7 @@ struct redisServer {
off_t aof_rewrite_min_size; /* the AOF file is at least N bytes. */
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */
off_t aof_current_size; /* AOF current size. */
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
pid_t aof_child_pid; /* PID if rewriting process */
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
@@ -1464,10 +1475,10 @@ struct redisServer {
int list_max_ziplist_size;
int list_compress_depth;
/* time cache */
time_t unixtime; /* Unix time sampled every cron cycle. */
time_t timezone; /* Cached timezone. As set by tzset(). */
int daylight_active; /* Currently in daylight saving time. */
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
std::atomic<time_t> unixtime; /* Unix time sampled every cron cycle. */
time_t timezone; /* Cached timezone. As set by tzset(). */
int daylight_active; /* Currently in daylight saving time. */
long long mstime; /* 'unixtime' with milliseconds resolution. */
/* Pubsub */
dict *pubsub_channels; /* Map channels to list of subscribed clients */
list *pubsub_patterns; /* A list of pubsub_patterns */
@@ -1493,8 +1504,7 @@ struct redisServer {
REDISMODULE_CLUSTER_FLAG_*. */
/* Scripting */
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
client *lua_client; /* The "fake client" to query Redis from Lua */
client *lua_caller; /* The client running EVAL right now, or NULL */
client *lua_caller = nullptr; /* The client running EVAL right now, or NULL */
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
mstime_t lua_time_limit; /* Script timeout in milliseconds */
@@ -1526,12 +1536,6 @@ struct redisServer {
int bug_report_start; /* True if bug report header was already logged. */
int watchdog_period; /* Software watchdog period in ms. 0 = off */
/* Mutexes used to protect atomic variables when atomic builtins are
* not available. */
pthread_mutex_t lruclock_mutex;
pthread_mutex_t next_client_id_mutex;
pthread_mutex_t unixtime_mutex;
int fActiveReplica; /* Can this replica also be a master? */
struct fastlock flock;
@@ -1540,6 +1544,9 @@ struct redisServer {
// Lower 20 bits: a counter incrementing for each command executed in the same millisecond
// Upper 44 bits: mstime (least significant 44-bits) enough for ~500 years before rollover from date of addition
uint64_t mvcc_tstamp;
/* System hardware info */
size_t system_memory_size; /* Total memory in system as reported by OS */
};
typedef struct pubsubPattern {
@@ -1673,6 +1680,7 @@ void moduleAcquireGIL(int fServerThread);
void moduleReleaseGIL(int fServerThread);
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
void moduleCallCommandFilters(client *c);
int moduleHasCommandFilters();
/* Utils */
long long ustime(void);
@@ -1687,7 +1695,7 @@ void redisSetProcTitle(const char *title);
/* networking.c -- Networking and Client related operations */
client *createClient(int fd, int iel);
void closeTimedoutClients(void);
void freeClient(client *c);
bool freeClient(client *c);
void freeClientAsync(client *c);
void resetClient(client *c);
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
@@ -1703,12 +1711,13 @@ void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
void addReplyNull(client *c);
void addReplyNull(client *c, robj_roptr objOldProtocol = nullptr);
void addReplyNullArray(client *c);
void addReplyBool(client *c, int b);
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
void addReplyProto(client *c, const char *s, size_t len);
void addReplyBulk(client *c, robj_roptr obj);
void AddReplyFromClient(client *c, client *src);
void addReplyBulkCString(client *c, const char *s);
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
void addReplyBulkLongLong(client *c, long long ll);
@@ -2466,7 +2475,7 @@ struct redisMaster *MasterInfoFromClient(client *c);
uint64_t getMvccTstamp();
void incrementMvccTstamp();
#if defined(__GNUC__) && !defined(NO_DEPRECATE_FREE)
#if __GNUC__ >= 7 && !defined(NO_DEPRECATE_FREE)
[[deprecated]]
void *calloc(size_t count, size_t size);
[[deprecated]]
@@ -2495,9 +2504,10 @@ void xorDigest(unsigned char *digest, const void *ptr, size_t len);
int populateCommandTableParseFlags(struct redisCommand *c, const char *strflags);
int moduleGILAcquiredByModule(void);
extern bool g_fInCrash;
static inline int GlobalLocksAcquired(void) // Used in asserts to verify all global locks are correctly acquired for a server-thread to operate
{
return aeThreadOwnsLock() || moduleGILAcquiredByModule();
return aeThreadOwnsLock() || moduleGILAcquiredByModule() || g_fInCrash;
}
inline int ielFromEventLoop(const aeEventLoop *eventLoop)
+1 -1
View File
@@ -475,7 +475,7 @@ void sortCommand(client *c) {
vector[j].u.score = strtod(szFromObj(byval),&eptr);
if (eptr[0] != '\0' || errno == ERANGE ||
isnan(vector[j].u.score))
std::isnan(vector[j].u.score))
{
int_conversion_error = 1;
}
+1 -1
View File
@@ -615,7 +615,7 @@ void hincrbyfloatCommand(client *c) {
}
value += incr;
if (isnan(value) || isinf(value)) {
if (std::isnan(value) || std::isinf(value)) {
addReplyError(c,"increment would produce NaN or Infinity");
return;
}
+3 -3
View File
@@ -331,7 +331,7 @@ void lindexCommand(client *c) {
addReplyBulk(c,value);
decrRefCount(value);
} else {
addReplyNull(c);
addReplyNull(c,shared.nullbulk);
}
} else {
serverPanic("Unknown list encoding");
@@ -414,7 +414,7 @@ void lrangeCommand(client *c) {
/* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
addReplyNull(c);
addReplyNull(c,shared.emptymultibulk);
return;
}
if (end >= llen) end = llen-1;
@@ -617,7 +617,7 @@ void rpoplpushCommand(client *c) {
* the AOF and replication channel.
*
* The argument 'where' is LIST_TAIL or LIST_HEAD, and indicates if the
* 'value' element was popped fron the head (BLPOP) or tail (BRPOP) so that
* 'value' element was popped from the head (BLPOP) or tail (BRPOP) so that
* we can propagate the command properly.
*
* The function returns C_OK if we are able to serve the client, otherwise
+2 -2
View File
@@ -492,14 +492,14 @@ void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamI
streamEncodeID(si->start_key,start);
} else {
si->start_key[0] = 0;
si->start_key[0] = 0;
si->start_key[1] = 0;
}
if (end) {
streamEncodeID(si->end_key,end);
} else {
si->end_key[0] = UINT64_MAX;
si->end_key[0] = UINT64_MAX;
si->end_key[1] = UINT64_MAX;
}
/* Seek the correct node in the radix tree. */
+2 -2
View File
@@ -28,7 +28,7 @@
*/
#include "server.h"
#include <math.h> /* isnan(), isinf() */
#include <cmath> /* isnan(), isinf() */
/*-----------------------------------------------------------------------------
* String Commands
@@ -408,7 +408,7 @@ void incrbyfloatCommand(client *c) {
return;
value += incr;
if (isnan(value) || isinf(value)) {
if (std::isnan(value) || std::isinf(value)) {
addReplyError(c,"increment would produce NaN or Infinity");
return;
}
+20 -14
View File
@@ -134,7 +134,7 @@ zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
serverAssert(!isnan(score));
serverAssert(!std::isnan(score));
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
/* store rank that is crossed to reach the insert position */
@@ -530,11 +530,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
} else {
if (((char*)ptrFromObj(min))[0] == '(') {
spec->min = strtod((char*)ptrFromObj(min)+1,&eptr);
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
if (eptr[0] != '\0' || std::isnan(spec->min)) return C_ERR;
spec->minex = 1;
} else {
spec->min = strtod((char*)ptrFromObj(min),&eptr);
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
if (eptr[0] != '\0' || std::isnan(spec->min)) return C_ERR;
}
}
if (max->encoding == OBJ_ENCODING_INT) {
@@ -542,11 +542,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
} else {
if (((char*)ptrFromObj(max))[0] == '(') {
spec->max = strtod((char*)ptrFromObj(max)+1,&eptr);
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
if (eptr[0] != '\0' || std::isnan(spec->max)) return C_ERR;
spec->maxex = 1;
} else {
spec->max = strtod((char*)ptrFromObj(max),&eptr);
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
if (eptr[0] != '\0' || std::isnan(spec->max)) return C_ERR;
}
}
@@ -1320,7 +1320,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
double curscore;
/* NaN as input is an error regardless of all the other parameters. */
if (isnan(score)) {
if (std::isnan(score)) {
*flags = ZADD_NAN;
return 0;
}
@@ -1339,7 +1339,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
/* Prepare the score for the increment if needed. */
if (incr) {
score += curscore;
if (isnan(score)) {
if (std::isnan(score)) {
*flags |= ZADD_NAN;
return 0;
}
@@ -1385,7 +1385,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
/* Prepare the score for the increment if needed. */
if (incr) {
score += curscore;
if (isnan(score)) {
if (std::isnan(score)) {
*flags |= ZADD_NAN;
return 0;
}
@@ -2150,7 +2150,7 @@ inline static void zunionInterAggregate(double *target, double val, int aggregat
/* The result of adding two doubles is NaN when one variable
* is +inf and the other is -inf. When these numbers are added,
* we maintain the convention of the result being 0.0. */
if (isnan(*target)) *target = 0.0;
if (std::isnan(*target)) *target = 0.0;
} else if (aggregate == REDIS_AGGR_MIN) {
*target = val < *target ? val : *target;
} else if (aggregate == REDIS_AGGR_MAX) {
@@ -2283,7 +2283,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
double score, value;
score = src[0].weight * zval.score;
if (isnan(score)) score = 0;
if (std::isnan(score)) score = 0;
for (j = 1; j < setnum; j++) {
/* It is not safe to access the zset we are
@@ -2330,7 +2330,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
while (zuiNext(&src[i],&zval)) {
/* Initialize value */
score = src[i].weight * zval.score;
if (isnan(score)) score = 0;
if (std::isnan(score)) score = 0;
/* Search for this element in the accumulating dictionary. */
de = dictAddRaw(accumulator,zuiSdsFromValue(&zval),&existing);
@@ -2439,7 +2439,7 @@ void zrangeGenericCommand(client *c, int reverse) {
/* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
addReplyNull(c);
addReplyNull(c,shared.emptymultibulk);
return;
}
if (end >= llen) end = llen-1;
@@ -2595,7 +2595,10 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
if (eptr == NULL) {
addReplyNull(c);
if (c->resp < 3)
addReply(c, shared.emptyarray);
else
addReplyNull(c);
return;
}
@@ -2662,7 +2665,10 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
if (ln == NULL) {
addReplyNull(c);
if (c->resp < 3)
addReply(c, shared.emptyarray);
else
addReplyNull(c);
return;
}
+3 -2
View File
@@ -1,2 +1,3 @@
#define KEYDB_REAL_VERSION "0.9.2"
extern const char *KEYDB_SET_VERSION; // Unlike real version, this can be overriden by the config
#define KEYDB_REAL_VERSION "5.0.1"
extern const char *KEYDB_SET_VERSION; // Unlike real version, this can be overriden by the config
+1 -1
View File
@@ -576,7 +576,7 @@ void zipEntry(unsigned char *p, zlentry *e) {
/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) {
unsigned int bytes = ZIPLIST_HEADER_SIZE+1;
unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;
unsigned char *zl = zmalloc(bytes, MALLOC_SHARED);
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
+4 -1
View File
@@ -166,12 +166,15 @@ start_server {} {
# Pick a random slave
set slave_id [expr {($master_id+1)%5}]
set sync_count [status $R($master_id) sync_full]
set sync_partial [status $R($master_id) sync_partial_ok]
catch {
$R($slave_id) config rewrite
$R($slave_id) debug restart
}
# note: just waiting for connected_slaves==4 has a race condition since
# we might do the check before the master realized that the slave disconnected
wait_for_condition 50 2000 {
[status $R($master_id) connected_slaves] == 4
[status $R($master_id) sync_partial_ok] == $sync_partial + 1
} else {
fail "Replica not reconnecting"
}
+97
View File
@@ -0,0 +1,97 @@
start_server {tags {"active-repl"} overrides {active-replica yes}} {
set slave [srv 0 client]
set slave_host [srv 0 host]
set slave_port [srv 0 port]
set slave_log [srv 0 stdout]
set slave_pid [s process_id]
start_server [list overrides [list active-replica yes replicaof [list $slave_host $slave_port]]] {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
# Use a short replication timeout on the slave, so that if there
# are no bugs the timeout is triggered in a reasonable amount
# of time.
$slave config set repl-timeout 5
$master config set repl-timeout 5
# Start the replication process...
$slave slaveof $master_host $master_port
#note the master is a replica via the config (see start_server above)
test {Active replicas report the correct role} {
wait_for_condition 50 100 {
[string match *active-replica* [$slave role]]
} else {
fail "Replica0 does not report the correct role"
}
wait_for_condition 50 100 {
[string match *active-replica* [$master role]]
} else {
fail "Replica1 does not report the correct role"
}
}
test {Active replicas propogate} {
$master set testkey foo
wait_for_condition 50 500 {
[string match *foo* [$slave get testkey]]
} else {
fail "replication failed to propogate"
}
$slave set testkey bar
wait_for_condition 50 500 {
[string match bar [$master get testkey]]
} else {
fail "replication failed to propogate in the other direction"
}
}
test {Active replicas propogate binary} {
$master set binkey "\u0000foo"
wait_for_condition 50 500 {
[string match *foo* [$slave get binkey]]
} else {
fail "replication failed to propogate binary data"
}
}
test {Active replicas WAIT} {
# Test that wait succeeds since replicas should be syncronized
$master set testkey foo
$slave set testkey2 test
assert_equal {1} [$master wait 1 1000] { "value should propogate
within 1 second" }
assert_equal {1} [$slave wait 1 1000] { "value should propogate
within 1 second" }
# Now setup a situation where wait should fail
exec kill -SIGSTOP $slave_pid
$master set testkey fee
assert_equal {0} [$master wait 1 1000] { "slave shouldn't be
synchronized since its stopped" }
}
# Resume the replica we paused in the prior test
exec kill -SIGCONT $slave_pid
test {Active replica expire propogates} {
$master set testkey1 foo
wait_for_condition 50 1000 {
[string match *foo* [$slave get testkey1]]
} else {
fail "Replication failed to propogate"
}
$master pexpire testkey1 200
after 1000
assert_equal {0} [$master del testkey1] {"master expired"}
assert_equal {0} [$slave del testkey1] {"slave expired"}
$slave set testkey1 foo px 200
after 1000
assert_equal {0} [$master del testkey1]
assert_equal {0} [$slave del testkey1]
}
}
}
+26
View File
@@ -79,6 +79,32 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
stop_bg_complex_data $load_handle0
stop_bg_complex_data $load_handle1
stop_bg_complex_data $load_handle2
# Wait for the slave to reach the "online"
# state from the POV of the master.
set retry 5000
while {$retry} {
set info [$master info]
if {[string match {*slave0:*state=online*} $info]} {
break
} else {
incr retry -1
after 100
}
}
if {$retry == 0} {
error "assertion:Slave not correctly synchronized"
}
# Wait that slave acknowledge it is online so
# we are sure that DBSIZE and DEBUG DIGEST will not
# fail because of timing issues. (-LOADING error)
wait_for_condition 5000 100 {
[lindex [$slave role] 3] eq {connected}
} else {
fail "Slave still not connected after some time"
}
set retry 10
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
{
+24
View File
@@ -0,0 +1,24 @@
# find the OS
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
# Compile flags for linux / osx
ifeq ($(uname_S),Linux)
SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2
SHOBJ_LDFLAGS ?= -shared
else
SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -g -ggdb -std=c99 -O2
SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
endif
.SUFFIXES: .c .so .xo .o
all: commandfilter.so
.c.xo:
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
commandfilter.xo: ../../src/redismodule.h
commandfilter.so: commandfilter.xo
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
@@ -1,18 +1,18 @@
#define REDISMODULE_EXPERIMENTAL_API
#include "../redismodule.h"
#include "redismodule.h"
#include <string.h>
static RedisModuleString *log_key_name;
static const char log_command_name[] = "hellofilter.log";
static const char ping_command_name[] = "hellofilter.ping";
static const char unregister_command_name[] = "hellofilter.unregister";
static const char log_command_name[] = "commandfilter.log";
static const char ping_command_name[] = "commandfilter.ping";
static const char unregister_command_name[] = "commandfilter.unregister";
static int in_log_command = 0;
static RedisModuleCommandFilter *filter = NULL;
int HelloFilter_UnregisterCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
int CommandFilter_UnregisterCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
(void) argc;
(void) argv;
@@ -23,7 +23,7 @@ int HelloFilter_UnregisterCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
return REDISMODULE_OK;
}
int HelloFilter_PingCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
int CommandFilter_PingCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
(void) argc;
(void) argv;
@@ -39,7 +39,7 @@ int HelloFilter_PingCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int a
return REDISMODULE_OK;
}
int HelloFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
int CommandFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModuleString *s = RedisModule_CreateString(ctx, "", 0);
@@ -74,9 +74,9 @@ int HelloFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int ar
return REDISMODULE_OK;
}
void HelloFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
void CommandFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
{
if (in_log_command) return; /* don't process our own RM_Call() from HelloFilter_LogCommand() */
if (in_log_command) return; /* don't process our own RM_Call() from CommandFilter_LogCommand() */
/* Fun manipulations:
* - Remove @delme
@@ -117,7 +117,7 @@ void HelloFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (RedisModule_Init(ctx,"hellofilter",1,REDISMODULE_APIVER_1)
if (RedisModule_Init(ctx,"commandfilter",1,REDISMODULE_APIVER_1)
== REDISMODULE_ERR) return REDISMODULE_ERR;
if (argc != 2) {
@@ -130,18 +130,18 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
RedisModule_StringToLongLong(argv[1], &noself);
if (RedisModule_CreateCommand(ctx,log_command_name,
HelloFilter_LogCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
CommandFilter_LogCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,ping_command_name,
HelloFilter_PingCommand,"deny-oom",1,1,1) == REDISMODULE_ERR)
CommandFilter_PingCommand,"deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,unregister_command_name,
HelloFilter_UnregisterCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
CommandFilter_UnregisterCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if ((filter = RedisModule_RegisterCommandFilter(ctx, HelloFilter_CommandFilter,
if ((filter = RedisModule_RegisterCommandFilter(ctx, CommandFilter_CommandFilter,
noself ? REDISMODULE_CMDFILTER_NOSELF : 0))
== NULL) return REDISMODULE_ERR;
+2 -2
View File
@@ -41,6 +41,7 @@ set ::all_tests {
integration/replication-3
integration/replication-4
integration/replication-psync
integration/replication-active
integration/aof
integration/rdb
integration/convert-zipmap-hash-on-load
@@ -63,7 +64,6 @@ set ::all_tests {
unit/lazyfree
unit/wait
unit/pendingquerybuf
modules/commandfilter
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
@@ -504,7 +504,7 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} elseif {$opt eq {--only}} {
lappend ::only_tests $arg
incr j
} elseif {$opt eq {--skiptill}} {
} elseif {$opt eq {--skip-till}} {
set ::skip_till $arg
incr j
} elseif {$opt eq {--list-tests}} {
+7
View File
@@ -108,4 +108,11 @@ start_server {tags {"acl"}} {
assert_match {*+debug|segfault*} $cmdstr
assert_match {*+acl*} $cmdstr
}
test {ACL #5998 regression: memory leaks adding / removing subcommands} {
r AUTH default ""
r ACL setuser newuser reset -debug +debug|a +debug|b +debug|c
r ACL setuser newuser -debug
# The test framework will detect a leak if any.
}
}
+4 -5
View File
@@ -2,11 +2,11 @@ start_server {tags {"lazyfree"}} {
test "UNLINK can reclaim memory in background" {
set orig_mem [s used_memory]
set args {}
for {set i 0} {$i < 100000} {incr i} {
for {set i 0} {$i < 200000} {incr i} {
lappend args $i
}
r sadd myset {*}$args
assert {[r scard myset] == 100000}
assert {[r scard myset] == 200000}
set peak_mem [s used_memory]
assert {[r unlink myset] == 1}
assert {$peak_mem > $orig_mem+1000000}
@@ -19,14 +19,13 @@ start_server {tags {"lazyfree"}} {
}
test "FLUSHDB ASYNC can reclaim memory in background" {
after 500 # Sometimes Redis is busy with a prior operation
set orig_mem [s used_memory]
set args {}
for {set i 0} {$i < 100000} {incr i} {
for {set i 0} {$i < 200000} {incr i} {
lappend args $i
}
r sadd myset {*}$args
assert {[r scard myset] == 100000}
assert {[r scard myset] == 200000}
set peak_mem [s used_memory]
r flushdb async
assert {$peak_mem > $orig_mem+1000000}
+4 -3
View File
@@ -161,7 +161,7 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
}
# make sure master doesn't disconnect slave because of timeout
$master config set repl-timeout 300 ;# 5 minutes
$master config set repl-timeout 1200 ;# 20 minutes (for valgrind and slow machines)
$master config set maxmemory-policy allkeys-random
$master config set client-output-buffer-limit "replica 100000000 100000000 300"
$master config set repl-backlog-size [expr {10*1024}]
@@ -212,7 +212,8 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
assert {[$master dbsize] == 100}
assert {$slave_buf > 2*1024*1024} ;# some of the data may have been pushed to the OS buffers
assert {$delta < 50*1024 && $delta > -50*1024} ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
set delta_max [expr {$cmd_count / 2}] ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
assert {$delta < $delta_max && $delta > -$delta_max}
$master client kill type slave
set killed_used [s -1 used_memory]
@@ -221,7 +222,7 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
set killed_used_no_repl [expr {$killed_used - $killed_mem_not_counted_for_evict}]
set delta_no_repl [expr {$killed_used_no_repl - $used_no_repl}]
assert {$killed_slave_buf == 0}
assert {$delta_no_repl > -50*1024 && $delta_no_repl < 50*1024} ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
assert {$delta_no_repl > -$delta_max && $delta_no_repl < $delta_max}
}
# unfreeze slave process (after the 'test' succeeded or failed, but before we attempt to terminate the server
@@ -1,4 +1,4 @@
set testmodule [file normalize src/modules/hellofilter.so]
set testmodule [file normalize tests/modules/commandfilter.so]
start_server {tags {"modules"}} {
r module load $testmodule log-key 0
@@ -27,7 +27,7 @@ start_server {tags {"modules"}} {
test {Command Filter applies on RM_Call() commands} {
r del log-key
r hellofilter.ping
r commandfilter.ping
r lrange log-key 0 -1
} "{ping @log}"
@@ -39,13 +39,13 @@ start_server {tags {"modules"}} {
test {Command Filter applies on Lua redis.call() that calls a module} {
r del log-key
r eval "redis.call('hellofilter.ping')" 0
r eval "redis.call('commandfilter.ping')" 0
r lrange log-key 0 -1
} "{ping @log}"
test {Command Filter is unregistered implicitly on module unload} {
r del log-key
r module unload hellofilter
r module unload commandfilter
r set mykey @log
r lrange log-key 0 -1
} {}
@@ -59,14 +59,14 @@ start_server {tags {"modules"}} {
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
# Unregister
r hellofilter.unregister
r commandfilter.unregister
r del log-key
r set mykey @log
r lrange log-key 0 -1
} {}
r module unload hellofilter
r module unload commandfilter
r module load $testmodule log-key 1
test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} {
@@ -74,10 +74,10 @@ start_server {tags {"modules"}} {
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
r del log-key
r hellofilter.ping
r commandfilter.ping
assert_equal {} [r lrange log-key 0 -1]
r eval "redis.call('hellofilter.ping')" 0
r eval "redis.call('commandfilter.ping')" 0
assert_equal {} [r lrange log-key 0 -1]
}
+1 -1
View File
@@ -15,7 +15,7 @@ start_server {tags {"obuf-limits"}} {
if {![regexp {omem=([0-9]+)} $c - omem]} break
if {$omem > 200000} break
}
assert {$omem >= 90000 && $omem < 200000}
assert {$omem >= 80000 && $omem < 200000}
$rd1 close
}
+7
View File
@@ -1,3 +1,10 @@
start_server {tags {"other"} overrides {databases 64}} {
test {CONF-DATABASES - ensure the databases config option is respected} {
r select 63
r set testkey {foo}
} {OK}
}
start_server {tags {"other"}} {
if {$::force_failure} {
# This is used just for test suite development purposes.