Compare commits

...
64 Commits
Author SHA1 Message Date
benschermel a643c66ca5 update perf chart, build info, job post link 2021-01-10 04:54:47 +00:00
benschermel 58d2cd592c created ascii logo image 2021-01-10 04:29:27 +00:00
John Sully bc8cc1adb3 Merge branch 'repl_perf' into unstable 2020-12-24 02:22:57 +00:00
John Sully d03c2de901 Merge branch 'unstable' of https://github.com/JohnSully/KeyDB into unstable 2020-12-11 03:59:50 +00:00
John Sully 7d3c5f1e64 don't try and free a client that can't be freed 2020-12-11 03:59:39 +00:00
John Sully 81573de393 Prevent use after free 2020-12-11 03:59:26 +00:00
John Sully a80dd5d001 Remove AE_ASSERT and use the proper assert infra 2020-12-11 00:24:27 +00:00
John Sully 7e0077f293 Prevent test crash due to unimplemented save for nested hash 2020-12-11 00:23:44 +00:00
John Sully 1eb035e6ad Fix crash freeing nested hash with list 2020-12-11 00:23:14 +00:00
Kajaruban Surendran bdd14ac517 Addressing PR comments 2020-12-04 14:54:06 -05:00
Kajaruban Surendran 63e5d00bdf Addressing PR comments 2020-12-04 14:54:06 -05:00
Kajaruban Surendran c25fb3d74f Handling of aeResizeSetSize's return code for current thread 2020-12-04 14:54:06 -05:00
Kajaruban Surendran 572d41325b check the aeResizeSetSize return code 2020-12-04 14:54:06 -05:00
Kajaruban Surendran e02120cdf7 Adding thread index to the error message 2020-12-04 14:54:06 -05:00
Kajaruban Surendran 7b1221a74d Call aePostFunction to change the setsize of each threads by that thread 2020-12-04 14:54:06 -05:00
Kajaruban Surendran d6b4f994ad Calling initServerThread function cserver.cthreads(server-threads) times than MAX_EVENT_LOOPS(constant) 2020-12-04 14:52:28 -05:00
christianEQ 66a41dafc4 Issue: #204 Allocate 8 MB to thread stack 2020-12-01 14:50:03 -05:00
John Sully f19b9c155e Issue: #260 Don't use C++ static_assert in C files 2020-11-30 23:01:20 +00:00
John Sully 5d016fdb6b code coverage reports should test multithreaded 2020-11-30 22:59:00 +00:00
VivekSainiEQ cae9924fd9 Added module data type load/save tests 2020-11-30 17:47:04 -05:00
VivekSainiEQ 089f28e8ba Removed use of module datatypes, now should work if tests/modules is not built 2020-11-30 17:47:04 -05:00
VivekSainiEQ dcb44d3a09 Added tests for saving various data types to disk and loading them back, and for loading data types from redis to maintain compatibility 2020-11-30 17:47:04 -05:00
VivekSainiEQ f99cba0b92 Added remainder of runtest scripts to make lcov command. Code coverage goes from 57.9% to 72.1% 2020-11-30 17:47:04 -05:00
VivekSainiEQ e10dc0dace Added KeyDB.info to .gitignore file 2020-11-30 17:47:04 -05:00
Kajaruban Surendran 49a89d636b Configurable option for MOTD 2020-11-30 10:42:48 -05:00
VivekSainiEQ b94f91b9b2 Added references to KeyDB.info instead of redis.info 2020-11-25 18:49:20 -05:00
VivekSainiEQ 06a17c8511 Properly escaped $ in lcov command 2020-11-25 18:49:20 -05:00
VivekSainiEQ eebd70aaa6 Updated 'make lcov' command to exclude external dependancies and generate final code coverage percentage 2020-11-25 18:49:20 -05:00
VivekSainiEQ 779c775df9 Added test for issue #257 2020-11-25 17:21:37 -05:00
VivekSainiEQ 1d4d2925c2 Made NULL pointer check explicit, issue #257 2020-11-25 17:21:37 -05:00
VivekSainiEQ b67db7c2d5 Added NULL check to function objFromAllocPtr, now NULL input will map to NULL output even if active replica is enabled 2020-11-25 17:21:37 -05:00
John Sully 18953b2998 Merge branch 'unstable' of https://github.com/JohnSully/KeyDB into unstable 2020-11-23 02:01:54 +00:00
John Sully 527b7eb074 Fix issue where active replication doesn't replicate RDB data 2020-11-23 02:01:40 +00:00
Kajaruban Surendran 34a97c447f fix for the server crash when the maxclients increased via config set 2020-11-20 15:20:46 -05:00
John Sully 1c525e20b1 Blocking clients should not crash if an active replica loads a remote RDB with a key in the blocklist 2020-11-19 23:28:01 +00:00
John Sully 0bcce50285 Ensure multithreaded tests pass 2020-11-13 21:08:10 +00:00
John Sully 96e0b2a2b1 Improve replication performance in multithreaded scenarios 2020-11-13 21:08:10 +00:00
John Sully f3794d2c42 Add ability to compile KeyDB without MOTD and curl 2020-11-13 20:51:30 +00:00
John Sully 97fd736923 Add missing file 2020-11-11 03:29:38 +00:00
John Sully ba950a3d1f Second implementation of nested hashes 2020-11-06 19:24:48 +00:00
John Sully dad5a902d3 Additional test reliability fixes 2020-10-27 07:25:43 +00:00
John Sully 3e33fbcd35 Disarm not release D'oh 2020-10-27 06:41:33 +00:00
John Sully 444555d3e4 Active replica test reliability enhancements 2020-10-27 06:23:14 +00:00
John Sully 6a74f524e5 Prevent crashes on shutdown due to lock being held 2020-10-27 04:53:19 +00:00
John Sully 9c5b59ac01 Ensure MVCC timestamp is incremented after the real time is updated 2020-10-27 04:52:35 +00:00
John Sully 1840601f8e Fix multithreaded test failure 2020-10-27 01:54:13 +00:00
John Sully 88962af8b2 Avoid unnecessary memory fences 2020-10-26 02:28:38 +00:00
John Sully df22cdf6e9 Remove addReply*Async methods since we already know if its async or not. This is just a source of bugs 2020-10-24 02:18:03 +00:00
John Sully 041446005b Allow the locker to be used even without a client 2020-10-23 20:21:49 +00:00
John Sully 7c39a9b0e1 Fix multithread test failures 2020-10-23 15:57:39 +00:00
John Sully 05cd92f766 Remove leak warning with ASAN in the CLI 2020-10-21 21:16:08 +00:00
John Sully d724e7226c run when necessary 2020-10-16 07:00:03 +00:00
John Sully 738c782f02 Fix replica buffer overflows 2020-10-16 06:47:40 +00:00
John Sully 33b05c859a Avoid locking if we won't run a time event 2020-10-16 06:19:52 +00:00
John Sully ed369d7223 Reduce async write copies 2020-10-15 23:10:17 +00:00
John Sully fb32bb12b1 Fix branding 2020-10-15 20:15:35 +00:00
John Sully 3e996035ea Relax memory order where possible 2020-10-13 06:35:16 +00:00
John Sully ae8a94d615 Significantly improve perf in replication scenarios 2020-10-12 15:27:03 +00:00
John Sully 69a74a567b Avoid excess locking, seeing up to 8% performance improvements 2020-10-12 05:50:58 +00:00
John Sully 1b46d4f09a Perf: remove unnecessary vector operations 2020-10-12 04:28:39 +00:00
John Sully c6f13892e0 Remove dead code 2020-10-09 21:02:09 +00:00
Hanif Bin Ariffin 05e0f32345 Fixed non-empty check in src/Makefile
Per [GNU Make Manual](https://www.gnu.org/software/make/manual/html_node/Conditional-Syntax.html).
To properly check for non-empty variable, one must strip whitespaces.

Signed-off-by: Hanif Bin Ariffin <hanif.ariffin.4326@gmail.com>
2020-10-05 10:58:49 -04:00
Hanif Bin Ariffin 8171e6de13 Removed dead code from a macro in zmalloc.cpp
I think the compiler would have removed this no-op anyways but it
definitely wasted me some 30 minutes on this :(

I was hoping I could remove the branch through some bit-hacking but
apparently its dead code :). Oh well, its 30 minutes of refreshing
bit hacking.

Signed-off-by: Hanif Bin Ariffin <hanif.ariffin.4326@gmail.com>
2020-10-03 20:41:26 -04:00
John Sully b042495f37 Delete CONTRIBUTING 2020-10-01 11:17:56 -04:00
48 changed files with 1580 additions and 765 deletions

No files matched your search

-50
View File
@@ -1,50 +0,0 @@
Note: by contributing code to the Redis project in any form, including sending
a pull request via Github, a code fragment or patch via private email or
public discussion groups, you agree to release your code under the terms
of the BSD license that you can find in the COPYING file included in the Redis
source distribution. You will include BSD license in the COPYING file within
each source file that you contribute.
# IMPORTANT: HOW TO USE REDIS GITHUB ISSUES
* Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature
requests. Everything else belongs to the Redis Google Group:
https://groups.google.com/forum/m/#!forum/Redis-db
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
bugs in the Github issues system. We'll be very happy to help you and provide
all the support in the mailing list.
There is also an active community of Redis users at Stack Overflow:
http://stackoverflow.com/questions/tagged/redis
# How to provide a patch for a new feature
1. If it is a major feature or a semantical change, please don't start coding
straight away: if your feature is not a conceptual fit you'll lose a lot of
time writing the code without any reason. Start by posting in the mailing list
and creating an issue at Github with the description of, exactly, what you want
to accomplish and why. Use cases are important for features to be accepted.
Here you'll see if there is consensus about your idea.
2. If in step 1 you get an acknowledgment from the project leaders, use the
following procedure to submit a patch:
a. Fork Redis on github ( http://help.github.com/fork-a-repo/ )
b. Create a topic branch (git checkout -b my_branch)
c. Push to your branch (git push origin my_branch)
d. Initiate a pull request on github ( https://help.github.com/articles/creating-a-pull-request/ )
e. Done :)
3. Keep in mind that we are very overloaded, so issues and PRs sometimes wait
for a *very* long time. However this is not lack of interest, as the project
gets more and more users, we find ourselves in a constant need to prioritize
certain issues/PRs over others. If you think your issue/PR is very important
try to popularize it, have other users commenting and sharing their point of
view and so forth. This helps.
4. For minor fixes just open a pull request on Github.
Thanks!
+9 -3
View File
@@ -8,6 +8,8 @@
##### Have feedback? Take our quick survey: https://www.surveymonkey.com/r/Y9XNS93
##### KeyDB is Hiring! We are currently building out our dev team. If you are interested please see the posting here: https://keydb.dev/careers.html
What is KeyDB?
--------------
@@ -15,11 +17,13 @@ KeyDB is a high performance fork of Redis with a focus on multithreading, memory
KeyDB maintains full compatibility with the Redis protocol, modules, and scripts. This includes the atomicity guarantees for scripts and transactions. Because KeyDB keeps in sync with Redis development KeyDB is a superset of Redis functionality, making KeyDB a drop in replacement for existing Redis deployments.
On the same hardware KeyDB can perform twice as many queries per second as Redis, with 60% lower latency. Active-Replication simplifies hot-spare failover allowing you to easily distribute writes over replicas and use simple TCP based load balancing/failover. KeyDB's higher performance allows you to do more on less hardware which reduces operation costs and complexity.
On the same hardware KeyDB can achieve significantly higher throughput than Redis. Active-Replication simplifies hot-spare failover allowing you to easily distribute writes over replicas and use simple TCP based load balancing/failover. KeyDB's higher performance allows you to do more on less hardware which reduces operation costs and complexity.
<img src="https://keydb.dev/assets/img/blog/5x_opspersecVSdatasize.PNG"/>
The chart below compares several KeyDB and Redis setups, including the latest Redis6 io-threads option, and TLS benchmarks.
See the full benchmark results and setup information here: https://docs.keydb.dev/blog/2019/10/07/blog-post/
<img src="https://docs.keydb.dev/img/blog/2020-09-15/ops_comparison.png"/>
See the full benchmark results and setup information here: https://docs.keydb.dev/blog/2020/09/29/blog-post/
Why fork Redis?
---------------
@@ -82,6 +86,8 @@ Building KeyDB
KeyDB can be compiled and is tested for use on Linux. KeyDB currently relies on SO_REUSEPORT's load balancing behavior which is available only in Linux. When we support marshalling connections across threads we plan to support other operating systems such as FreeBSD.
More on CentOS/Archlinux/Alpine/Debian/Ubuntu dependencies and builds can be found here: https://docs.keydb.dev/docs/build/
Install dependencies:
% sudo apt install build-essential nasm autotools-dev autoconf libjemalloc-dev tcl tcl-dev uuid-dev libcurl4-openssl-dev
+3
View File
@@ -282,6 +282,9 @@ databases 16
# ASCII art logo in startup logs by setting the following option to yes.
always-show-logo yes
# Retrieving "message of today" using CURL requests.
#enable-motd yes
################################ SNAPSHOTTING ################################
#
# Save the DB on disk:
+1
View File
@@ -27,4 +27,5 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/auth \
--single unit/moduleapi/keyspace_events \
--single unit/moduleapi/blockedclient \
--single unit/moduleapi/moduleloadsave \
"${@}"
+1
View File
@@ -2,4 +2,5 @@
*.gcno
*.gcov
redis.info
KeyDB.info
lcov-html
+11 -7
View File
@@ -47,7 +47,7 @@ endif
USEASM?=true
ifneq ($(SANITIZE),)
ifneq ($(strip $(SANITIZE)),)
CFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
CXXFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
LDFLAGS+= -fsanitize=$(SANITIZE)
@@ -107,7 +107,7 @@ endif
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(REDIS_CFLAGS)
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
FINAL_LIBS+=-lm -lcurl
FINAL_LIBS+=-lm
DEBUG=-g -ggdb
ifneq ($(uname_S),Darwin)
@@ -171,8 +171,11 @@ else
# All the other OSes (notably Linux)
FINAL_LDFLAGS+= -rdynamic
FINAL_LIBS+=-ldl -pthread -lrt -luuid
ifneq ($(NO_MOTD),yes)
FINAL_CFLAGS += -DMOTD
FINAL_CXXFLAGS += -DMOTD
FINAL_LIBS+=-lcurl
endif
endif
endif
endif
@@ -276,7 +279,7 @@ 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 crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o $(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 t_nhash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o $(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 crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ)
REDIS_BENCHMARK_NAME=keydb-benchmark
@@ -375,7 +378,7 @@ DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ
$(KEYDB_AS) $< -o $@
clean:
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep dict-benchmark
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov KeyDB.info lcov-html Makefile.dep dict-benchmark
rm -f $(DEP)
.PHONY: clean
@@ -397,9 +400,10 @@ check: test
lcov:
$(MAKE) gcov
@(set -e; cd ..; ./runtest --clients 1)
@geninfo -o redis.info .
@genhtml --legend -o lcov-html redis.info
@(set -e; cd ..; ./runtest --config server-threads 3; ./runtest-sentinel; ./runtest-cluster; ./runtest-moduleapi)
@geninfo -o KeyDB.info --no-external .
@genhtml --legend -o lcov-html KeyDB.info
@genhtml --legend -o lcov-html KeyDB.info | grep lines | awk '{print $$2;}' | sed 's/%//g'
test-sds: sds.c sds.h
$(REDIS_CC) sds.c zmalloc.cpp -DSDS_TEST_MAIN $(FINAL_LIBS) -o /tmp/sds_test
+33 -21
View File
@@ -48,6 +48,7 @@
#include "fastlock.h"
#include "zmalloc.h"
#include "config.h"
#include "serverassert.h"
#ifdef USE_MUTEX
thread_local int cOwnLock = 0;
@@ -84,8 +85,6 @@ fastlock g_lock("AE (global)");
#endif
thread_local aeEventLoop *g_eventLoopThisThread = NULL;
#define AE_ASSERT(x) if (!(x)) do { fprintf(stderr, "AE_ASSERT FAILURE %s: %d\n", __FILE__, __LINE__); *((volatile int*)1) = 1; } while(0)
/* Include the best multiplexing layer supported by this system.
* The following should be ordered by performances, descending. */
#ifdef HAVE_EVPORT
@@ -140,7 +139,7 @@ void aeProcessCmd(aeEventLoop *eventLoop, int fd, void *, int )
auto cb = read(fd, &cmd, sizeof(aeCommand));
if (cb != sizeof(cmd))
{
AE_ASSERT(errno == EAGAIN);
serverAssert(errno == EAGAIN);
break;
}
switch (cmd.op)
@@ -251,8 +250,8 @@ int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
auto size = safe_write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
if (size != sizeof(cmd))
{
AE_ASSERT(size == sizeof(cmd) || size <= 0);
AE_ASSERT(errno == EAGAIN);
serverAssert(size == sizeof(cmd) || size <= 0);
serverAssert(errno == EAGAIN);
ret = AE_ERR;
}
@@ -307,9 +306,14 @@ int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fSynch
}
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
if (size != sizeof(cmd))
if (!(!size || size == sizeof(cmd))) {
printf("Last error: %d\n", errno);
}
serverAssert(!size || size == sizeof(cmd));
if (size == 0)
return AE_ERR;
AE_ASSERT(size == sizeof(cmd));
int ret = AE_OK;
if (fSynchronous)
{
@@ -352,7 +356,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
goto err;
eventLoop->fdCmdRead = rgfd[0];
eventLoop->fdCmdWrite = rgfd[1];
fcntl(eventLoop->fdCmdWrite, F_SETFL, O_NONBLOCK);
//fcntl(eventLoop->fdCmdWrite, F_SETFL, O_NONBLOCK);
fcntl(eventLoop->fdCmdRead, F_SETFL, O_NONBLOCK);
eventLoop->cevents = 0;
aeCreateFileEvent(eventLoop, eventLoop->fdCmdRead, AE_READABLE|AE_READ_THREADSAFE, aeProcessCmd, NULL);
@@ -373,6 +377,11 @@ int aeGetSetSize(aeEventLoop *eventLoop) {
return eventLoop->setsize;
}
/* Return the current EventLoop. */
aeEventLoop *aeGetCurrentEventLoop(){
return g_eventLoopThisThread;
}
/* Tells the next iteration/s of the event processing to set timeout of 0. */
void aeSetDontWait(aeEventLoop *eventLoop, int noWait) {
if (noWait)
@@ -389,7 +398,7 @@ void aeSetDontWait(aeEventLoop *eventLoop, int noWait) {
*
* Otherwise AE_OK is returned and the operation is successful. */
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) {
AE_ASSERT(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
int i;
if (setsize == eventLoop->setsize) return AE_OK;
@@ -427,14 +436,14 @@ extern "C" void aeDeleteEventLoop(aeEventLoop *eventLoop) {
}
extern "C" void aeStop(aeEventLoop *eventLoop) {
AE_ASSERT(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
eventLoop->stop = 1;
}
extern "C" int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData)
{
AE_ASSERT(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
@@ -463,12 +472,12 @@ void aeDeleteFileEventAsync(aeEventLoop *eventLoop, int fd, int mask)
cmd.mask = mask;
cmd.fLock = true;
auto cb = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
AE_ASSERT(cb == sizeof(cmd));
serverAssert(cb == sizeof(cmd));
}
extern "C" void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
{
AE_ASSERT(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
if (fd >= eventLoop->setsize) return;
aeFileEvent *fe = &eventLoop->events[fd];
if (fe->mask == AE_NONE) return;
@@ -526,7 +535,7 @@ extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long millise
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
AE_ASSERT(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
long long id = eventLoop->timeEventNextId++;
aeTimeEvent *te;
@@ -548,7 +557,7 @@ extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long millise
extern "C" int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
{
AE_ASSERT(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
aeTimeEvent *te = eventLoop->timeEventHead;
while(te) {
if (te->id == id) {
@@ -573,7 +582,7 @@ extern "C" int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
*/
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
{
AE_ASSERT(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
aeTimeEvent *te = eventLoop->timeEventHead;
aeTimeEvent *nearest = NULL;
@@ -589,7 +598,7 @@ static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
std::unique_lock<decltype(g_lock)> ulock(g_lock);
std::unique_lock<decltype(g_lock)> ulock(g_lock, std::defer_lock);
int processed = 0;
aeTimeEvent *te;
long long maxId;
@@ -634,8 +643,10 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
eventLoop->timeEventHead = te->next;
if (te->next)
te->next->prev = te->prev;
if (te->finalizerProc)
if (te->finalizerProc) {
if (!ulock.owns_lock()) ulock.lock();
te->finalizerProc(eventLoop, te->clientData);
}
zfree(te);
te = next;
continue;
@@ -654,6 +665,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
if (now_sec > te->when_sec ||
(now_sec == te->when_sec && now_ms >= te->when_ms))
{
if (!ulock.owns_lock()) ulock.lock();
int retval;
id = te->id;
@@ -746,7 +758,7 @@ extern "C" void ProcessEventCore(aeEventLoop *eventLoop, aeFileEvent *fe, int ma
* The function returns the number of events processed. */
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
AE_ASSERT(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
int processed = 0, numevents;
/* Nothing to do? return ASAP */
@@ -870,9 +882,9 @@ void aeMain(aeEventLoop *eventLoop) {
ulock.lock();
eventLoop->beforesleep(eventLoop);
}
AE_ASSERT(!aeThreadOwnsLock()); // we should have relinquished it after processing
serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
AE_ASSERT(!aeThreadOwnsLock()); // we should have relinquished it after processing
serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing
}
}
+1
View File
@@ -160,6 +160,7 @@ const char *aeGetApiName(void);
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep, int flags);
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep, int flags);
int aeGetSetSize(aeEventLoop *eventLoop);
aeEventLoop *aeGetCurrentEventLoop();
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
void aeSetDontWait(aeEventLoop *eventLoop, int noWait);
+1 -1
View File
@@ -9,7 +9,7 @@ public:
{
}
void arm(client *c, bool fIfNeeded = false) // if a client is passed, then the client is already locked
void arm(client *c = nullptr, bool fIfNeeded = false) // if a client is passed, then the client is already locked
{
if (m_fArmed)
return;
+1 -1
View File
@@ -642,7 +642,7 @@ sds catAppendOnlyExpireMemberAtCommand(sds buf, struct redisCommand *cmd, robj *
when += mstime();
robj *argvNew[4];
argvNew[0] = createStringObject("PEXPIREMEMBERAT",15);
argvNew[0] = shared.pexpirememberat;
argvNew[1] = argv[1];
argvNew[2] = argv[2];
argvNew[3] = createStringObjectFromLongLong(when);
+17 -10
View File
@@ -28,14 +28,21 @@
*/
const char *ascii_logo =
" \n"
" \n"
" KeyDB %s (%s/%d) %s bit\n"
" \n"
" Running in %s mode\n"
" Port: %d\n"
" PID: %ld\n"
" \n"
" %s\n"
" \n"
" \n"
" _ \n"
" _-(+)-_ \n"
" _-- / \\ --_ \n"
" _-- / \\ --_ KeyDB %s (%s/%d) %s bit \n"
" __-- / \\ --__ \n"
" (+) _ / \\ _ (+) Running in %s mode\n"
" | -- / \\ -- | Port: %d\n"
" | /--_ _ _--\\ | PID: %ld\n"
" | / -(+)- \\ | \n"
" | / | \\ | https://docs.keydb.dev \n"
" | / | \\ | \n"
" | / | \\ | \n"
" (+)_ -- -- -- | -- -- -- _(+) \n"
" --_ | _-- \n"
" --_ | _-- \n"
" -(+)- %s\n"
" \n";
+15 -8
View File
@@ -188,9 +188,9 @@ void replyToBlockedClientTimedOut(client *c) {
if (c->btype == BLOCKED_LIST ||
c->btype == BLOCKED_ZSET ||
c->btype == BLOCKED_STREAM) {
addReplyNullArrayAsync(c);
addReplyNullArray(c);
} else if (c->btype == BLOCKED_WAIT) {
addReplyLongLongAsync(c,replicationCountAcksByOffset(c->bpop.reploffset));
addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset));
} else if (c->btype == BLOCKED_MODULE) {
moduleBlockedClientTimedOut(c);
} else {
@@ -216,7 +216,7 @@ void disconnectAllBlockedClients(void) {
fastlock_lock(&c->lock);
if (c->flags & CLIENT_BLOCKED) {
addReplySdsAsync(c,sdsnew(
addReplySds(c,sdsnew(
"-UNBLOCKED force unblock from blocking operation, "
"instance state changed (master -> replica?)\r\n"));
unblockClient(c);
@@ -373,7 +373,7 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
/* If the group was not found, send an error
* to the consumer. */
if (!group) {
addReplyErrorAsync(receiver,
addReplyError(receiver,
"-NOGROUP the consumer group this client "
"was blocked on no longer exists");
unblockClient(receiver);
@@ -404,12 +404,12 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
* extracted from it. Wrapped in a single-item
* array, since we have just one key. */
if (receiver->resp == 2) {
addReplyArrayLenAsync(receiver,1);
addReplyArrayLenAsync(receiver,2);
addReplyArrayLen(receiver,1);
addReplyArrayLen(receiver,2);
} else {
addReplyMapLenAsync(receiver,1);
addReplyMapLen(receiver,1);
}
addReplyBulkAsync(receiver,rl->key);
addReplyBulk(receiver,rl->key);
streamPropInfo pi = {
rl->key,
@@ -672,6 +672,13 @@ void signalKeyAsReady(redisDb *db, robj *key) {
/* Key was already signaled? No need to queue it again. */
if (dictFind(db->ready_keys,key) != NULL) return;
if (key->getrefcount() == OBJ_STATIC_REFCOUNT) {
// Sometimes a key may be stack allocated, we'll need to dupe it
robj *newKey = createStringObject(szFromObj(key), sdslen(szFromObj(key)));
newKey->setrefcount(0); // Start with 0 but don't free
key = newKey;
}
/* Ok, we need to queue this key into g_pserver->ready_keys. */
rl = (readyList*)zmalloc(sizeof(*rl), MALLOC_SHARED);
rl->key = key;
+42
View File
@@ -5019,6 +5019,48 @@ void dumpCommand(client *c) {
return;
}
/* KEYDB.MVCCRESTORE key mvcc expire serialized-value */
void mvccrestoreCommand(client *c) {
long long mvcc, expire;
robj *key = c->argv[1], *obj = nullptr;
int type;
if (getLongLongFromObjectOrReply(c, c->argv[2], &mvcc, "Invalid MVCC Tstamp") != C_OK)
return;
if (getLongLongFromObjectOrReply(c, c->argv[3], &expire, "Invalid expire") != C_OK)
return;
/* Verify RDB version and data checksum unles the client is already a replica or master */
if (!(c->flags & (CLIENT_SLAVE | CLIENT_MASTER))) {
if (verifyDumpPayload((unsigned char*)ptrFromObj(c->argv[4]),sdslen(szFromObj(c->argv[4]))) == C_ERR)
{
addReplyError(c,"DUMP payload version or checksum are wrong");
return;
}
}
rio payload;
rioInitWithBuffer(&payload,szFromObj(c->argv[4]));
if (((type = rdbLoadObjectType(&payload)) == -1) ||
((obj = rdbLoadObject(type,&payload,szFromObj(key), OBJ_MVCC_INVALID)) == NULL))
{
addReplyError(c,"Bad data format");
return;
}
setMvccTstamp(obj, mvcc);
/* Create the key and set the TTL if any */
dbMerge(c->db,key,obj,true);
if (expire >= 0) {
setExpire(c,c->db,key,nullptr,expire);
}
signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",key,c->db->id);
addReply(c,shared.ok);
g_pserver->dirty++;
}
/* RESTORE key ttl serialized-value [REPLACE] */
void restoreCommand(client *c) {
long long ttl, lfu_freq = -1, lru_idle = -1, lru_clock = -1;
+32 -6
View File
@@ -641,7 +641,7 @@ void loadServerConfigFromString(char *config) {
return;
loaderr:
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (Redis %s) ***\n",
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (KeyDB %s) ***\n",
KEYDB_REAL_VERSION);
fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
fprintf(stderr, ">>> '%s'\n", lines[i]);
@@ -2273,17 +2273,42 @@ static int updateMaxclients(long long val, long long prev, const char **err) {
}
return 0;
}
for (int iel = 0; iel < MAX_EVENT_LOOPS; ++iel)
/* Change the SetSize for the current thread first. If any error, return the error message to the client,
* otherwise, continue to do the same for other threads */
if ((unsigned int) aeGetSetSize(aeGetCurrentEventLoop()) <
g_pserver->maxclients + CONFIG_FDSET_INCR)
{
if (aeResizeSetSize(aeGetCurrentEventLoop(),
g_pserver->maxclients + CONFIG_FDSET_INCR) == AE_ERR)
{
*err = "The event loop API used by Redis is not able to handle the specified number of clients";
return 0;
}
serverLog(LL_DEBUG,"Successfully changed the setsize for current thread %d", ielFromEventLoop(aeGetCurrentEventLoop()));
}
for (int iel = 0; iel < cserver.cthreads; ++iel)
{
if (g_pserver->rgthreadvar[iel].el == aeGetCurrentEventLoop()){
continue;
}
if ((unsigned int) aeGetSetSize(g_pserver->rgthreadvar[iel].el) <
g_pserver->maxclients + CONFIG_FDSET_INCR)
{
if (aeResizeSetSize(g_pserver->rgthreadvar[iel].el,
g_pserver->maxclients + CONFIG_FDSET_INCR) == AE_ERR)
{
*err = "The event loop API used by Redis is not able to handle the specified number of clients";
int res = aePostFunction(g_pserver->rgthreadvar[iel].el, [iel] {
if (aeResizeSetSize(g_pserver->rgthreadvar[iel].el, g_pserver->maxclients + CONFIG_FDSET_INCR) == AE_ERR) {
serverLog(LL_WARNING,"Failed to change the setsize for Thread %d", iel);
}
});
if (res != AE_OK){
static char msg[128];
sprintf(msg, "Failed to post the request to change setsize for Thread %d", iel);
*err = msg;
return 0;
}
serverLog(LL_DEBUG,"Successfully post the request to change the setsize for thread %d", iel);
}
}
}
@@ -2344,6 +2369,7 @@ standardConfig configs[] = {
createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, cserver.daemonize, 0, NULL, NULL),
createBoolConfig("lua-replicate-commands", NULL, MODIFIABLE_CONFIG, g_pserver->lua_always_replicate_commands, 1, NULL, NULL),
createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, g_pserver->always_show_logo, 0, NULL, NULL),
createBoolConfig("enable-motd", NULL, IMMUTABLE_CONFIG, cserver.enable_motd, 1, NULL, NULL),
createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, g_pserver->protected_mode, 1, NULL, NULL),
createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, g_pserver->rdb_compression, 1, NULL, NULL),
createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, g_pserver->rdb_del_sync_files, 0, NULL, NULL),
+8 -6
View File
@@ -202,13 +202,15 @@ robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) {
return o;
}
int dbAddCore(redisDb *db, robj *key, robj *val) {
int dbAddCore(redisDb *db, robj *key, robj *val, bool fUpdateMvcc) {
serverAssert(!val->FExpires());
sds copy = sdsdup(szFromObj(key));
int retval = dictAdd(db->pdict, copy, val);
uint64_t mvcc = getMvccTstamp();
setMvccTstamp(key, mvcc);
setMvccTstamp(val, mvcc);
if (fUpdateMvcc) {
setMvccTstamp(key, mvcc);
setMvccTstamp(val, mvcc);
}
if (retval == DICT_OK)
{
@@ -232,7 +234,7 @@ int dbAddCore(redisDb *db, robj *key, robj *val) {
* The program is aborted if the key already exists. */
void dbAdd(redisDb *db, robj *key, robj *val)
{
int retval = dbAddCore(db, key, val);
int retval = dbAddCore(db, key, val, true /* fUpdateMvcc */);
serverAssertWithInfo(NULL,key,retval == DICT_OK);
}
@@ -290,7 +292,7 @@ int dbMerge(redisDb *db, robj *key, robj *val, int fReplace)
{
dictEntry *de = dictFind(db->pdict, ptrFromObj(key));
if (de == nullptr)
return (dbAddCore(db, key, val) == DICT_OK);
return (dbAddCore(db, key, val, false /* fUpdateMvcc */) == DICT_OK);
robj *old = (robj*)dictGetVal(de);
if (mvccFromObj(old) <= mvccFromObj(val))
@@ -303,7 +305,7 @@ int dbMerge(redisDb *db, robj *key, robj *val, int fReplace)
}
else
{
return (dbAddCore(db, key, val) == DICT_OK);
return (dbAddCore(db, key, val, true /* fUpdateMvcc */) == DICT_OK);
}
}
+6 -1
View File
@@ -304,7 +304,7 @@ void pexpireMemberAtCommand(client *c)
* executed, where the time limit is a percentage of the REDIS_HZ period
* as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. */
void activeExpireCycle(int type) {
void activeExpireCycleCore(int type) {
/* This function has some global state in order to continue the work
* incrementally across calls. */
static unsigned int current_db = 0; /* Last DB tested. */
@@ -420,6 +420,11 @@ void activeExpireCycle(int type) {
(g_pserver->stat_expired_stale_perc*0.95);
}
void activeExpireCycle(int type)
{
runAndPropogateToReplicas(activeExpireCycleCore, type);
}
/*-----------------------------------------------------------------------------
* Expires of keys created in writable slaves
*
+2
View File
@@ -85,4 +85,6 @@ struct fastlock
#endif
};
#ifdef __cplusplus
static_assert(offsetof(struct fastlock, m_ticket) == 64, "ensure padding is correct");
#endif
+18 -18
View File
@@ -1358,7 +1358,7 @@ int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyLongLongAsync(c,ll);
addReplyLongLong(c,ll);
return REDISMODULE_OK;
}
@@ -1371,9 +1371,9 @@ int replyWithStatus(RedisModuleCtx *ctx, const char *msg, const char *prefix) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyProtoAsync(c,prefix,strlen(prefix));
addReplyProtoAsync(c,msg,strlen(msg));
addReplyProtoAsync(c,"\r\n",2);
addReplyProto(c,prefix,strlen(prefix));
addReplyProto(c,msg,strlen(msg));
addReplyProto(c,"\r\n",2);
return REDISMODULE_OK;
}
@@ -1426,10 +1426,10 @@ int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
ctx->postponed_arrays = (void**)zrealloc(ctx->postponed_arrays,sizeof(void*)*
(ctx->postponed_arrays_count+1), MALLOC_LOCAL);
ctx->postponed_arrays[ctx->postponed_arrays_count] =
addReplyDeferredLenAsync(c);
addReplyDeferredLen(c);
ctx->postponed_arrays_count++;
} else {
addReplyArrayLenAsync(c,len);
addReplyArrayLen(c,len);
}
return REDISMODULE_OK;
}
@@ -1444,7 +1444,7 @@ int RM_ReplyWithNullArray(RedisModuleCtx *ctx) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyNullArrayAsync(c);
addReplyNullArray(c);
return REDISMODULE_OK;
}
@@ -1457,7 +1457,7 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyAsync(c,shared.emptyarray);
addReply(c,shared.emptyarray);
return REDISMODULE_OK;
}
@@ -1502,7 +1502,7 @@ void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
return;
}
ctx->postponed_arrays_count--;
setDeferredArrayLenAsync(c,
setDeferredArrayLen(c,
ctx->postponed_arrays[ctx->postponed_arrays_count],
len);
if (ctx->postponed_arrays_count == 0) {
@@ -1520,7 +1520,7 @@ int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyBulkCBufferAsync(c,(char*)buf,len);
addReplyBulkCBuffer(c,(char*)buf,len);
return REDISMODULE_OK;
}
@@ -1534,7 +1534,7 @@ int RM_ReplyWithCString(RedisModuleCtx *ctx, const char *buf) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyBulkCStringAsync(c,(char*)buf);
addReplyBulkCString(c,(char*)buf);
return REDISMODULE_OK;
}
@@ -1547,7 +1547,7 @@ int RM_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyBulkAsync(c,str);
addReplyBulk(c,str);
return REDISMODULE_OK;
}
@@ -1560,7 +1560,7 @@ int RM_ReplyWithEmptyString(RedisModuleCtx *ctx) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyAsync(c,shared.emptybulk);
addReply(c,shared.emptybulk);
return REDISMODULE_OK;
}
@@ -1574,7 +1574,7 @@ int RM_ReplyWithVerbatimString(RedisModuleCtx *ctx, const char *buf, size_t len)
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyVerbatimAsync(c, buf, len, "txt");
addReplyVerbatim(c, buf, len, "txt");
return REDISMODULE_OK;
}
@@ -1587,7 +1587,7 @@ int RM_ReplyWithNull(RedisModuleCtx *ctx) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyNullAsync(c);
addReplyNull(c);
return REDISMODULE_OK;
}
@@ -1604,7 +1604,7 @@ int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) {
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
sds proto = sdsnewlen(reply->proto, reply->protolen);
addReplySdsAsync(c,proto);
addReplySds(c,proto);
return REDISMODULE_OK;
}
@@ -1620,7 +1620,7 @@ int RM_ReplyWithDouble(RedisModuleCtx *ctx, double d) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyDoubleAsync(c,d);
addReplyDouble(c,d);
return REDISMODULE_OK;
}
@@ -1638,7 +1638,7 @@ int RM_ReplyWithLongDouble(RedisModuleCtx *ctx, long double ld) {
AeLocker locker;
std::unique_lock<fastlock> lock(c->lock);
locker.arm(c);
addReplyHumanLongDoubleAsync(c, ld);
addReplyHumanLongDouble(c, ld);
return REDISMODULE_OK;
}
+7 -3
View File
@@ -71,12 +71,16 @@ static void setMOTDCache(const char *sz)
fclose(pf);
}
extern "C" char *fetchMOTD(int cache)
extern "C" char *fetchMOTD(int cache, int enable_motd)
{
sds str;
CURL *curl;
CURLcode res;
/* Do not try the CURL if the motd is disabled*/
if (!enable_motd) {
return NULL;
}
/* First try and get the string from the cache */
if (cache) {
str = fetchMOTDFromCache();
@@ -124,9 +128,9 @@ extern "C" char *fetchMOTD(int cache)
#else
extern "C" char *fetchMOTD(int /* cache */)
extern "C" char *fetchMOTD(int /* cache */, int /* enable_motd */)
{
return NULL;
}
#endif
#endif
+2 -3
View File
@@ -6,9 +6,8 @@ extern const char *motd_cache_file;
#ifdef __cplusplus
extern "C" {
#endif
char *fetchMOTD(int fCache);
char *fetchMOTD(int fCache, int enable_motd);
#ifdef __cplusplus
}
#endif
#endif
+188 -322
View File
@@ -40,8 +40,6 @@
#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);
/* Return the size consumed from the allocator, for the specified SDS string,
* including internal fragmentation. This function is used in order to compute
@@ -172,9 +170,7 @@ client *createClient(connection *conn, int iel) {
c->pubsub_patterns = listCreate();
c->peerid = NULL;
c->client_list_node = NULL;
c->bufAsync = NULL;
c->buflenAsync = 0;
c->bufposAsync = 0;
c->replyAsync = NULL;
c->client_tracking_redirection = 0;
c->casyncOpsPending = 0;
c->master_error = 0;
@@ -253,29 +249,31 @@ void clientInstallAsyncWriteHandler(client *c) {
* Typically gets called every time a reply is built, before adding more
* data to the clients output buffers. If the function returns C_ERR no
* data should be appended to the output buffers. */
int prepareClientToWrite(client *c, bool fAsync) {
fAsync = fAsync && !FCorrectThread(c); // Not async if we're on the right thread
serverAssert(FCorrectThread(c) || fAsync);
if (FCorrectThread(c)) {
int prepareClientToWrite(client *c) {
bool fAsync = !FCorrectThread(c); // Not async if we're on the right thread
if (!fAsync) {
serverAssert(c->conn == nullptr || c->lock.fOwnLock());
} else {
serverAssert(GlobalLocksAcquired());
}
if (c->flags & CLIENT_FORCE_REPLY) return C_OK; // FORCE REPLY means we're doing something else with the buffer.
auto flags = c->flags.load(std::memory_order_relaxed);
if (flags & CLIENT_FORCE_REPLY) return C_OK; // FORCE REPLY means we're doing something else with the buffer.
// do not install a write handler
/* If it's the Lua client we always return ok without installing any
* handler since there is no socket at all. */
if (c->flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK;
if (flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK;
/* CLIENT REPLY OFF / SKIP handling: don't send replies. */
if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;
if (flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;
/* Masters don't receive replies, unless CLIENT_MASTER_FORCE_REPLY flag
* is set. */
if ((c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
if ((flags & CLIENT_MASTER) &&
!(flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
if (!c->conn) return C_ERR; /* Fake client for AOF loading. */
@@ -292,22 +290,34 @@ int prepareClientToWrite(client *c, bool fAsync) {
* Low level functions to add more data to output buffers.
* -------------------------------------------------------------------------- */
int _addReplyToBuffer(client *c, const char *s, size_t len, bool fAsync) {
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return C_OK;
int _addReplyToBuffer(client *c, const char *s, size_t len) {
if (c->flags.load(std::memory_order_relaxed) & CLIENT_CLOSE_AFTER_REPLY) return C_OK;
fAsync = fAsync && !FCorrectThread(c); // Not async if we're on the right thread
bool fAsync = !FCorrectThread(c);
if (fAsync)
{
serverAssert(GlobalLocksAcquired());
if ((c->buflenAsync - c->bufposAsync) < (int)len)
if (c->replyAsync == nullptr || (c->replyAsync->size - c->replyAsync->used) < len)
{
int minsize = len + c->bufposAsync;
c->buflenAsync = std::max(minsize, c->buflenAsync*2 - c->buflenAsync);
c->bufAsync = (char*)zrealloc(c->bufAsync, c->buflenAsync, MALLOC_LOCAL);
c->buflenAsync = zmalloc_usable(c->bufAsync);
if (c->replyAsync == nullptr) {
size_t newsize = std::max(len, (size_t)PROTO_ASYNC_REPLY_CHUNK_BYTES);
clientReplyBlock *replyNew = (clientReplyBlock*)zmalloc(sizeof(clientReplyBlock) + newsize);
replyNew->size = zmalloc_usable(replyNew) - sizeof(clientReplyBlock);
replyNew->used = 0;
c->replyAsync = replyNew;
} else {
size_t newsize = std::max(c->replyAsync->used + len, c->replyAsync->size*2);
clientReplyBlock *replyNew = (clientReplyBlock*)zmalloc(sizeof(clientReplyBlock) + newsize);
replyNew->size = zmalloc_usable(replyNew) - sizeof(clientReplyBlock);
replyNew->used = c->replyAsync->used;
memcpy(replyNew->buf(), c->replyAsync->buf(), c->replyAsync->used);
zfree(c->replyAsync);
c->replyAsync = replyNew;
}
}
memcpy(c->bufAsync+c->bufposAsync,s,len);
c->bufposAsync += len;
memcpy(c->replyAsync->buf() + c->replyAsync->used,s,len);
c->replyAsync->used += len;
}
else
{
@@ -327,7 +337,7 @@ int _addReplyToBuffer(client *c, const char *s, size_t len, bool fAsync) {
}
void _addReplyProtoToList(client *c, const char *s, size_t len) {
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return;
if (c->flags.load(std::memory_order_relaxed) & CLIENT_CLOSE_AFTER_REPLY) return;
AssertCorrectThread(c);
listNode *ln = listLast(c->reply);
@@ -367,11 +377,12 @@ void _addReplyProtoToList(client *c, const char *s, size_t len) {
* Higher level functions to queue data on the client output buffer.
* The following functions are the ones that commands implementations will call.
* -------------------------------------------------------------------------- */
void addReplyCore(client *c, robj_roptr obj, bool fAsync) {
if (prepareClientToWrite(c, fAsync) != C_OK) return;
/* Add the object 'obj' string representation to the client output buffer. */
void addReply(client *c, robj_roptr obj) {
if (prepareClientToWrite(c) != C_OK) return;
if (sdsEncodedObject(obj)) {
if (_addReplyToBuffer(c,(const char*)ptrFromObj(obj),sdslen((sds)ptrFromObj(obj)),fAsync) != C_OK)
if (_addReplyToBuffer(c,(const char*)ptrFromObj(obj),sdslen((sds)ptrFromObj(obj))) != C_OK)
_addReplyProtoToList(c,(const char*)ptrFromObj(obj),sdslen((sds)ptrFromObj(obj)));
} else if (obj->encoding == OBJ_ENCODING_INT) {
/* For integer encoded strings we just convert it into a string
@@ -379,44 +390,26 @@ void addReplyCore(client *c, robj_roptr obj, bool fAsync) {
* to the output buffer. */
char buf[32];
size_t len = ll2string(buf,sizeof(buf),(long)ptrFromObj(obj));
if (_addReplyToBuffer(c,buf,len,fAsync) != C_OK)
if (_addReplyToBuffer(c,buf,len) != C_OK)
_addReplyProtoToList(c,buf,len);
} else {
serverPanic("Wrong obj->encoding in addReply()");
}
}
/* Add the object 'obj' string representation to the client output buffer. */
void addReply(client *c, robj_roptr obj)
{
addReplyCore(c, obj, false);
}
void addReplyAsync(client *c, robj_roptr obj)
{
addReplyCore(c, obj, true);
}
/* Add the SDS 's' string to the client output buffer, as a side effect
* the SDS string is freed. */
void addReplySdsCore(client *c, sds s, bool fAsync) {
if (prepareClientToWrite(c, fAsync) != C_OK) {
void addReplySds(client *c, sds s) {
if (prepareClientToWrite(c) != C_OK) {
/* The caller expects the sds to be free'd. */
sdsfree(s);
return;
}
if (_addReplyToBuffer(c,s,sdslen(s), fAsync) != C_OK)
if (_addReplyToBuffer(c,s,sdslen(s)) != C_OK)
_addReplyProtoToList(c,s,sdslen(s));
sdsfree(s);
}
void addReplySds(client *c, sds s) {
addReplySdsCore(c, s, false);
}
void addReplySdsAsync(client *c, sds s) {
addReplySdsCore(c, s, true);
}
/* This low level function just adds whatever protocol you send it to the
* client buffer, trying the static buffer initially, and using the string
* of objects if not possible.
@@ -425,18 +418,14 @@ void addReplySdsAsync(client *c, sds s) {
* if not needed. The object will only be created by calling
* _addReplyProtoToList() if we fail to extend the existing tail object
* in the list of objects. */
void addReplyProtoCore(client *c, const char *s, size_t len, bool fAsync) {
if (prepareClientToWrite(c, fAsync) != C_OK) return;
if (_addReplyToBuffer(c,s,len,fAsync) != C_OK)
void addReplyProto(client *c, const char *s, size_t len) {
if (prepareClientToWrite(c) != C_OK) return;
if (_addReplyToBuffer(c,s,len) != C_OK)
_addReplyProtoToList(c,s,len);
}
void addReplyProto(client *c, const char *s, size_t len) {
addReplyProtoCore(c, s, len, false);
}
void addReplyProtoAsync(client *c, const char *s, size_t len) {
addReplyProtoCore(c, s, len, true);
void addReplyProtoCString(client *c, const char *s) {
addReplyProto(c, s, strlen(s));
}
std::string escapeString(sds str)
@@ -476,12 +465,12 @@ std::string escapeString(sds str)
* code provided is used, otherwise the string "-ERR " for the generic
* error code is automatically added.
* Note that 's' must NOT end with \r\n. */
void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync) {
void addReplyErrorLength(client *c, const char *s, size_t len) {
/* If the string already starts with "-..." then the error code
* is provided by the caller. Otherwise we use "-ERR". */
if (!len || s[0] != '-') addReplyProtoCore(c,"-ERR ",5,fAsync);
addReplyProtoCore(c,s,len,fAsync);
addReplyProtoCore(c,"\r\n",2,fAsync);
if (!len || s[0] != '-') addReplyProto(c,"-ERR ",5);
addReplyProto(c,s,len);
addReplyProto(c,"\r\n",2);
}
/* Do some actions after an error reply was sent (Log if needed, updates stats, etc.) */
@@ -525,11 +514,6 @@ void afterErrorReply(client *c, const char *s, size_t len) {
}
}
void addReplyErrorLength(client *c, const char *s, size_t len)
{
addReplyErrorLengthCore(c, s, len, false);
}
/* The 'err' object is expected to start with -ERRORCODE and end with \r\n.
* Unlike addReplyErrorSds and others alike which rely on addReplyErrorLength. */
void addReplyErrorObject(client *c, robj *err) {
@@ -537,13 +521,8 @@ void addReplyErrorObject(client *c, robj *err) {
afterErrorReply(c, szFromObj(err), sdslen(szFromObj(err))-2); /* Ignore trailing \r\n */
}
/* See addReplyErrorLength for expectations from the input string. */
void addReplyError(client *c, const char *err) {
addReplyErrorLengthCore(c,err,strlen(err), false);
}
void addReplyErrorAsync(client *c, const char *err) {
addReplyErrorLengthCore(c, err, strlen(err), true);
addReplyErrorLength(c, err, strlen(err));
afterErrorReply(c,err,strlen(err));
}
@@ -619,21 +598,21 @@ void trimReplyUnusedTailSpace(client *c) {
/* Adds an empty object to the reply list that will contain the multi bulk
* length, which is not known when this function is called. */
void *addReplyDeferredLen(client *c) {
void *addReplyDeferredLenCore(client *c) {
/* Note that we install the write event here even if the object is not
* ready to be sent, since we are sure that before returning to the
* event loop setDeferredAggregateLen() will be called. */
if (prepareClientToWrite(c, false) != C_OK) return NULL;
if (prepareClientToWrite(c) != C_OK) return NULL;
trimReplyUnusedTailSpace(c);
listAddNodeTail(c->reply,NULL); /* NULL is our placeholder. */
return listLast(c->reply);
}
void *addReplyDeferredLenAsync(client *c) {
void *addReplyDeferredLen(client *c) {
if (FCorrectThread(c))
return addReplyDeferredLen(c);
return addReplyDeferredLenCore(c);
return (void*)((ssize_t)c->bufposAsync);
return (void*)((ssize_t)(c->replyAsync ? c->replyAsync->used : 0));
}
/* Populate the length object and try gluing it to the next chunk. */
@@ -689,25 +668,29 @@ void setDeferredAggregateLenAsync(client *c, void *node, long length, char prefi
char lenstr[128];
int lenstr_len = sprintf(lenstr, "%c%ld\r\n", prefix, length);
ssize_t idxSplice = (ssize_t)node;
serverAssert(idxSplice <= c->bufposAsync);
if (c->buflenAsync < (c->bufposAsync + lenstr_len))
size_t idxSplice = (size_t)node;
serverAssert(idxSplice <= c->replyAsync->used);
if (c->replyAsync->size < (c->replyAsync->used + lenstr_len))
{
c->buflenAsync = std::max((int)(c->bufposAsync+lenstr_len), c->buflenAsync*2 - c->buflenAsync);
c->bufAsync = (char*)zrealloc(c->bufAsync, c->buflenAsync, MALLOC_LOCAL);
int newsize = std::max(c->replyAsync->used + lenstr_len, c->replyAsync->size*2);
clientReplyBlock *replyNew = (clientReplyBlock*)zmalloc(sizeof(clientReplyBlock) + newsize);
replyNew->size = zmalloc_usable(replyNew) - sizeof(clientReplyBlock);
replyNew->used = c->replyAsync->used;
memcpy(replyNew->buf(), c->replyAsync->buf(), c->replyAsync->used);
zfree(c->replyAsync);
c->replyAsync = replyNew;
}
memmove(c->bufAsync + idxSplice + lenstr_len, c->bufAsync + idxSplice, c->bufposAsync - idxSplice);
memcpy(c->bufAsync + idxSplice, lenstr, lenstr_len);
c->bufposAsync += lenstr_len;
memmove(c->replyAsync->buf() + idxSplice + lenstr_len, c->replyAsync->buf() + idxSplice, c->replyAsync->used - idxSplice);
memcpy(c->replyAsync->buf() + idxSplice, lenstr, lenstr_len);
c->replyAsync->used += lenstr_len;
}
void setDeferredArrayLen(client *c, void *node, long length) {
setDeferredAggregateLen(c,node,length,'*');
}
void setDeferredArrayLenAsync(client *c, void *node, long length) {
setDeferredAggregateLenAsync(c, node, length, '*');
if (FCorrectThread(c))
setDeferredAggregateLen(c,node,length,'*');
else
setDeferredAggregateLenAsync(c, node, length, '*');
}
void setDeferredMapLen(client *c, void *node, long length) {
@@ -733,15 +716,15 @@ void setDeferredPushLen(client *c, void *node, long length) {
}
/* Add a double as a bulk reply */
void addReplyDoubleCore(client *c, double d, bool fAsync) {
void addReplyDouble(client *c, double 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) {
addReplyBulkCStringCore(c, d > 0 ? "inf" : "-inf", fAsync);
addReplyBulkCString(c, d > 0 ? "inf" : "-inf");
} else {
addReplyProtoCore(c, d > 0 ? ",inf\r\n" : ",-inf\r\n",
d > 0 ? 6 : 7, fAsync);
addReplyProto(c, d > 0 ? ",inf\r\n" : ",-inf\r\n",
d > 0 ? 6 : 7);
}
} else {
char dbuf[MAX_LONG_DOUBLE_CHARS+3],
@@ -750,52 +733,34 @@ void addReplyDoubleCore(client *c, double d, bool fAsync) {
if (c->resp == 2) {
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
addReplyProtoCore(c,sbuf,slen,fAsync);
addReplyProto(c,sbuf,slen);
} else {
dlen = snprintf(dbuf,sizeof(dbuf),",%.17g\r\n",d);
addReplyProtoCore(c,dbuf,dlen,fAsync);
addReplyProto(c,dbuf,dlen);
}
}
}
void addReplyDouble(client *c, double d) {
addReplyDoubleCore(c, d, false);
}
void addReplyDoubleAsync(client *c, double d) {
addReplyDoubleCore(c, d, true);
}
void addReplyBulkCore(client *c, robj_roptr obj, bool fAsync);
/* Add a long double as a bulk reply, but uses a human readable formatting
* of the double instead of exposing the crude behavior of doubles to the
* dear user. */
void addReplyHumanLongDoubleCore(client *c, long double d, bool fAsync) {
void addReplyHumanLongDouble(client *c, long double d) {
if (c->resp == 2) {
robj *o = createStringObjectFromLongDouble(d,1);
addReplyBulkCore(c,o,fAsync);
addReplyBulk(c,o);
decrRefCount(o);
} else {
char buf[MAX_LONG_DOUBLE_CHARS];
int len = ld2string(buf,sizeof(buf),d,LD_STR_HUMAN);
addReplyProtoCore(c,",",1,fAsync);
addReplyProtoCore(c,buf,len,fAsync);
addReplyProtoCore(c,"\r\n",2,fAsync);
addReplyProto(c,",",1);
addReplyProto(c,buf,len);
addReplyProto(c,"\r\n",2);
}
}
void addReplyHumanLongDouble(client *c, long double d) {
addReplyHumanLongDoubleCore(c, d, false);
}
void addReplyHumanLongDoubleAsync(client *c, long double d) {
addReplyHumanLongDoubleCore(c, d, true);
}
/* Add a long long as integer reply or bulk len / multi bulk count.
* Basically this is used to output <prefix><long long><crlf>. */
void addReplyLongLongWithPrefixCore(client *c, long long ll, char prefix, bool fAsync) {
void addReplyLongLongWithPrefix(client *c, long long ll, char prefix) {
char buf[128];
int len;
@@ -803,10 +768,10 @@ void addReplyLongLongWithPrefixCore(client *c, long long ll, char prefix, bool f
* so we have a few shared objects to use if the integer is small
* like it is most of the times. */
if (prefix == '*' && ll < OBJ_SHARED_BULKHDR_LEN && ll >= 0) {
addReplyCore(c,shared.mbulkhdr[ll], fAsync);
addReply(c,shared.mbulkhdr[ll]);
return;
} else if (prefix == '$' && ll < OBJ_SHARED_BULKHDR_LEN && ll >= 0) {
addReplyCore(c,shared.bulkhdr[ll], fAsync);
addReply(c,shared.bulkhdr[ll]);
return;
}
@@ -814,65 +779,33 @@ void addReplyLongLongWithPrefixCore(client *c, long long ll, char prefix, bool f
len = ll2string(buf+1,sizeof(buf)-1,ll);
buf[len+1] = '\r';
buf[len+2] = '\n';
addReplyProtoCore(c,buf,len+3, fAsync);
}
void addReplyLongLongWithPrefix(client *c, long long ll, char prefix) {
addReplyLongLongWithPrefixCore(c, ll, prefix, false);
}
void addReplyLongLongCore(client *c, long long ll, bool fAsync) {
if (ll == 0)
addReplyCore(c,shared.czero, fAsync);
else if (ll == 1)
addReplyCore(c,shared.cone, fAsync);
else
addReplyLongLongWithPrefixCore(c,ll,':', fAsync);
addReplyProto(c,buf,len+3);
}
void addReplyLongLong(client *c, long long ll) {
addReplyLongLongCore(c, ll, false);
}
void addReplyLongLongAsync(client *c, long long ll) {
addReplyLongLongCore(c, ll, true);
}
void addReplyAggregateLenCore(client *c, long length, int prefix, bool fAsync) {
if (prefix == '*' && length < OBJ_SHARED_BULKHDR_LEN)
addReplyCore(c,shared.mbulkhdr[length], fAsync);
if (ll == 0)
addReply(c,shared.czero);
else if (ll == 1)
addReply(c,shared.cone);
else
addReplyLongLongWithPrefixCore(c,length,prefix, fAsync);
addReplyLongLongWithPrefix(c,ll,':');
}
void addReplyAggregateLen(client *c, long length, int prefix) {
addReplyAggregateLenCore(c, length, prefix, false);
}
void addReplyArrayLenCore(client *c, long length, bool fAsync) {
addReplyAggregateLenCore(c,length,'*', fAsync);
if (prefix == '*' && length < OBJ_SHARED_BULKHDR_LEN)
addReply(c,shared.mbulkhdr[length]);
else
addReplyLongLongWithPrefix(c,length,prefix);
}
void addReplyArrayLen(client *c, long length) {
addReplyArrayLenCore(c, length, false);
}
void addReplyArrayLenAsync(client *c, long length) {
addReplyArrayLenCore(c, length, true);
}
void addReplyMapLenCore(client *c, long length, bool fAsync) {
int prefix = c->resp == 2 ? '*' : '%';
if (c->resp == 2) length *= 2;
addReplyAggregateLenCore(c,length,prefix,fAsync);
addReplyAggregateLen(c,length,'*');
}
void addReplyMapLen(client *c, long length) {
addReplyMapLenCore(c, length, false);
}
void addReplyMapLenAsync(client *c, long length) {
addReplyMapLenCore(c, length, true);
int prefix = c->resp == 2 ? '*' : '%';
if (c->resp == 2) length *= 2;
addReplyAggregateLen(c,length,prefix);
}
void addReplySetLen(client *c, long length) {
@@ -886,38 +819,19 @@ void addReplyAttributeLen(client *c, long length) {
addReplyAggregateLen(c,length,prefix);
}
void addReplyPushLenCore(client *c, long length, bool fAsync) {
int prefix = c->resp == 2 ? '*' : '>';
addReplyAggregateLenCore(c,length,prefix, fAsync);
}
void addReplyPushLen(client *c, long length) {
addReplyPushLenCore(c, length, false);
int prefix = c->resp == 2 ? '*' : '>';
addReplyAggregateLen(c,length,prefix);
}
void addReplyPushLenAsync(client *c, long length) {
addReplyPushLenCore(c, length, true);
}
void addReplyNullCore(client *c, bool fAsync) {
void addReplyNull(client *c) {
if (c->resp == 2) {
addReplyProtoCore(c,"$-1\r\n",5,fAsync);
addReplyProto(c,"$-1\r\n",5);
} else {
addReplyProtoCore(c,"_\r\n",3,fAsync);
addReplyProto(c,"_\r\n",3);
}
}
void addReplyNull(client *c, robj_roptr objOldProtocol) {
if (c->resp < 3 && objOldProtocol != nullptr)
addReply(c, objOldProtocol);
else
addReplyNullCore(c, false);
}
void addReplyNullAsync(client *c) {
addReplyNullCore(c, true);
}
void addReplyBool(client *c, int b) {
if (c->resp == 2) {
addReply(c, b ? shared.cone : shared.czero);
@@ -930,107 +844,58 @@ void addReplyBool(client *c, int b) {
* RESP2 had it, so API-wise we have this call, that will emit the correct
* RESP2 protocol, however for RESP3 the reply will always be just the
* Null type "_\r\n". */
void addReplyNullArrayCore(client *c, bool fAsync)
void addReplyNullArray(client *c)
{
if (c->resp == 2) {
addReplyProtoCore(c,"*-1\r\n",5,fAsync);
addReplyProto(c,"*-1\r\n",5);
} else {
addReplyProtoCore(c,"_\r\n",3,fAsync);
addReplyProto(c,"_\r\n",3);
}
}
void addReplyNullArray(client *c)
{
addReplyNullArrayCore(c, false);
}
void addReplyNullArrayAsync(client *c)
{
addReplyNullArrayCore(c, true);
}
/* Create the length prefix of a bulk reply, example: $2234 */
void addReplyBulkLenCore(client *c, robj_roptr obj, bool fAsync) {
void addReplyBulkLen(client *c, robj_roptr obj) {
size_t len = stringObjectLen(obj);
if (len < OBJ_SHARED_BULKHDR_LEN)
addReplyCore(c,shared.bulkhdr[len], fAsync);
addReply(c,shared.bulkhdr[len]);
else
addReplyLongLongWithPrefixCore(c,len,'$', fAsync);
}
void addReplyBulkLen(client *c, robj *obj)
{
addReplyBulkLenCore(c, obj, false);
addReplyLongLongWithPrefix(c,len,'$');
}
/* Add a Redis Object as a bulk reply */
void addReplyBulkCore(client *c, robj_roptr obj, bool fAsync) {
addReplyBulkLenCore(c,obj,fAsync);
addReplyCore(c,obj,fAsync);
addReplyCore(c,shared.crlf,fAsync);
}
void addReplyBulk(client *c, robj_roptr obj)
{
addReplyBulkCore(c, obj, false);
}
void addReplyBulkAsync(client *c, robj_roptr obj)
{
addReplyBulkCore(c, obj, true);
void addReplyBulk(client *c, robj_roptr obj) {
addReplyBulkLen(c,obj);
addReply(c,obj);
addReply(c,shared.crlf);
}
/* Add a C buffer as bulk reply */
void addReplyBulkCBufferCore(client *c, const void *p, size_t len, bool fAsync) {
addReplyLongLongWithPrefixCore(c,len,'$',fAsync);
addReplyProtoCore(c,(const char*)p,len,fAsync);
addReplyCore(c,shared.crlf,fAsync);
}
void addReplyBulkCBuffer(client *c, const void *p, size_t len) {
addReplyBulkCBufferCore(c, p, len, false);
}
void addReplyBulkCBufferAsync(client *c, const void *p, size_t len) {
addReplyBulkCBufferCore(c, p, len, true);
addReplyLongLongWithPrefix(c,len,'$');
addReplyProto(c,(const char*)p,len);
addReply(c,shared.crlf);
}
/* Add sds to reply (takes ownership of sds and frees it) */
void addReplyBulkSdsCore(client *c, sds s, bool fAsync) {
addReplyLongLongWithPrefixCore(c,sdslen(s),'$', fAsync);
addReplySdsCore(c,s,fAsync);
addReplyCore(c,shared.crlf,fAsync);
}
void addReplyBulkSds(client *c, sds s) {
addReplyBulkSdsCore(c, s, false);
}
void addReplyBulkSdsAsync(client *c, sds s) {
addReplyBulkSdsCore(c, s, true);
void addReplyBulkSds(client *c, sds s) {
addReplyLongLongWithPrefix(c,sdslen(s),'$');
addReplySds(c,s);
addReply(c,shared.crlf);
}
/* Add a C null term string as bulk reply */
void addReplyBulkCStringCore(client *c, const char *s, bool fAsync) {
void addReplyBulkCString(client *c, const char *s) {
if (s == NULL) {
if (c->resp < 3)
addReplyCore(c,shared.nullbulk, fAsync);
addReply(c,shared.nullbulk);
else
addReplyNullCore(c,fAsync);
addReplyNull(c);
} else {
addReplyBulkCBufferCore(c,s,strlen(s),fAsync);
addReplyBulkCBuffer(c,s,strlen(s));
}
}
void addReplyBulkCString(client *c, const char *s) {
addReplyBulkCStringCore(c, s, false);
}
void addReplyBulkCStringAsync(client *c, const char *s) {
addReplyBulkCStringCore(c, s, true);
}
/* Add a long long as a bulk reply */
void addReplyBulkLongLong(client *c, long long ll) {
char buf[64];
@@ -1049,9 +914,9 @@ void addReplyBulkLongLong(client *c, long long ll) {
* three first characters of the extension are used, and if the
* provided one is shorter than that, the remaining is filled with
* spaces. */
void addReplyVerbatimCore(client *c, const char *s, size_t len, const char *ext, bool fAsync) {
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext) {
if (c->resp == 2) {
addReplyBulkCBufferCore(c,s,len,fAsync);
addReplyBulkCBuffer(c,s,len);
} else {
char buf[32];
size_t preflen = snprintf(buf,sizeof(buf),"=%zu\r\nxxx:",len+4);
@@ -1063,20 +928,12 @@ void addReplyVerbatimCore(client *c, const char *s, size_t len, const char *ext,
p[i] = *ext++;
}
}
addReplyProtoCore(c,buf,preflen,fAsync);
addReplyProtoCore(c,s,len,fAsync);
addReplyProtoCore(c,"\r\n",2,fAsync);
addReplyProto(c,buf,preflen);
addReplyProto(c,s,len);
addReplyProto(c,"\r\n",2);
}
}
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext) {
addReplyVerbatimCore(c, s, len, ext, false);
}
void addReplyVerbatimAsync(client *c, const char *s, size_t len, const char *ext) {
addReplyVerbatimCore(c, s, len, ext, true);
}
/* Add an array of C strings as status replies with a heading.
* This function is typically invoked by from commands that support
* subcommands in response to the 'help' subcommand. The help array
@@ -1112,7 +969,7 @@ void addReplySubcommandSyntaxError(client *c) {
/* 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)
if (prepareClientToWrite(dst) != C_OK)
return;
addReplyProto(dst,src->buf, src->bufpos);
if (listLength(src->reply))
@@ -1640,7 +1497,7 @@ bool freeClient(client *c) {
/* Release other dynamically allocated client structure fields,
* and finally release the client structure itself. */
zfree(c->bufAsync);
zfree(c->replyAsync);
if (c->name) decrRefCount(c->name);
zfree(c->argv);
freeClientMultiState(c);
@@ -1685,7 +1542,7 @@ int freeClientsInAsyncFreeQueue(int iel) {
while((ln = listNext(&li)))
{
client *c = (client*)listNodeValue(ln);
if (c->iel == iel && !(c->flags & CLIENT_PROTECTED))
if (c->iel == iel && !(c->flags & CLIENT_PROTECTED) && !c->casyncOpsPending)
{
vecclientsFree.push_back(c);
listDelNode(g_pserver->clients_to_close, ln);
@@ -1846,29 +1703,25 @@ void ProcessPendingAsyncWrites()
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;
zfree(c->replyAsync);
c->replyAsync = nullptr;
c->fPendingAsyncWrite = FALSE;
continue;
}
// TODO: Append to end of reply block?
int size = c->replyAsync->used;
size_t size = c->bufposAsync;
clientReplyBlock *reply = (clientReplyBlock*)zmalloc(size + sizeof(clientReplyBlock), MALLOC_LOCAL);
/* take over the allocation's internal fragmentation */
reply->size = zmalloc_usable(reply) - sizeof(clientReplyBlock);
reply->used = c->bufposAsync;
memcpy(reply->buf(), c->bufAsync, c->bufposAsync);
listAddNodeTail(c->reply, reply);
c->reply_bytes += reply->size;
if (listLength(c->reply) == 0 && size <= (PROTO_REPLY_CHUNK_BYTES - c->bufpos)) {
memcpy(c->buf + c->bufpos, c->replyAsync->buf(), size);
c->bufpos += size;
} else {
c->reply_bytes += c->replyAsync->size;
listAddNodeTail(c->reply, c->replyAsync);
c->replyAsync = nullptr;
}
c->bufposAsync = 0;
c->buflenAsync = 0;
zfree(c->bufAsync);
c->bufAsync = nullptr;
zfree(c->replyAsync);
c->replyAsync = nullptr;
c->fPendingAsyncWrite = FALSE;
// Now install the write event handler
@@ -1896,16 +1749,17 @@ void ProcessPendingAsyncWrites()
if (FCorrectThread(c))
{
prepareClientToWrite(c, false); // queue an event
prepareClientToWrite(c); // queue an event
}
else
{
if (!c->fPendingAsyncWriteHandler) {
c->fPendingAsyncWriteHandler = true;
bool expected = false;
if (c->fPendingAsyncWriteHandler.compare_exchange_strong(expected, true)) {
bool fResult = c->postFunction([](client *c) {
c->fPendingAsyncWriteHandler = false;
connSetWriteHandler(c->conn, sendReplyToClient, true);
});
clientInstallWriteHandler(c);
handleClientsWithPendingWrites(c->iel, g_pserver->aof_state);
}, false);
if (!fResult)
c->fPendingAsyncWriteHandler = false; // if we failed to set the handler then prevent this from never being reset
@@ -1920,10 +1774,16 @@ void ProcessPendingAsyncWrites()
* get it called, and so forth. */
int handleClientsWithPendingWrites(int iel, int aof_state) {
std::unique_lock<fastlock> lockf(g_pserver->rgthreadvar[iel].lockPendingWrite);
auto &vec = g_pserver->rgthreadvar[iel].clients_pending_write;
int processed = (int)vec.size();
int processed = 0;
serverAssert(iel == (serverTL - g_pserver->rgthreadvar));
if (listLength(serverTL->clients_pending_asyncwrite))
{
AeLocker locker;
locker.arm(nullptr);
ProcessPendingAsyncWrites();
}
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,
@@ -1936,16 +1796,17 @@ int handleClientsWithPendingWrites(int iel, int aof_state) {
ae_flags |= AE_BARRIER;
}
while(!vec.empty()) {
client *c = vec.back();
auto vec = std::move(g_pserver->rgthreadvar[iel].clients_pending_write);
processed += (int)vec.size();
for (client *c : vec) {
AssertCorrectThread(c);
c->flags &= ~CLIENT_PENDING_WRITE;
vec.pop_back();
uint64_t flags = c->flags.fetch_and(~CLIENT_PENDING_WRITE, std::memory_order_relaxed);
/* If a client is protected, don't do anything,
* that may trigger write error or recreate handler. */
if (c->flags & CLIENT_PROTECTED) continue;
* that may trigger write error or recreate handler. */
if (flags & CLIENT_PROTECTED) continue;
std::unique_lock<decltype(c->lock)> lock(c->lock);
@@ -1964,20 +1825,13 @@ int handleClientsWithPendingWrites(int iel, int aof_state) {
}
/* If after the synchronous writes above we still have data to
* output to the client, we need to install the writable handler. */
* output to the client, we need to install the writable handler. */
if (clientHasPendingReplies(c)) {
if (connSetWriteHandlerWithBarrier(c->conn, sendReplyToClient, ae_flags, true) == C_ERR)
freeClientAsync(c);
}
}
if (listLength(serverTL->clients_pending_asyncwrite))
{
AeLocker locker;
locker.arm(nullptr);
ProcessPendingAsyncWrites();
}
return processed;
}
@@ -2333,8 +2187,7 @@ void commandProcessed(client *c, int flags) {
if (applied) {
if (!g_pserver->fActiveReplica && (flags & CMD_CALL_PROPAGATE))
{
replicationFeedSlavesFromMasterStream(g_pserver->slaves,
c->pending_querybuf, applied);
replicationFeedSlavesFromMasterStream(c->pending_querybuf, applied);
}
sdsrange(c->pending_querybuf,applied,-1);
}
@@ -2886,7 +2739,7 @@ NULL
if (target && target->flags & CLIENT_BLOCKED) {
std::unique_lock<fastlock> ul(target->lock);
if (unblock_error)
addReplyErrorAsync(target,
addReplyError(target,
"-UNBLOCKED client unblocked via CLIENT UNBLOCK");
else
replyToBlockedClientTimedOut(target);
@@ -3241,7 +3094,7 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
* enforcing the client output length limits. */
unsigned long getClientOutputBufferMemoryUsage(client *c) {
unsigned long list_item_size = sizeof(listNode) + sizeof(clientReplyBlock);
return c->reply_bytes + (list_item_size*listLength(c->reply)) + c->buflenAsync;
return c->reply_bytes + (list_item_size*listLength(c->reply)) + (c->replyAsync ? c->replyAsync->size : 0);
}
/* Get the class of a client, used in order to enforce limits to different
@@ -3484,6 +3337,13 @@ void processEventsWhileBlocked(int iel) {
}
}
/* Since we're about to release our lock we need to flush the repl backlog queue */
bool fReplBacklog = g_pserver->repl_batch_offStart >= 0;
if (fReplBacklog) {
flushReplBacklogToClients();
g_pserver->repl_batch_idxStart = -1;
g_pserver->repl_batch_offStart = -1;
}
aeReleaseLock();
serverAssert(!GlobalLocksAcquired());
@@ -3516,6 +3376,12 @@ void processEventsWhileBlocked(int iel) {
locker.arm(nullptr);
locker.release();
// Restore it so the calling code is not confused
if (fReplBacklog) {
g_pserver->repl_batch_idxStart = g_pserver->repl_backlog_idx;
g_pserver->repl_batch_offStart = g_pserver->master_repl_offset;
}
for (client *c : vecclients)
c->lock.lock();
+7 -3
View File
@@ -30,6 +30,7 @@
#include "server.h"
#include "cron.h"
#include "t_nhash.h"
#include <math.h>
#include <ctype.h>
#include <mutex>
@@ -309,8 +310,10 @@ void freeStringObject(robj_roptr o) {
void freeListObject(robj_roptr o) {
if (o->encoding == OBJ_ENCODING_QUICKLIST) {
quicklistRelease((quicklist*)ptrFromObj(o));
} else if (o->encoding == OBJ_ENCODING_ZIPLIST) {
zfree(ptrFromObj(o));
} else {
serverPanic("Unknown list encoding type");
serverPanic("Unknown list encoding type: %d", o->encoding);
}
}
@@ -395,6 +398,7 @@ void decrRefCount(robj_roptr o) {
case OBJ_MODULE: freeModuleObject(o); break;
case OBJ_STREAM: freeStreamObject(o); break;
case OBJ_CRON: freeCronObject(o); break;
case OBJ_NESTEDHASH: freeNestedHashObject(o); break;
default: serverPanic("Unknown object type"); break;
}
if (g_pserver->fActiveReplica) {
@@ -433,7 +437,7 @@ robj *resetRefCount(robj *obj) {
int checkType(client *c, robj_roptr o, int type) {
if (o->type != type) {
addReplyAsync(c,shared.wrongtypeerr);
addReply(c,shared.wrongtypeerr);
return 1;
}
return 0;
@@ -1526,7 +1530,7 @@ void *allocPtrFromObj(robj_roptr o) {
}
robj *objFromAllocPtr(void *pv) {
if (g_pserver->fActiveReplica) {
if (pv != nullptr && g_pserver->fActiveReplica) {
return reinterpret_cast<robj*>(reinterpret_cast<redisObjectExtended*>(pv)+1);
}
return reinterpret_cast<robj*>(pv);
+11 -11
View File
@@ -43,12 +43,12 @@ int clientSubscriptionsCount(client *c);
* addReply*() API family. */
void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
if (c->resp == 2)
addReplyAsync(c,shared.mbulkhdr[3]);
addReply(c,shared.mbulkhdr[3]);
else
addReplyPushLenAsync(c,3);
addReplyAsync(c,shared.messagebulk);
addReplyBulkAsync(c,channel);
if (msg) addReplyBulkAsync(c,msg);
addReplyPushLen(c,3);
addReply(c,shared.messagebulk);
addReplyBulk(c,channel);
if (msg) addReplyBulk(c,msg);
}
/* Send a pubsub message of type "pmessage" to the client. The difference
@@ -56,13 +56,13 @@ void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
* this message format also includes the pattern that matched the message. */
void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
if (c->resp == 2)
addReplyAsync(c,shared.mbulkhdr[4]);
addReply(c,shared.mbulkhdr[4]);
else
addReplyPushLenAsync(c,4);
addReplyAsync(c,shared.pmessagebulk);
addReplyBulkAsync(c,pat);
addReplyBulkAsync(c,channel);
addReplyBulkAsync(c,msg);
addReplyPushLen(c,4);
addReply(c,shared.pmessagebulk);
addReplyBulk(c,pat);
addReplyBulk(c,channel);
addReplyBulk(c,msg);
}
/* Send the pubsub subscription notification to the client. */
+5 -1
View File
@@ -2353,7 +2353,9 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
else {
redisObjectStack keyobj;
initStaticStringObject(keyobj,key);
setExpire(NULL, db, &keyobj, subexpireKey, strtoll(szFromObj(auxval), nullptr, 10));
long long expireT = strtoll(szFromObj(auxval), nullptr, 10);
setExpire(NULL, db, &keyobj, subexpireKey, expireT);
replicateSubkeyExpire(db, &keyobj, subexpireKey, expireT);
decrRefCount(subexpireKey);
subexpireKey = nullptr;
}
@@ -2475,6 +2477,8 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
/* call key space notification on key loaded for modules only */
moduleNotifyKeyspaceEvent(NOTIFY_LOADED, "loaded", &keyobj, db->id);
replicationNotifyLoadedKey(db, &keyobj, val, expiretime);
}
else
{
+14 -7
View File
@@ -1878,14 +1878,15 @@ static void repl(void) {
exit(0);
}
static int noninteractive(int argc, char **argv) {
static int noninteractive(int argc, char ***argv) {
int retval = 0;
if (config.stdinarg) {
argv = zrealloc(argv, (argc+1)*sizeof(char*), MALLOC_LOCAL);
argv[argc] = readArgFromStdin();
retval = issueCommand(argc+1, argv);
*argv = zrealloc(*argv, (argc+1)*sizeof(char*), MALLOC_LOCAL);
(*argv)[argc] = readArgFromStdin();
retval = issueCommand(argc+1, *argv);
sdsfree((*argv)[argc]);
} else {
retval = issueCommand(argc, argv);
retval = issueCommand(argc, *argv);
}
return retval;
}
@@ -7112,7 +7113,8 @@ int main(int argc, char **argv) {
if (argc == 0 && !config.eval) {
/* Show the message of the day if we are interactive */
if (config.output == OUTPUT_STANDARD && !config.disable_motd) {
char *szMotd = fetchMOTD(1 /* cache */);
/*enable_motd=1 will retrieve the message of today using CURL*/
char *szMotd = fetchMOTD(1 /* cache */, 1 /* enable_motd */);
if (szMotd != NULL) {
printf("Message of the day:\n %s\n", szMotd);
sdsfree(szMotd);
@@ -7134,6 +7136,11 @@ int main(int argc, char **argv) {
if (config.eval) {
return evalMode(argc,argv);
} else {
return noninteractive(argc,convertToSds(argc,argv));
sds *sdsArgs = convertToSds(argc,argv);
int rval = noninteractive(argc,&sdsArgs);
for (int i = 0; i < argc; ++i)
sdsfree(sdsArgs[i]);
zfree(sdsArgs);
return rval;
}
}
+227 -149
View File
@@ -196,6 +196,10 @@ void createReplicationBacklog(void) {
* byte we have is the next byte that will be generated for the
* replication stream. */
g_pserver->repl_backlog_off = g_pserver->master_repl_offset+1;
/* Allow transmission to clients */
g_pserver->repl_batch_idxStart = 0;
g_pserver->repl_batch_offStart = g_pserver->master_repl_offset;
}
/* This function is called when the user modifies the replication backlog
@@ -209,20 +213,44 @@ void resizeReplicationBacklog(long long newsize) {
newsize = CONFIG_REPL_BACKLOG_MIN_SIZE;
if (g_pserver->repl_backlog_size == newsize) return;
g_pserver->repl_backlog_size = newsize;
if (g_pserver->repl_backlog != NULL) {
/* What we actually do is to flush the old buffer and realloc a new
* empty one. It will refill with new data incrementally.
* The reason is that copying a few gigabytes adds latency and even
* worse often we need to alloc additional space before freeing the
* old buffer. */
zfree(g_pserver->repl_backlog);
g_pserver->repl_backlog = (char*)zmalloc(g_pserver->repl_backlog_size, MALLOC_LOCAL);
g_pserver->repl_backlog_histlen = 0;
g_pserver->repl_backlog_idx = 0;
/* Next byte we have is... the next since the buffer is empty. */
g_pserver->repl_backlog_off = g_pserver->master_repl_offset+1;
if (g_pserver->repl_batch_idxStart >= 0) {
// We need to keep critical data so we can't shrink less than the hot data in the buffer
newsize = std::max(newsize, g_pserver->master_repl_offset - g_pserver->repl_batch_offStart);
char *backlog = (char*)zmalloc(newsize);
g_pserver->repl_backlog_histlen = g_pserver->master_repl_offset - g_pserver->repl_batch_offStart;
if (g_pserver->repl_backlog_idx >= g_pserver->repl_batch_idxStart) {
auto cbActiveBacklog = g_pserver->repl_backlog_idx - g_pserver->repl_batch_idxStart;
memcpy(backlog, g_pserver->repl_backlog + g_pserver->repl_batch_idxStart, cbActiveBacklog);
serverAssert(g_pserver->repl_backlog_histlen == cbActiveBacklog);
} else {
auto cbPhase1 = g_pserver->repl_backlog_size - g_pserver->repl_batch_idxStart;
memcpy(backlog, g_pserver->repl_backlog + g_pserver->repl_batch_idxStart, cbPhase1);
memcpy(backlog + cbPhase1, g_pserver->repl_backlog, g_pserver->repl_backlog_idx);
auto cbActiveBacklog = cbPhase1 + g_pserver->repl_backlog_idx;
serverAssert(g_pserver->repl_backlog_histlen == cbActiveBacklog);
}
zfree(g_pserver->repl_backlog);
g_pserver->repl_backlog = backlog;
g_pserver->repl_backlog_idx = g_pserver->repl_backlog_histlen;
g_pserver->repl_batch_idxStart = 0;
} else {
zfree(g_pserver->repl_backlog);
g_pserver->repl_backlog = (char*)zmalloc(newsize);
g_pserver->repl_backlog_histlen = 0;
g_pserver->repl_backlog_idx = 0;
/* Next byte we have is... the next since the buffer is empty. */
g_pserver->repl_backlog_off = g_pserver->master_repl_offset+1;
}
}
g_pserver->repl_backlog_size = newsize;
}
void freeReplicationBacklog(void) {
@@ -247,6 +275,21 @@ void feedReplicationBacklog(const void *ptr, size_t len) {
serverAssert(GlobalLocksAcquired());
const unsigned char *p = (const unsigned char*)ptr;
if (g_pserver->repl_batch_idxStart >= 0) {
long long minimumsize = g_pserver->master_repl_offset + len - g_pserver->repl_batch_offStart+1;
if (minimumsize > g_pserver->repl_backlog_size) {
flushReplBacklogToClients();
minimumsize = g_pserver->master_repl_offset + len - g_pserver->repl_batch_offStart+1;
if (minimumsize > g_pserver->repl_backlog_size) {
// This is an emergency overflow, we better resize to fit
long long newsize = std::max(g_pserver->repl_backlog_size*2, minimumsize);
serverLog(LL_WARNING, "Replication backlog is too small, resizing to: %lld", newsize);
resizeReplicationBacklog(newsize);
}
}
}
g_pserver->master_repl_offset += len;
/* This is a circular buffer, so write as much data we can at every
@@ -315,7 +358,7 @@ void replicationFeedSlave(client *replica, int dictid, robj **argv, int argc, bo
if (g_pserver->repl_backlog && fSendRaw) feedReplicationBacklogWithObject(selectcmd);
/* Send it to slaves */
addReplyAsync(replica,selectcmd);
addReply(replica,selectcmd);
if (dictid < 0 || dictid >= PROTO_SHARED_SELECT_CMDS)
decrRefCount(selectcmd);
@@ -329,18 +372,18 @@ void replicationFeedSlave(client *replica, int dictid, robj **argv, int argc, bo
if (fSendRaw)
{
/* Add the multi bulk length. */
addReplyArrayLenAsync(replica,argc);
addReplyArrayLen(replica,argc);
/* Finally any additional argument that was not stored inside the
* static buffer if any (from j to argc). */
for (int j = 0; j < argc; j++)
addReplyBulkAsync(replica,argv[j]);
addReplyBulk(replica,argv[j]);
}
else
{
struct redisCommand *cmd = lookupCommand(szFromObj(argv[0]));
sds buf = catCommandForAofAndActiveReplication(sdsempty(), cmd, argv, argc);
addReplyProtoAsync(replica, buf, sdslen(buf));
addReplyProto(replica, buf, sdslen(buf));
sdsfree(buf);
}
}
@@ -370,12 +413,10 @@ static int writeProtoNum(char *dst, const size_t cchdst, long long num)
* the commands received by our clients in order to create the replication
* stream. Instead if the instance is a replica and has sub-slaves attached,
* we use replicationFeedSlavesFromMaster() */
void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
listNode *ln, *lnReply;
listIter li, liReply;
int j, len;
void replicationFeedSlavesCore(list *slaves, int dictid, robj **argv, int argc) {
int j;
serverAssert(GlobalLocksAcquired());
static client *fake = nullptr;
serverAssert(g_pserver->repl_batch_offStart >= 0);
if (dictid < 0)
dictid = 0; // this can happen if we send a PING before any real operation
@@ -394,58 +435,34 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
/* We can't have slaves attached and no backlog. */
serverAssert(!(listLength(slaves) != 0 && g_pserver->repl_backlog == NULL));
if (fake == nullptr)
{
fake = createClient(nullptr, serverTL - g_pserver->rgthreadvar);
fake->flags |= CLIENT_FORCE_REPLY;
}
bool fSendRaw = !g_pserver->fActiveReplica;
replicationFeedSlave(fake, dictid, argv, argc, fSendRaw); // Note: updates the repl log, keep above the repl update code below
/* Send SELECT command to every replica if needed. */
if (g_pserver->replicaseldb != dictid) {
char llstr[LONG_STR_SIZE];
robj *selectcmd;
long long cchbuf = fake->bufpos;
listRewind(fake->reply, &liReply);
while ((lnReply = listNext(&liReply)))
{
clientReplyBlock* reply = (clientReplyBlock*)listNodeValue(lnReply);
cchbuf += reply->used;
/* For a few DBs we have pre-computed SELECT command. */
if (dictid >= 0 && dictid < PROTO_SHARED_SELECT_CMDS) {
selectcmd = shared.select[dictid];
} else {
int dictid_len;
dictid_len = ll2string(llstr,sizeof(llstr),dictid);
selectcmd = createObject(OBJ_STRING,
sdscatprintf(sdsempty(),
"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
dictid_len, llstr));
}
/* Add the SELECT command into the backlog. */
/* 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);
if (dictid < 0 || dictid >= PROTO_SHARED_SELECT_CMDS)
decrRefCount(selectcmd);
}
serverAssert(argc > 0);
serverAssert(cchbuf > 0);
// The code below used to be: snprintf(proto, sizeof(proto), "*5\r\n$7\r\nRREPLAY\r\n$%d\r\n%s\r\n$%lld\r\n", (int)strlen(uuid), uuid, cchbuf);
// but that was much too slow
static const char *protoRREPLAY = "*5\r\n$7\r\nRREPLAY\r\n$36\r\n00000000-0000-0000-0000-000000000000\r\n$";
char proto[1024];
int cchProto = 0;
if (!fSendRaw)
{
char uuid[37];
uuid_unparse(cserver.uuid, uuid);
cchProto = strlen(protoRREPLAY);
memcpy(proto, protoRREPLAY, strlen(protoRREPLAY));
memcpy(proto + 22, uuid, 36); // Note UUID_STR_LEN includes the \0 trailing byte which we don't want
cchProto += ll2string(proto + cchProto, sizeof(proto)-cchProto, cchbuf);
memcpy(proto + cchProto, "\r\n", 3);
cchProto += 2;
}
long long master_repl_offset_start = g_pserver->master_repl_offset;
char szDbNum[128];
int cchDbNum = 0;
if (!fSendRaw)
cchDbNum = writeProtoNum(szDbNum, sizeof(szDbNum), dictid);
char szMvcc[128];
int cchMvcc = 0;
incrementMvccTstamp(); // Always increment MVCC tstamp so we're consistent with active and normal replication
if (!fSendRaw)
cchMvcc = writeProtoNum(szMvcc, sizeof(szMvcc), getMvccTstamp());
g_pserver->replicaseldb = dictid;
/* Write the command to the replication backlog if any. */
if (g_pserver->repl_backlog)
@@ -456,10 +473,11 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
/* 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);
int multilen = ll2string(aux+1,sizeof(aux)-1,argc);
aux[multilen+1] = '\r';
aux[multilen+2] = '\n';
feedReplicationBacklog(aux,multilen+3);
for (j = 0; j < argc; j++) {
long objlen = stringObjectLen(argv[j]);
@@ -468,7 +486,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
* 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);
int len = ll2string(aux+1,sizeof(aux)-1,objlen);
aux[len+1] = '\r';
aux[len+2] = '\n';
feedReplicationBacklog(aux,len+3);
@@ -478,67 +496,57 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
}
else
{
feedReplicationBacklog(proto, cchProto);
feedReplicationBacklog(fake->buf, fake->bufpos);
listRewind(fake->reply, &liReply);
while ((lnReply = listNext(&liReply)))
char szDbNum[128];
int cchDbNum = 0;
if (!fSendRaw)
cchDbNum = writeProtoNum(szDbNum, sizeof(szDbNum), dictid);
char szMvcc[128];
int cchMvcc = 0;
incrementMvccTstamp(); // Always increment MVCC tstamp so we're consistent with active and normal replication
if (!fSendRaw)
cchMvcc = writeProtoNum(szMvcc, sizeof(szMvcc), getMvccTstamp());
//size_t cchlen = multilen+3;
struct redisCommand *cmd = lookupCommand(szFromObj(argv[0]));
sds buf = catCommandForAofAndActiveReplication(sdsempty(), cmd, argv, argc);
size_t cchlen = sdslen(buf);
// The code below used to be: snprintf(proto, sizeof(proto), "*5\r\n$7\r\nRREPLAY\r\n$%d\r\n%s\r\n$%lld\r\n", (int)strlen(uuid), uuid, cchbuf);
// but that was much too slow
static const char *protoRREPLAY = "*5\r\n$7\r\nRREPLAY\r\n$36\r\n00000000-0000-0000-0000-000000000000\r\n$";
char proto[1024];
int cchProto = 0;
if (!fSendRaw)
{
clientReplyBlock* reply = (clientReplyBlock*)listNodeValue(lnReply);
feedReplicationBacklog(reply->buf(), reply->used);
char uuid[37];
uuid_unparse(cserver.uuid, uuid);
cchProto = strlen(protoRREPLAY);
memcpy(proto, protoRREPLAY, strlen(protoRREPLAY));
memcpy(proto + 22, uuid, 36); // Note UUID_STR_LEN includes the \0 trailing byte which we don't want
cchProto += ll2string(proto + cchProto, sizeof(proto)-cchProto, cchlen);
memcpy(proto + cchProto, "\r\n", 3);
cchProto += 2;
}
feedReplicationBacklog(proto, cchProto);
feedReplicationBacklog(buf, sdslen(buf));
const char *crlf = "\r\n";
feedReplicationBacklog(crlf, 2);
feedReplicationBacklog(szDbNum, cchDbNum);
feedReplicationBacklog(szMvcc, cchMvcc);
sdsfree(buf);
}
}
}
/* Write the command to every replica. */
listRewind(slaves,&li);
while((ln = listNext(&li))) {
client *replica = (client*)ln->value;
/* Don't feed slaves that are still waiting for BGSAVE to start */
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
if (replica->flags & CLIENT_CLOSE_ASAP) continue;
std::unique_lock<decltype(replica->lock)> lock(replica->lock, std::defer_lock);
// When writing to clients on other threads the global lock is sufficient provided we only use AddReply*Async()
if (FCorrectThread(replica))
lock.lock();
if (serverTL->current_client && FSameHost(serverTL->current_client, replica))
{
replica->reploff_skipped += g_pserver->master_repl_offset - master_repl_offset_start;
continue;
}
/* Feed slaves that are waiting for the initial SYNC (so these commands
* are queued in the output buffer until the initial SYNC completes),
* or are already in sync with the master. */
if (!fSendRaw)
addReplyProtoAsync(replica, proto, cchProto);
addReplyProtoAsync(replica,fake->buf,fake->bufpos);
listRewind(fake->reply, &liReply);
while ((lnReply = listNext(&liReply)))
{
clientReplyBlock* reply = (clientReplyBlock*)listNodeValue(lnReply);
addReplyProtoAsync(replica, reply->buf(), reply->used);
}
if (!fSendRaw)
{
addReplyAsync(replica,shared.crlf);
addReplyProtoAsync(replica, szDbNum, cchDbNum);
addReplyProtoAsync(replica, szMvcc, cchMvcc);
}
}
// Cleanup cached fake client output buffers
fake->bufpos = 0;
fake->sentlen = 0;
fake->reply_bytes = 0;
listEmpty(fake->reply);
void replicationFeedSlaves(list *replicas, int dictid, robj **argv, int argc) {
runAndPropogateToReplicas(replicationFeedSlavesCore, replicas, dictid, argv, argc);
}
/* This is a debugging function that gets called when we detect something
@@ -578,10 +586,7 @@ void showLatestBacklog(void) {
/* This function is used in order to proxy what we receive from our master
* to our sub-slaves. */
#include <ctype.h>
void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t buflen) {
listNode *ln;
listIter li;
void replicationFeedSlavesFromMasterStream(char *buf, size_t buflen) {
/* Debugging: this is handy to see the stream sent from master
* to slaves. Disabled with if(0). */
if (0) {
@@ -593,23 +598,6 @@ void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t bufle
}
if (g_pserver->repl_backlog) feedReplicationBacklog(buf,buflen);
listRewind(slaves,&li);
while((ln = listNext(&li))) {
client *replica = (client*)ln->value;
std::unique_lock<decltype(replica->lock)> ulock(replica->lock, std::defer_lock);
if (FCorrectThread(replica))
ulock.lock();
if (FMasterHost(replica))
continue; // Active Active case, don't feed back
/* Don't feed slaves that are still waiting for BGSAVE to start */
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
addReplyProtoAsync(replica,buf,buflen);
}
if (listLength(slaves))
ProcessPendingAsyncWrites(); // flush them to their respective threads
}
void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv, int argc) {
@@ -651,7 +639,7 @@ void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv,
// When writing to clients on other threads the global lock is sufficient provided we only use AddReply*Async()
if (FCorrectThread(c))
lock.lock();
addReplyAsync(monitor,cmdobj);
addReply(monitor,cmdobj);
}
decrRefCount(cmdobj);
}
@@ -2269,6 +2257,8 @@ void readSyncBulkPayload(connection *conn) {
* we are starting a new history. */
memcpy(g_pserver->replid,mi->master->replid,sizeof(g_pserver->replid));
g_pserver->master_repl_offset = mi->master->reploff;
if (g_pserver->repl_batch_offStart >= 0)
g_pserver->repl_batch_offStart = g_pserver->master_repl_offset;
}
clearReplicationId2();
@@ -2276,7 +2266,7 @@ void readSyncBulkPayload(connection *conn) {
* accumulate the backlog regardless of the fact they have sub-slaves
* or not, in order to behave correctly if they are promoted to
* masters after a failover. */
if (g_pserver->repl_backlog == NULL) createReplicationBacklog();
if (g_pserver->repl_backlog == NULL) runAndPropogateToReplicas(createReplicationBacklog);
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success");
if (cserver.supervised_mode == SUPERVISED_SYSTEMD) {
@@ -2536,7 +2526,7 @@ int slaveTryPartialResynchronization(redisMaster *mi, connection *conn, int read
/* If this instance was restarted and we read the metadata to
* PSYNC from the persistence file, our replication backlog could
* be still not initialized. Create it. */
if (g_pserver->repl_backlog == NULL) createReplicationBacklog();
if (g_pserver->repl_backlog == NULL) runAndPropogateToReplicas(createReplicationBacklog);
return PSYNC_CONTINUE;
}
@@ -3049,7 +3039,8 @@ struct redisMaster *replicationAddMaster(char *ip, int port) {
else
freeClientAsync(mi->master);
}
disconnectAllBlockedClients(); /* Clients blocked in master, now replica. */
if (!g_pserver->fActiveReplica)
disconnectAllBlockedClients(); /* Clients blocked in master, now replica. */
/* Update oom_score_adj */
setOOMScoreAdj(-1);
@@ -3267,7 +3258,7 @@ void replicaofCommand(client *c) {
miNew->masterhost, miNew->masterport, client);
sdsfree(client);
}
addReplyAsync(c,shared.ok);
addReply(c,shared.ok);
}
/* ROLE command: provide information about the role of the instance
@@ -3747,7 +3738,7 @@ void processClientsWaitingReplicas(void) {
last_numreplicas > c->bpop.numreplicas)
{
unblockClient(c);
addReplyLongLongAsync(c,last_numreplicas);
addReplyLongLong(c,last_numreplicas);
} else {
int numreplicas = replicationCountAcksByOffset(c->bpop.reploffset);
@@ -3755,7 +3746,7 @@ void processClientsWaitingReplicas(void) {
last_offset = c->bpop.reploffset;
last_numreplicas = numreplicas;
unblockClient(c);
addReplyLongLongAsync(c,numreplicas);
addReplyLongLong(c,numreplicas);
}
}
fastlock_unlock(&c->lock);
@@ -4366,3 +4357,90 @@ static void propagateMasterStaleKeys()
decrRefCount(rgobj[0]);
}
void replicationNotifyLoadedKey(redisDb *db, robj_roptr key, robj_roptr val, long long expire) {
if (!g_pserver->fActiveReplica || listLength(g_pserver->slaves) == 0)
return;
// Send a digest over to the replicas
rio r;
createDumpPayload(&r, val, key.unsafe_robjcast());
redisObjectStack objPayload;
initStaticStringObject(objPayload, r.io.buffer.ptr);
redisObjectStack objTtl;
initStaticStringObject(objTtl, sdscatprintf(sdsempty(), "%lld", expire));
redisObjectStack objMvcc;
initStaticStringObject(objMvcc, sdscatprintf(sdsempty(), "%lu", mvccFromObj(val)));
redisObject *argv[5] = {shared.mvccrestore, key.unsafe_robjcast(), &objMvcc, &objTtl, &objPayload};
replicationFeedSlaves(g_pserver->slaves, db - g_pserver->db, argv, 5);
sdsfree(szFromObj(&objTtl));
sdsfree(szFromObj(&objMvcc));
sdsfree(r.io.buffer.ptr);
}
void replicateSubkeyExpire(redisDb *db, robj_roptr key, robj_roptr subkey, long long expire) {
if (!g_pserver->fActiveReplica || listLength(g_pserver->slaves) == 0)
return;
redisObjectStack objTtl;
initStaticStringObject(objTtl, sdscatprintf(sdsempty(), "%lld", expire));
redisObject *argv[4] = {shared.pexpirememberat, key.unsafe_robjcast(), subkey.unsafe_robjcast(), &objTtl};
replicationFeedSlaves(g_pserver->slaves, db - g_pserver->db, argv, 4);
sdsfree(szFromObj(&objTtl));
}
void flushReplBacklogToClients()
{
serverAssert(GlobalLocksAcquired());
if (g_pserver->repl_batch_offStart < 0)
return;
if (g_pserver->repl_batch_offStart != g_pserver->master_repl_offset) {
bool fAsyncWrite = false;
// Ensure no overflow
serverAssert(g_pserver->repl_batch_offStart < g_pserver->master_repl_offset);
serverAssert(g_pserver->master_repl_offset - g_pserver->repl_batch_offStart <= g_pserver->repl_backlog_size);
serverAssert(g_pserver->repl_batch_idxStart != g_pserver->repl_backlog_idx);
listIter li;
listNode *ln;
listRewind(g_pserver->slaves, &li);
while ((ln = listNext(&li))) {
client *replica = (client*)listNodeValue(ln);
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
if (replica->flags & CLIENT_CLOSE_ASAP) continue;
std::unique_lock<fastlock> ul(replica->lock, std::defer_lock);
if (FCorrectThread(replica))
ul.lock();
else
fAsyncWrite = true;
if (g_pserver->repl_backlog_idx >= g_pserver->repl_batch_idxStart) {
long long cbCopy = g_pserver->repl_backlog_idx - g_pserver->repl_batch_idxStart;
serverAssert((g_pserver->master_repl_offset - g_pserver->repl_batch_offStart) == cbCopy);
serverAssert((g_pserver->repl_backlog_size - g_pserver->repl_batch_idxStart) >= (cbCopy));
serverAssert((g_pserver->repl_batch_idxStart + cbCopy) <= g_pserver->repl_backlog_size);
addReplyProto(replica, g_pserver->repl_backlog + g_pserver->repl_batch_idxStart, cbCopy);
} else {
auto cbPhase1 = g_pserver->repl_backlog_size - g_pserver->repl_batch_idxStart;
addReplyProto(replica, g_pserver->repl_backlog + g_pserver->repl_batch_idxStart, cbPhase1);
addReplyProto(replica, g_pserver->repl_backlog, g_pserver->repl_backlog_idx);
serverAssert((cbPhase1 + g_pserver->repl_backlog_idx) == (g_pserver->master_repl_offset - g_pserver->repl_batch_offStart));
}
}
if (fAsyncWrite)
ProcessPendingAsyncWrites();
// This may be called multiple times per "frame" so update with our progress flushing to clients
g_pserver->repl_batch_idxStart = g_pserver->repl_backlog_idx;
g_pserver->repl_batch_offStart = g_pserver->master_repl_offset;
}
}
+10
View File
@@ -390,6 +390,10 @@ public:
: sdsview(sdsdup(other.m_str))
{}
sdsstring(const char *rgch, size_t cch)
: sdsview(sdsnewlen(rgch, cch))
{}
sdsstring(sdsstring &&other)
: sdsview(other.m_str)
{
@@ -410,6 +414,12 @@ public:
return *this;
}
sds release() {
sds sdsT = m_str;
m_str = nullptr;
return sdsT;
}
~sdsstring()
{
sdsfree(m_str);
+81 -50
View File
@@ -63,6 +63,7 @@
#include <mutex>
#include "aelocker.h"
#include "motd.h"
#include "t_nhash.h"
#ifdef __linux__
#include <sys/prctl.h>
#endif
@@ -1060,7 +1061,19 @@ struct redisCommand redisCommandTable[] = {
{"stralgo",stralgoCommand,-2,
"read-only @string",
0,lcsGetKeys,0,0,0,0,0,0}
0,lcsGetKeys,0,0,0,0,0,0},
{"keydb.nhget",nhgetCommand,-2,
"read-only fast @hash",
0,NULL,1,1,1,0,0,0},
{"keydb.nhset",nhsetCommand,-3,
"read-only fast @hash",
0,NULL,1,1,1,0,0,0},
{"KEYDB.MVCCRESTORE",mvccrestoreCommand, 5,
"write use-memory @keyspace @dangerous",
0,NULL,1,1,1,0,0,0},
};
/*============================ Utility functions ============================ */
@@ -2221,17 +2234,23 @@ void processClients();
* The most important is freeClientsInAsyncFreeQueue but we also
* call some other low-risk functions. */
void beforeSleep(struct aeEventLoop *eventLoop) {
AeLocker locker;
UNUSED(eventLoop);
int iel = ielFromEventLoop(eventLoop);
processClients();
locker.arm();
serverAssert(g_pserver->repl_batch_offStart < 0);
runAndPropogateToReplicas(processClients);
/* Handle precise timeouts of blocked clients. */
handleBlockedClientsTimeout();
/* Handle TLS pending data. (must be done before flushAppendOnlyFile) */
aeReleaseLock();
tlsProcessPendingData();
aeAcquireLock();
if (tlsHasPendingData()) {
locker.disarm();
tlsProcessPendingData();
locker.arm();
}
/* If tls still has pending unread data don't sleep at all. */
aeSetDontWait(eventLoop, tlsHasPendingData());
@@ -2289,47 +2308,28 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
/* Handle writes with pending output buffers. */
int aof_state = g_pserver->aof_state;
aeReleaseLock();
handleClientsWithPendingWrites(iel, aof_state);
aeAcquireLock();
/* Close clients that need to be closed asynchronous */
freeClientsInAsyncFreeQueue(iel);
/* 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. */
if (moduleCount()) moduleReleaseGIL(TRUE /*fServerThread*/);
}
void beforeSleepLite(struct aeEventLoop *eventLoop)
{
int iel = ielFromEventLoop(eventLoop);
/* Try to process pending commands for clients that were just unblocked. */
aeAcquireLock();
processClients();
if (listLength(g_pserver->rgthreadvar[iel].unblocked_clients)) {
processUnblockedClients(iel);
/* We try to handle writes at the end so we don't have to reacquire the lock,
but if there is a pending async close we need to ensure the writes happen
first so perform it here */
bool fSentReplies = false;
if (listLength(g_pserver->clients_to_close)) {
locker.disarm();
handleClientsWithPendingWrites(iel, aof_state);
locker.arm();
fSentReplies = true;
}
/* Check if there are clients unblocked by modules that implement
* blocking commands. */
if (moduleCount()) moduleHandleBlockedClients(ielFromEventLoop(eventLoop));
int aof_state = g_pserver->aof_state;
aeReleaseLock();
/* Handle writes with pending output buffers. */
handleClientsWithPendingWrites(iel, aof_state);
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. */
serverAssert(g_pserver->repl_batch_offStart < 0);
locker.disarm();
if (!fSentReplies)
handleClientsWithPendingWrites(iel, aof_state);
if (moduleCount()) moduleReleaseGIL(TRUE /*fServerThread*/);
}
@@ -2444,6 +2444,8 @@ void createSharedObjects(void) {
shared.hdel = makeObjectShared(createStringObject("HDEL", 4));
shared.zrem = makeObjectShared(createStringObject("ZREM", 4));
shared.srem = makeObjectShared(createStringObject("SREM", 4));
shared.mvccrestore = makeObjectShared(createStringObject("KEYDB.MVCCRESTORE", 17));
shared.pexpirememberat = makeObjectShared(createStringObject("PEXPIREMEMBERAT",15));
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
shared.integers[j] =
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
@@ -3049,7 +3051,7 @@ static void initServerThread(struct redisServerThreadVars *pvar, int fMain)
pvar->tlsfd_count = 0;
pvar->cclients = 0;
pvar->el = aeCreateEventLoop(g_pserver->maxclients+CONFIG_FDSET_INCR);
aeSetBeforeSleepProc(pvar->el, beforeSleep, 0);
aeSetBeforeSleepProc(pvar->el, beforeSleep, AE_SLEEP_THREADSAFE);
aeSetAfterSleepProc(pvar->el, afterSleep, AE_SLEEP_THREADSAFE);
pvar->current_client = nullptr;
pvar->clients_paused = 0;
@@ -3550,12 +3552,19 @@ void call(client *c, int flags) {
/* Send the command to clients in MONITOR mode if applicable.
* Administrative commands are considered too dangerous to be shown. */
if (listLength(g_pserver->monitors) &&
!g_pserver->loading &&
!g_pserver->loading.load(std::memory_order_relaxed) &&
!(c->cmd->flags & (CMD_SKIP_MONITOR|CMD_ADMIN)))
{
replicationFeedMonitors(c,g_pserver->monitors,c->db->id,c->argv,c->argc);
}
/* We need to transfer async writes before a client's repl state gets changed. Otherwise
we won't be able to propogate them correctly. */
if (c->cmd->flags & CMD_CATEGORY_REPLICATION) {
flushReplBacklogToClients();
ProcessPendingAsyncWrites();
}
/* Initialization: clear the flags that must be set by the command on
* demand, and initialize the array for additional commands propagation. */
c->flags &= ~(CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP);
@@ -3565,8 +3574,17 @@ void call(client *c, int flags) {
/* Call the command. */
dirty = g_pserver->dirty;
updateCachedTime(0);
incrementMvccTstamp();
start = g_pserver->ustime;
c->cmd->proc(c);
try {
c->cmd->proc(c);
} catch (robj_roptr o) {
addReply(c, o);
} catch (robj *o) {
addReply(c, o);
} catch (const char *sz) {
addReplyError(c, sz);
}
serverTL->commandsExecuted++;
duration = ustime()-start;
dirty = g_pserver->dirty-dirty;
@@ -3685,8 +3703,6 @@ void call(client *c, int flags) {
}
redisOpArrayFree(&g_pserver->also_propagate);
}
ProcessPendingAsyncWrites();
g_pserver->also_propagate = prev_also_propagate;
@@ -3850,8 +3866,6 @@ int processCommand(client *c, int callFlags) {
}
}
incrementMvccTstamp();
/* Handle the maxmemory directive.
*
* Note that we do not want to reclaim memory if we are here re-entering
@@ -4002,21 +4016,31 @@ int processCommand(client *c, int callFlags) {
queueMultiCommand(c);
addReply(c,shared.queued);
} else {
/* If the command was replication or admin related we *must* flush our buffers first. This is in case
something happens which would modify what we would send to replicas */
if (c->cmd->flags & (CMD_MODULE | CMD_ADMIN))
flushReplBacklogToClients();
call(c,callFlags);
c->woff = g_pserver->master_repl_offset;
if (c->cmd->flags & (CMD_MODULE | CMD_ADMIN))
flushReplBacklogToClients();
if (listLength(g_pserver->ready_keys))
handleClientsBlockedOnKeys();
}
return C_OK;
}
bool client::postFunction(std::function<void(client *)> fn) {
bool client::postFunction(std::function<void(client *)> fn, bool fLock) {
this->casyncOpsPending++;
return aePostFunction(g_pserver->rgthreadvar[this->iel].el, [this, fn]{
std::lock_guard<decltype(this->lock)> lock(this->lock);
--casyncOpsPending;
fn(this);
}) == AE_OK;
--casyncOpsPending;
}, false, fLock) == AE_OK;
}
/*================================== Shutdown =============================== */
@@ -5125,7 +5149,7 @@ void redisAsciiArt(void) {
mode, g_pserver->port ? g_pserver->port : g_pserver->tls_port
);
} else {
sds motd = fetchMOTD(true);
sds motd = fetchMOTD(true, cserver.enable_motd);
snprintf(buf,1024*16,ascii_logo,
KEYDB_REAL_VERSION,
redisGitSHA1(),
@@ -5301,6 +5325,8 @@ void loadDataFromDisk(void) {
{
memcpy(g_pserver->replid,rsi.repl_id,sizeof(g_pserver->replid));
g_pserver->master_repl_offset = rsi.repl_offset;
if (g_pserver->repl_batch_offStart >= 0)
g_pserver->repl_batch_offStart = g_pserver->master_repl_offset;
listIter li;
listNode *ln;
@@ -5551,6 +5577,7 @@ int main(int argc, char **argv) {
srand(time(NULL)^getpid());
gettimeofday(&tv,NULL);
crc64_init();
serverAssert(g_pserver->repl_batch_offStart < 0);
uint8_t hashseed[16];
getRandomHexChars((char*)hashseed,sizeof(hashseed));
@@ -5684,7 +5711,7 @@ int main(int argc, char **argv) {
validateConfiguration();
for (int iel = 0; iel < MAX_EVENT_LOOPS; ++iel)
for (int iel = 0; iel < cserver.cthreads; ++iel)
{
initServerThread(g_pserver->rgthreadvar+iel, iel == IDX_EVENT_LOOP_MAIN);
}
@@ -5777,9 +5804,13 @@ int main(int argc, char **argv) {
setOOMScoreAdj(-1);
serverAssert(cserver.cthreads > 0 && cserver.cthreads <= MAX_EVENT_LOOPS);
pthread_t rgthread[MAX_EVENT_LOOPS];
pthread_attr_t tattr;
pthread_attr_init(&tattr);
pthread_attr_setstacksize(&tattr, 1 << 23); // 8 MB
for (int iel = 0; iel < cserver.cthreads; ++iel)
{
pthread_create(rgthread + iel, NULL, workerThreadMain, (void*)((int64_t)iel));
pthread_create(rgthread + iel, &tattr, workerThreadMain, (void*)((int64_t)iel));
if (cserver.fThreadAffinity)
{
#ifdef __linux__
+49 -39
View File
@@ -327,6 +327,7 @@ inline bool operator!=(const void *p, const robj_sharedptr &rhs)
#define PROTO_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */
#define PROTO_IOBUF_LEN (1024*16) /* Generic I/O buffer size */
#define PROTO_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */
#define PROTO_ASYNC_REPLY_CHUNK_BYTES (1024)
#define PROTO_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */
#define PROTO_MBULK_BIG_ARG (1024*32)
#define LONG_STR_SIZE 21 /* Bytes needed for long -> str + '\0' */
@@ -653,11 +654,11 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
/* A redis object, that is a type able to hold a string / list / set */
/* The actual Redis Object */
#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */
#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */
/* The "module" object type is a special one that signals that the object
* is one directly managed by a Redis module. In this case the value points
@@ -670,10 +671,10 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
* by a 64 bit module type ID, which has a 54 bits module-specific signature
* in order to dispatch the loading to the right module, plus a 10 bits
* encoding version. */
#define OBJ_MODULE 5 /* Module object. */
#define OBJ_STREAM 6 /* Stream object. */
#define OBJ_CRON 7 /* CRON job */
#define OBJ_MODULE 5 /* Module object. */
#define OBJ_STREAM 6 /* Stream object. */
#define OBJ_CRON 7 /* CRON job */
#define OBJ_NESTEDHASH 8 /* Nested Hash Object */
/* Extract encver / signature from a module type ID. */
#define REDISMODULE_TYPE_ENCVER_BITS 10
@@ -1082,7 +1083,7 @@ typedef struct client {
std::atomic<uint64_t> 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 fPendingAsyncWriteHandler;
std::atomic<bool> fPendingAsyncWriteHandler;
int authenticated; /* Needed when the default user requires auth. */
int replstate; /* Replication state if this is a replica. */
int repl_put_online_on_ack; /* Install replica write handler on ACK. */
@@ -1145,16 +1146,14 @@ typedef struct client {
char buf[PROTO_REPLY_CHUNK_BYTES];
/* Async Response Buffer - other threads write here */
int bufposAsync;
int buflenAsync;
char *bufAsync;
clientReplyBlock *replyAsync;
int iel; /* the event loop index we're registered with */
struct fastlock lock;
int master_error;
// post a function from a non-client thread to run on its client thread
bool postFunction(std::function<void(client *)> fn);
bool postFunction(std::function<void(client *)> fn, bool fLock = true);
} client;
struct saveparam {
@@ -1177,7 +1176,7 @@ struct sharedObjectsStruct {
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
*rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan,
*multi, *exec, *srem, *hdel, *zrem,
*multi, *exec, *srem, *hdel, *zrem, *mvccrestore, *pexpirememberat,
*select[PROTO_SHARED_SELECT_CMDS],
*integers[OBJ_SHARED_INTEGERS],
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
@@ -1457,6 +1456,7 @@ struct redisServerConst {
bool fUsePro = false;
int thread_min_client_threshold = 50;
int multimaster_no_forward;
int enable_motd; /* Flag to retrieve the Message of today using CURL request*/
};
struct redisServer {
@@ -1827,6 +1827,10 @@ struct redisServer {
char *bio_cpulist; /* cpu affinity list of bio thread. */
char *aof_rewrite_cpulist; /* cpu affinity list of aof rewrite process. */
char *bgsave_cpulist; /* cpu affinity list of bgsave process. */
long long repl_batch_offStart = -1;
long long repl_batch_idxStart = -1;
};
typedef struct pubsubPattern {
@@ -2005,17 +2009,15 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void readQueryFromClient(connection *conn);
void addReplyNull(client *c, robj_roptr objOldProtocol = nullptr);
void addReplyNull(client *c);
void addReplyNullArray(client *c);
void addReplyNullArrayAsync(client *c);
void addReplyBool(client *c, int b);
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
void addReplyVerbatimAsync(client *c, const char *s, size_t len, const char *ext);
void addReplyProto(client *c, const char *s, size_t len);
void addReplyProtoCString(client *c, const char *s);
void addReplyBulk(client *c, robj_roptr obj);
void AddReplyFromClient(client *c, client *src);
void addReplyBulkCString(client *c, const char *s);
void addReplyBulkCStringAsync(client *c, const char *s);
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
void addReplyBulkLongLong(client *c, long long ll);
void addReply(client *c, robj_roptr obj);
@@ -2027,10 +2029,9 @@ void addReplyError(client *c, const char *err);
void addReplyStatus(client *c, const char *status);
void addReplyDouble(client *c, double d);
void addReplyHumanLongDouble(client *c, long double d);
void addReplyHumanLongDoubleAsync(client *c, long double d);
void addReplyLongLong(client *c, long long ll);
#ifdef __cplusplus
void addReplyLongLongWithPrefixCore(client *c, long long ll, char prefix, bool fAsync);
void addReplyLongLongWithPrefixCore(client *c, long long ll, char prefix);
#endif
void addReplyArrayLen(client *c, long length);
void addReplyMapLen(client *c, long length);
@@ -2075,23 +2076,6 @@ void linkClient(client *c);
void protectClient(client *c);
void unprotectClient(client *c);
// Special Thread-safe addReply() commands for posting messages to clients from a different thread
void addReplyAsync(client *c, robj_roptr obj);
void addReplyArrayLenAsync(client *c, long length);
void addReplyProtoAsync(client *c, const char *s, size_t len);
void addReplyBulkAsync(client *c, robj_roptr obj);
void addReplyBulkCBufferAsync(client *c, const void *p, size_t len);
void addReplyErrorAsync(client *c, const char *err);
void addReplyMapLenAsync(client *c, long length);
void addReplyNullAsync(client *c);
void addReplyDoubleAsync(client *c, double d);
void *addReplyDeferredLenAsync(client *c);
void setDeferredArrayLenAsync(client *c, void *node, long length);
void addReplySdsAsync(client *c, sds s);
void addReplyBulkSdsAsync(client *c, sds s);
void addReplyPushLenAsync(client *c, long length);
void addReplyLongLongAsync(client *c, long long ll);
void ProcessPendingAsyncWrites(void);
client *lookupClientByID(uint64_t id);
@@ -2206,7 +2190,7 @@ ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout);
/* Replication */
void initMasterInfo(struct redisMaster *master);
void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc);
void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t buflen);
void replicationFeedSlavesFromMasterStream(char *buf, size_t buflen);
void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv, int argc);
void updateSlavesWaitingBgsave(int bgsaveerr, int type);
void replicationCron(void);
@@ -2238,6 +2222,8 @@ void updateMasterAuth();
void showLatestBacklog();
void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
void rdbPipeWriteHandlerConnRemoved(struct connection *conn);
void replicationNotifyLoadedKey(redisDb *db, robj_roptr key, robj_roptr val, long long expire);
void replicateSubkeyExpire(redisDb *db, robj_roptr key, robj_roptr subkey, long long expire);
/* Generic persistence functions */
void startLoadingFile(FILE* fp, const char * filename, int rdbflags);
@@ -2568,6 +2554,7 @@ void clusterPropagatePublish(robj *channel, robj *message);
void migrateCloseTimedoutSockets(void);
void clusterBeforeSleep(void);
int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, unsigned char *payload, uint32_t len);
void createDumpPayload(rio *payload, robj_roptr o, robj *key);
/* Sentinel */
void initSentinelConfig(void);
@@ -2785,6 +2772,7 @@ void watchCommand(client *c);
void unwatchCommand(client *c);
void clusterCommand(client *c);
void restoreCommand(client *c);
void mvccrestoreCommand(client *c);
void migrateCommand(client *c);
void askingCommand(client *c);
void readonlyCommand(client *c);
@@ -2900,6 +2888,28 @@ inline int FCorrectThread(client *c)
}
#define AssertCorrectThread(c) serverAssert(FCorrectThread(c))
void flushReplBacklogToClients();
template<typename FN_PTR, class ...TARGS>
void runAndPropogateToReplicas(FN_PTR *pfn, TARGS... args) {
// Store the replication backlog starting params, we use this to know how much data was written.
// these are TLS in case we need to expand the buffer and therefore need to update them
bool fNestedProcess = (g_pserver->repl_batch_idxStart >= 0);
if (!fNestedProcess) {
g_pserver->repl_batch_offStart = g_pserver->master_repl_offset;
g_pserver->repl_batch_idxStart = g_pserver->repl_backlog_idx;
}
pfn(args...);
if (!fNestedProcess) {
flushReplBacklogToClients();
g_pserver->repl_batch_offStart = -1;
g_pserver->repl_batch_idxStart = -1;
}
}
/* TLS stuff */
void tlsInit(void);
void tlsInitThread();
+4 -4
View File
@@ -677,7 +677,7 @@ static void rpoplpushHandlePush(client *c, robj *dstkey, robj *dstobj, robj *val
listTypePush(dstobj,value,LIST_HEAD);
notifyKeyspaceEvent(NOTIFY_LIST,"lpush",dstkey,c->db->id);
/* Always send the pushed value to the client. */
addReplyBulkAsync(c,value);
addReplyBulk(c,value);
}
void rpoplpushCommand(client *c) {
@@ -758,9 +758,9 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
/* BRPOP/BLPOP */
addReplyArrayLenAsync(receiver,2);
addReplyBulkAsync(receiver,key);
addReplyBulkAsync(receiver,value);
addReplyArrayLen(receiver,2);
addReplyBulk(receiver,key);
addReplyBulk(receiver,value);
/* Notify event. */
const char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
+353
View File
@@ -0,0 +1,353 @@
/*
* Copyright (c) 2020, EQ Alpha Technology Ltd. <john at eqalpha dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "server.h"
#include <math.h>
void dictObjectDestructor(void *privdata, void *val);
dictType nestedHashDictType {
dictSdsHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCompare, /* key compare */
dictSdsDestructor, /* key destructor */
dictObjectDestructor, /* val destructor */
};
robj *createNestHashBucket() {
dict *d = dictCreate(&nestedHashDictType, nullptr);
return createObject(OBJ_NESTEDHASH, d);
}
void freeNestedHashObject(robj_roptr o) {
dictRelease((dict*)ptrFromObj(o));
}
robj *fetchFromKey(redisDb *db, robj_roptr key) {
const char *pchCur = szFromObj(key);
const char *pchStart = pchCur;
const char *pchMax = pchCur + sdslen(pchCur);
robj *o = nullptr;
while (pchCur <= pchMax) {
if (pchCur == pchMax || *pchCur == '.') {
// WARNING: Don't deref pchCur as it may be pchMax
// New word
if ((pchCur - pchStart) < 1) {
throw shared.syntaxerr; // malformed
}
dict *d = nullptr;
if (o == nullptr)
d = db->pdict;
else
d = (dict*)ptrFromObj(o);
sdsstring str(pchStart, pchCur - pchStart);
dictEntry *de = dictFind(d, str.get());
o = (de != nullptr) ? (robj*)dictGetVal(de) : nullptr;
if (o == nullptr) throw shared.nokeyerr; // Not Found
serverAssert(o->type == OBJ_NESTEDHASH || o->type == OBJ_STRING || o->type == OBJ_LIST);
if (o->type == OBJ_STRING && pchCur != pchMax)
throw shared.nokeyerr; // Past the end
pchStart = pchCur + 1;
}
++pchCur;
}
return o;
}
// Returns one if we overwrote a value
bool setWithKey(redisDb *db, robj_roptr key, robj *val, bool fCreateBuckets) {
const char *pchCur = szFromObj(key);
const char *pchStart = pchCur;
const char *pchMax = pchCur + sdslen(pchCur);
robj *o = nullptr;
while (pchCur <= pchMax) {
if (pchCur == pchMax || *pchCur == '.') {
// WARNING: Don't deref pchCur as it may be pchMax
// New word
if ((pchCur - pchStart) < 1) {
throw shared.syntaxerr; // malformed
}
dict *d = nullptr;
if (o == nullptr)
d = db->pdict;
else
d = (dict*)ptrFromObj(o);
sdsstring str(pchStart, pchCur - pchStart);
dictEntry *de = dictFind(d, str.get());
if (pchCur == pchMax) {
val->addref();
if (de != nullptr) {
decrRefCount((robj*)dictGetVal(de));
dictSetVal(d, de, val);
return true;
} else {
dictAdd(d, str.release(), val);
return false;
}
} else {
o = (de != nullptr) ? (robj*)dictGetVal(de) : nullptr;
if (o == nullptr) {
if (!fCreateBuckets)
throw shared.nokeyerr; // Not Found
o = createNestHashBucket();
serverAssert(dictAdd(d, str.release(), o) == DICT_OK);
} else if (o->type != OBJ_NESTEDHASH) {
decrRefCount(o);
o = createNestHashBucket();
de->v.val = o;
}
}
pchStart = pchCur + 1;
}
++pchCur;
}
throw "Internal Error";
}
void writeNestedHashToClient(client *c, robj_roptr o) {
if (o == nullptr) {
addReply(c, shared.null[c->resp]);
} else if (o->type == OBJ_STRING) {
addReplyBulk(c, o);
} else if (o->type == OBJ_LIST) {
unsigned char *zl = (unsigned char*)ptrFromObj(o);
addReplyArrayLen(c, ziplistLen(zl));
unsigned char *p = ziplistIndex(zl, ZIPLIST_HEAD);
while (p != nullptr) {
unsigned char *str;
unsigned int len;
long long lval;
if (ziplistGet(p, &str, &len, &lval)) {
char rgT[128];
if (str == nullptr) {
len = ll2string(rgT, 128, lval);
str = (unsigned char*)rgT;
}
addReplyBulkCBuffer(c, (const char*)str, len);
}
p = ziplistNext(zl, p);
}
} else {
serverAssert(o->type == OBJ_NESTEDHASH );
dict *d = (dict*)ptrFromObj(o);
if (dictSize(d) > 1)
addReplyArrayLen(c, dictSize(d));
dictIterator *di = dictGetIterator(d);
dictEntry *de;
while ((de = dictNext(di))) {
robj_roptr oT = (robj*)dictGetVal(de);
addReplyArrayLen(c, 2);
addReplyBulkCBuffer(c, (sds)dictGetKey(de), sdslen((sds)dictGetKey(de)));
if (oT->type == OBJ_STRING) {
addReplyBulk(c, oT);
} else {
writeNestedHashToClient(c, oT);
}
}
dictReleaseIterator(di);
}
}
inline bool FSimpleJsonEscapeCh(char ch) {
return (ch == '"' || ch == '\\');
}
inline bool FExtendedJsonEscapeCh(char ch) {
return ch <= 0x1F;
}
sds writeJsonValue(sds output, const char *valIn, size_t cchIn) {
const char *val = valIn;
size_t cch = cchIn;
int cchEscapeExtra = 0;
// First scan for escaped chars
for (size_t ich = 0; ich < cchIn; ++ich) {
if (FSimpleJsonEscapeCh(valIn[ich])) {
++cchEscapeExtra;
} else if (FExtendedJsonEscapeCh(valIn[ich])) {
cchEscapeExtra += 5;
}
}
if (cchEscapeExtra > 0) {
size_t ichDst = 0;
sds dst = sdsnewlen(SDS_NOINIT, cchIn+cchEscapeExtra);
for (size_t ich = 0; ich < cchIn; ++ich) {
switch (valIn[ich]) {
case '"':
dst[ichDst++] = '\\'; dst[ichDst++] = '"';
break;
case '\\':
dst[ichDst++] = '\\'; dst[ichDst++] = '\\';
break;
default:
serverAssert(!FSimpleJsonEscapeCh(valIn[ich]));
if (FExtendedJsonEscapeCh(valIn[ich])) {
dst[ichDst++] = '\\'; dst[ichDst++] = 'u';
sprintf(dst + ichDst, "%4x", valIn[ich]);
ichDst += 4;
} else {
dst[ichDst++] = valIn[ich];
}
break;
}
}
val = (const char*)dst;
serverAssert(ichDst == (cchIn+cchEscapeExtra));
cch = ichDst;
}
output = sdscat(output, "\"");
output = sdscatlen(output, val, cch);
output = sdscat(output, "\"");
if (val != valIn)
sdsfree(val);
return output;
}
sds writeJsonValue(sds output, sds val) {
return writeJsonValue(output, (const char*)val, sdslen(val));
}
sds writeNestedHashAsJson(sds output, robj_roptr o) {
if (o->type == OBJ_STRING) {
output = writeJsonValue(output, (sds)szFromObj(o));
} else if (o->type == OBJ_LIST) {
unsigned char *zl = (unsigned char*)ptrFromObj(o);
output = sdscat(output, "[");
unsigned char *p = ziplistIndex(zl, ZIPLIST_HEAD);
bool fFirst = true;
while (p != nullptr) {
unsigned char *str;
unsigned int len;
long long lval;
if (ziplistGet(p, &str, &len, &lval)) {
char rgT[128];
if (str == nullptr) {
len = ll2string(rgT, 128, lval);
str = (unsigned char*)rgT;
}
if (!fFirst)
output = sdscat(output, ",");
fFirst = false;
output = writeJsonValue(output, (const char*)str, len);
}
p = ziplistNext(zl, p);
}
output = sdscat(output, "]");
} else {
output = sdscat(output, "{");
dictIterator *di = dictGetIterator((dict*)ptrFromObj(o));
dictEntry *de;
bool fFirst = true;
while ((de = dictNext(di))) {
robj_roptr oT = (robj*)dictGetVal(de);
if (!fFirst)
output = sdscat(output, ",");
fFirst = false;
output = writeJsonValue(output, (sds)dictGetKey(de));
output = sdscat(output, " : ");
output = writeNestedHashAsJson(output, oT);
}
dictReleaseIterator(di);
output = sdscat(output, "}");
}
return output;
}
void nhsetCommand(client *c) {
if (c->argc < 3)
throw shared.syntaxerr;
robj *val = c->argv[2];
if (c->argc > 3) {
// Its a list, we'll store as a ziplist
val = createZiplistObject();
for (int iarg = 2; iarg < c->argc; ++iarg) {
sds arg = (sds)szFromObj(c->argv[iarg]);
val->m_ptr = ziplistPush((unsigned char*)ptrFromObj(val), (unsigned char*)arg, sdslen(arg), ZIPLIST_TAIL);
}
}
try {
if (setWithKey(c->db, c->argv[1], val, true)) {
addReplyLongLong(c, 1); // we replaced a value
} else {
addReplyLongLong(c, 0); // we added a new value
}
} catch (...) {
if (val != c->argv[2])
decrRefCount(val);
throw;
}
if (val != c->argv[2])
decrRefCount(val);
}
void nhgetCommand(client *c) {
if (c->argc != 2 && c->argc != 3)
throw shared.syntaxerr;
bool fJson = false;
int argOffset = 0;
if (c->argc == 3) {
argOffset++;
if (strcasecmp(szFromObj(c->argv[1]), "json") == 0) {
fJson = true;
} else if (strcasecmp(szFromObj(c->argv[1]), "resp") != 0) {
throw shared.syntaxerr;
}
}
robj *o = fetchFromKey(c->db, c->argv[argOffset + 1]);
if (fJson) {
sds val = writeNestedHashAsJson(sdsnew(nullptr), o);
addReplyBulkSds(c, val);
} else {
writeNestedHashToClient(c, o);
}
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
void freeNestedHashObject(robj_roptr o);
void nhsetCommand(client *c);
void nhgetCommand(client *c);
+7 -7
View File
@@ -818,7 +818,7 @@ static void addReplyStreamID(client *c, streamID *id) {
static void addReplyStreamIDAsync(client *c, streamID *id) {
sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq);
addReplyBulkSdsAsync(c,replyid);
addReplyBulkSds(c,replyid);
}
/* Similar to the above function, but just creates an object, usually useful
@@ -968,7 +968,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
}
if (!(flags & STREAM_RWR_RAWENTRIES))
arraylen_ptr = addReplyDeferredLenAsync(c);
arraylen_ptr = addReplyDeferredLen(c);
streamIteratorStart(&si,s,start,end,rev);
while(streamIteratorGetID(&si,&id,&numfields)) {
/* Update the group last_id if needed. */
@@ -982,18 +982,18 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
/* Emit a two elements array for each item. The first is
* the ID, the second is an array of field-value pairs. */
addReplyArrayLenAsync(c,2);
addReplyArrayLen(c,2);
addReplyStreamIDAsync(c,&id);
addReplyArrayLenAsync(c,numfields*2);
addReplyArrayLen(c,numfields*2);
/* Emit the field-value pairs. */
while(numfields--) {
unsigned char *key, *value;
int64_t key_len, value_len;
streamIteratorGetField(&si,&key,&value,&key_len,&value_len);
addReplyBulkCBufferAsync(c,key,key_len);
addReplyBulkCBufferAsync(c,value,value_len);
addReplyBulkCBuffer(c,key,key_len);
addReplyBulkCBuffer(c,value,value_len);
}
/* If a group is passed, we need to create an entry in the
@@ -1052,7 +1052,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
streamPropagateGroupID(c,spi->keyname,group,spi->groupname);
streamIteratorStop(&si);
if (arraylen_ptr) setDeferredArrayLenAsync(c,arraylen_ptr,arraylen);
if (arraylen_ptr) setDeferredArrayLen(c,arraylen_ptr,arraylen);
return arraylen;
}
+5 -5
View File
@@ -3165,11 +3165,11 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
return;
}
void *arraylen_ptr = addReplyDeferredLenAsync(c);
void *arraylen_ptr = addReplyDeferredLen(c);
long arraylen = 0;
/* We emit the key only for the blocking variant. */
if (emitkey) addReplyBulkAsync(c,key);
if (emitkey) addReplyBulk(c,key);
/* Remove the element. */
do {
@@ -3219,8 +3219,8 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
signalModifiedKey(c,c->db,key);
}
addReplyBulkCBufferAsync(c,ele,sdslen(ele));
addReplyDoubleAsync(c,score);
addReplyBulkCBuffer(c,ele,sdslen(ele));
addReplyDouble(c,score);
sdsfree(ele);
arraylen += 2;
@@ -3232,7 +3232,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
}
} while(--count);
setDeferredArrayLenAsync(c,arraylen_ptr,arraylen + (emitkey != 0));
setDeferredArrayLen(c,arraylen_ptr,arraylen + (emitkey != 0));
}
/* ZPOPMIN key [<count>] */
+1 -1
View File
@@ -179,7 +179,7 @@ int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int
}
if (tval < 0) {
addReplyErrorAsync(c,"timeout is negative");
addReplyError(c,"timeout is negative");
return C_ERR;
}
+8 -8
View File
@@ -215,9 +215,9 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
* are unable to send invalidation messages to the redirected
* connection, because the client no longer exist. */
if (c->resp > 2) {
addReplyPushLenAsync(c,3);
addReplyBulkCBufferAsync(c,"tracking-redir-broken",21);
addReplyLongLongAsync(c,c->client_tracking_redirection);
addReplyPushLen(c,3);
addReplyBulkCBuffer(c,"tracking-redir-broken",21);
addReplyLongLong(c,c->client_tracking_redirection);
}
return;
}
@@ -232,8 +232,8 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
* in Pub/Sub mode, we can support the feature with RESP 2 as well,
* by sending Pub/Sub messages in the __redis__:invalidate channel. */
if (c->resp > 2) {
addReplyPushLenAsync(c,2);
addReplyBulkCBufferAsync(c,"invalidate",10);
addReplyPushLen(c,2);
addReplyBulkCBuffer(c,"invalidate",10);
} else if (using_redirection && c->flags & CLIENT_PUBSUB) {
/* We use a static object to speedup things, however we assume
* that addReplyPubsubMessage() will not take a reference. */
@@ -248,10 +248,10 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
/* Send the "value" part, which is the array of keys. */
if (proto) {
addReplyProtoAsync(c,keyname,keylen);
addReplyProto(c,keyname,keylen);
} else {
addReplyArrayLenAsync(c,1);
addReplyBulkCBufferAsync(c,keyname,keylen);
addReplyArrayLen(c,1);
addReplyBulkCBuffer(c,keyname,keylen);
}
}
+2 -11
View File
@@ -83,17 +83,8 @@ static_assert((PREFIX_SIZE % 16) == 0, "Our prefix must be modulo 16-bytes or ou
#define realloc(ptr,size,type) realloc(ptr,size)
#endif
#define update_zmalloc_stat_alloc(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
atomicIncr(used_memory,__n); \
} while(0)
#define update_zmalloc_stat_free(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
atomicDecr(used_memory,__n); \
} while(0)
#define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n))
#define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n))
static size_t used_memory = 0;
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;
Binary file not shown.
Binary file not shown.
+32 -7
View File
@@ -78,9 +78,9 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
$master flushall
}
test {Replication of EXPIREMEMBER (set) command (Active)} {
test {Replication of EXPIREMEMBER (set) command (Active)} {
$master sadd testkey a b c d
wait_for_condition 50 100 {
wait_for_condition 50 200 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "Failed to replicate set"
@@ -235,7 +235,7 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
$master replicaof no one
after 100
$master set testkey baz
after 100
after 200
$slave set testkey bar
after 100
$slave replicaof $master_host $master_port
@@ -243,10 +243,27 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
$master replicaof $slave_host $slave_port
after 1000
assert_equal {bar} [$slave get testkey]
assert_equal {bar} [$master get testkey]
assert_equal {bar} [$slave get testkey] {replica is correct}
assert_equal {bar} [$master get testkey] {master is correct}
}
test {Active replica merge works with client blocked} {
$slave flushall
$slave replicaof no one
$master replicaof no one
after 100
set rd [redis_deferring_client]
$rd blpop testlist 0
$slave lpush testlist foo
#OK Now reconnect
$slave replicaof $master_host $master_port
$master replicaof $slave_host $slave_port
after 1000
$rd read
} {testlist foo}
test {Active replica different databases} {
$master select 3
$master set testkey abcd
@@ -271,8 +288,16 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
test {Active Replica Merges Database On Sync} {
$slave set testkeyA foo
r replicaof $slave_host $slave_port
after 1000
assert_equal 2 [r dbsize]
wait_for_condition 50 1000 {
[string match *active-replica* [r role]]
} else {
fail "Replica did not connect"
}
wait_for_condition 50 1000 {
[string match "2" [r dbsize]]
} else {
fail "key did not propogate"
}
}
}
}
@@ -0,0 +1,124 @@
start_server {tags {"multi-master"} overrides {active-replica yes multi-master yes}} {
start_server {overrides {active-replica yes multi-master yes}} {
start_server {overrides {active-replica yes multi-master yes}} {
start_server {overrides {active-replica yes multi-master yes}} {
for {set j 0} {$j < 4} {incr j} {
set R($j) [srv [expr 0-$j] client]
set R_host($j) [srv [expr 0-$j] host]
set R_port($j) [srv [expr 0-$j] port]
}
set keysPerServer 100
# Initialize Dataset
for {set j 1} {$j < 4} {incr j} {
for {set key 0} { $key < $keysPerServer} { incr key } {
$R($j) set "key_$j\_$key" asdjaoijioasdjiod ex 100000
}
set hash($j) [$R($j) debug digest]
}
$R(1) replicaof $R_host(0) $R_port(0)
$R(2) replicaof $R_host(0) $R_port(0)
$R(3) replicaof $R_host(0) $R_port(0)
test "all nodes up" {
for {set j 1} {$j < 4} {incr j} {
wait_for_condition 50 100 {
[string match {*master_global_link_status:up*} [$R($j) info replication]]
} else {
fail "Multimaster group didn't connect up in a reasonable period of time"
}
}
}
test "nodes retain their data" {
for {set j 1} { $j < 4 } { incr j } {
assert_equal [$R($j) debug digest] $hash($j) $j
}
}
# Set all servers with an overlapping key - the last one should win
$R(0) set isvalid no
$R(1) set isvalid no
$R(2) set isvalid no
# Note: Sleep is due to mvcc slip
after 2
$R(3) set isvalid yes
for {set n 1} {$n < 4} {incr n} {
test "Node $n reciprocal rep works" {
$R(0) replicaof $R_host($n) $R_port($n)
after 2000
for {set key 0} { $key < $keysPerServer } { incr key } {
assert_equal [$R(0) get "key_$n\_$key"] asdjaoijioasdjiod $key
}
}
}
test "All data transferred between nodes" {
for {set server 0} {$server < 4} {incr server} {
set hash($j) [$R($server) debug digest]
for {set n 1} {$n < 4} {incr n} {
for {set key 0} {$key < $keysPerServer} {incr key} {
assert_equal [$R($server) get "key_$n\_$key"] asdjaoijioasdjiod "server: $n key: $key"
}
}
}
}
test "MVCC Updates Correctly" {
assert_equal [$R(0) get isvalid] yes
assert_equal [$R(1) get isvalid] yes
assert_equal [$R(2) get isvalid] yes
assert_equal [$R(3) get isvalid] yes
}
unset hash
test "All servers same debug digest" {
set hash [$R(0) debug digest]
for {set j 1} {$j < 4} {incr j} {
assert_equal $hash [$R($j) debug digest] $j
}
}
}}}}
# The tests below validate features replicated via RDB
start_server {tags {"multi-master"} overrides {active-replica yes multi-master yes}} {
start_server {overrides {active-replica yes multi-master yes}} {
start_server {overrides {active-replica yes multi-master yes}} {
for {set j 0} {$j < 3} {incr j} {
set R($j) [srv [expr 0-$j] client]
set R_host($j) [srv [expr 0-$j] host]
set R_port($j) [srv [expr 0-$j] port]
}
# Set replicated features here
$R(0) sadd testhash subkey
$R(0) expiremember testhash subkey 10000
test "node 2 up" {
$R(2) replicaof $R_host(1) $R_port(1)
wait_for_condition 50 100 {
[string match {*master_global_link_status:up*} [$R(2) info replication]]
} else {
fail "didn't connect up in a reasonable period of time"
}
}
# While node 1 loads from 0, it will relay to 2
test "node 1 up" {
$R(1) replicaof $R_host(0) $R_port(0)
wait_for_condition 50 100 {
[string match {*master_global_link_status:up*} [$R(1) info replication]]
} else {
fail "didn't connect up in a reasonable period of time"
}
}
#Tests that validate replication made it to node 2
test "subkey expire replicates via RDB" {
assert [expr [$R(2) ttl testhash subkey] > 0]
}
}}}
+3
View File
@@ -28,6 +28,7 @@ set ::all_tests {
unit/type/hash
unit/type/stream
unit/type/stream-cgroups
unit/type/nestedhash
unit/sort
unit/expire
unit/other
@@ -47,6 +48,7 @@ set ::all_tests {
integration/replication-psync
integration/replication-active
integration/replication-multimaster
integration/replication-multimaster-connect
integration/aof
integration/rdb
integration/convert-zipmap-hash-on-load
@@ -74,6 +76,7 @@ set ::all_tests {
unit/tls
unit/tracking
unit/oom-score-adj
unit/loadsave
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
+1
View File
@@ -67,6 +67,7 @@ start_server {tags {"introspection"}} {
io-threads-do-reads
tcp-backlog
always-show-logo
enable-motd
syslog-enabled
cluster-enabled
aclfile
+46
View File
@@ -0,0 +1,46 @@
set server_path [tmpdir "server.rdb-encoding-test"]
# Store a bunch of datatypes to the database,
# compute the hash of the database,
# and save the data to a file
start_server [list tags [list "loadsave"] overrides [list "dir" $server_path] keep_persistence true] {
test "Save various data types to .rdb file" {
r set "int" [expr {int(rand()*10000)}]
r set "string" [string repeat A [expr {int(rand()*10000)}]]
r hset "hash" [string repeat A [expr {int(rand()*1000)}]] 0[string repeat A [expr {int(rand()*1000)}]]
r sadd "set" [string repeat A [expr {int(rand()*1000)}]]
r zadd "zset" [expr {rand()}] [string repeat A [expr {int(rand()*1000)}]]
r lpush "list" [string repeat A [expr {int(rand()*1000)}]]
r keydb.cron "cron" single [expr {10000 + int(rand()*1000)}] "return 0" 0;# set delay long enough so it doesn't contend with saving
set saved_digest [r debug digest];# debug digest computes the hash
r save
} {OK};
}
# Load that data back from the file,
# and compare its hash to the previously computed hash
start_server [list tags [list "loadsave"] overrides [list "dir" $server_path] keep_persistence true] {
test "Load various data types from .rdb file" {
set loaded_digest [r debug digest]
if {![string match $saved_digest $loaded_digest]} {
fail "Loaded data did not match saved data"
}
}
}
# Load in data from a redis instance
# The hash should match what we get in redis
set saved_digest 0cff3e9c86eb26ef3b5c0e6bac8315829ad6adf4
exec cp -f tests/assets/redis-save.rdb $server_path/dump.rdb
start_server [list tags [list "loadsave"] overrides [list "dir" $server_path] keep_persistence true] {
test "Load various data types from Redis generated .rdb file" {
set loaded_digest [r debug digest]
puts loaded_digest
if {![string match $saved_digest $loaded_digest]} {
fail "Loaded data did not match saved data"
}
}
}
+1 -1
View File
@@ -215,7 +215,7 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
set used_no_repl [expr {$new_used - $mem_not_counted_for_evict}]
set delta [expr {($used_no_repl - $client_buf) - ($orig_used_no_repl - $orig_client_buf)}]
assert {[$master dbsize] == 100}
assert_equal [$master dbsize] 100
assert {$slave_buf > 2*1024*1024} ;# some of the data may have been pushed to the OS buffers
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}
+88 -1
View File
@@ -450,4 +450,91 @@ start_server {tags {"defrag"} overrides {server-threads 1} } {
}
}
}
} ;# run_solo
start_server {tags {"defrag"} overrides {server-threads 1 active-replica yes} } { ;#test defrag with active-replica enabled
if {[string match {*jemalloc*} [s mem_allocator]]} {
test "Active defrag with active replica" {
r config set save "" ;# prevent bgsave from interfereing with save below
r config set hz 100
r config set activedefrag no
r config set active-defrag-threshold-lower 5
r config set active-defrag-cycle-min 65
r config set active-defrag-cycle-max 75
r config set active-defrag-ignore-bytes 2mb
r config set maxmemory 100mb
r config set maxmemory-policy allkeys-lru
r debug populate 700000 asdf1 150
r debug populate 170000 asdf2 300
r ping ;# trigger eviction following the previous population
after 120 ;# serverCron only updates the info once in 100ms
set frag [s allocator_frag_ratio]
if {$::verbose} {
puts "frag $frag"
}
assert {$frag >= 1.4}
r config set latency-monitor-threshold 5
r latency reset
r config set maxmemory 110mb ;# prevent further eviction (not to fail the digest test)
set digest [r debug digest]
catch {r config set activedefrag yes} e
if {![string match {DISABLED*} $e]} {
# Wait for the active defrag to start working (decision once a
# second).
wait_for_condition 50 100 {
[s active_defrag_running] ne 0
} else {
fail "defrag not started."
}
# Wait for the active defrag to stop working.
catch {
wait_for_condition 150 100 {
[s active_defrag_running] eq 0
} else {
after 120 ;# serverCron only updates the info once in 100ms
puts [r info memory]
puts [r memory malloc-stats]
fail "defrag didn't stop."
}
} e
if {[string match *error* $e]} {
after 120
fail "defrag accessed invalid address."
}
# Test the the fragmentation is lower.
after 120 ;# serverCron only updates the info once in 100ms
set frag [s allocator_frag_ratio]
set max_latency 0
foreach event [r latency latest] {
lassign $event eventname time latency max
if {$eventname == "active-defrag-cycle"} {
set max_latency $max
}
}
if {$::verbose} {
puts "frag $frag"
set misses [s active_defrag_misses]
set hits [s active_defrag_hits]
puts "hits: $hits"
puts "misses: $misses"
puts "max latency $max_latency"
puts [r latency latest]
puts [r latency history active-defrag-cycle]
}
assert {$frag < 1.1}
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
assert {$max_latency <= 30}
} else {
set _ ""
}
# verify the data isn't corrupted or changed
set newdigest [r debug digest]
assert {$digest eq $newdigest}
r save ;# saving an rdb iterates over all the data / pointers
} {OK}
}
}
} ;# run solo
+43
View File
@@ -0,0 +1,43 @@
set server_path [tmpdir "server.rdb-encoding-test"]
set testmodule [file normalize tests/modules/datatype.so]
# Store module data typed data to the database,
# compute the hash of the database,
# and save the data to a file
start_server [list tags [list "loadsave"] overrides [list "dir" $server_path "loadmodule" $testmodule] keep_persistence true] {
test "Save module data type to .rdb file" {
r datatype.set key1 100 stringval
r datatype.set key2 200 stringval
r datatype.set key3 300 stringval
r datatype.set key4 400 stringval
r datatype.set key5 500 stringval
set saved_digest [r debug digest];
r save
} {OK};
}
# Load that data back from the file,
# and compare its hash to the previously computed hash
start_server [list tags [list "loadsave"] overrides [list "dir" $server_path "loadmodule" $testmodule] keep_persistence true] {
test "Load module data type from .rdb file" {
set loaded_digest [r debug digest]
if {![string match $saved_digest $loaded_digest]} {
fail "Loaded data did not match saved data"
}
}
}
# Load in data from a redis instance
# The hash should match what we get in redis
set saved_digest acffad6b89e21339dc5c80f53f6c6fa15977a848
exec cp -f tests/assets/redis-module-save.rdb $server_path/dump.rdb
start_server [list tags [list "loadsave"] overrides [list "dir" $server_path "loadmodule" $testmodule] keep_persistence true] {
test "Load module data type from Redis generated .rdb file" {
set loaded_digest [r debug digest]
if {![string match $saved_digest $loaded_digest]} {
fail "Loaded data did not match saved data"
}
}
}
+54
View File
@@ -0,0 +1,54 @@
start_server {tags {"nested_hash"}} {
test {Simple set/get} {
assert_equal [r keydb.nhset foo bar] 0
assert_equal bar [r keydb.nhget foo]
}
test {overwrite} {
r flushall
assert_equal [r keydb.nhset foo bar] 0
assert_equal [r keydb.nhset foo baz] 1
assert_equal [r keydb.nhget foo] baz
}
test {get simple string} {
r keydb.nhset foo bar
assert_equal [r keydb.nhget json foo] {"bar"}
assert_equal [r keydb.nhget foo] "bar"
}
test {get array} {
r keydb.nhset foo a b c d
assert_equal [r keydb.nhget json foo] {["a","b","c","d"]}
assert_equal [r keydb.nhget foo] {a b c d}
}
test {overwrite string with object} {
r keydb.nhset a.b.c val1
r keydb.nhset a.b val2
r keydb.nhset a.b.c.d val3
assert_equal [r keydb.nhget json a] {{"b" : {"c" : {"d" : "val3"}}}}
}
test {malformed nested key} {
assert_error *syntax* {r keydb.nhset a..b val1}
}
test {missing final selector key} {
assert_error *syntax* {r keydb.nhset a.b. val1}
}
test {expire nested hash} {
r keydb.nhset a.b.c val1
assert_equal [r expire a 100] 1
assert [expr [r ttl a] > 0]
}
test {expire subhash} {
r keydb.nhset a.b.c val1
assert_equal [r expire a.b 100] 0
}
# Save not implemented so ensure we don't crash saving the RDB on shutdown
r flushall
}