Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a643c66ca5 | ||
|
|
58d2cd592c | ||
|
|
bc8cc1adb3 | ||
|
|
d03c2de901 | ||
|
|
7d3c5f1e64 | ||
|
|
81573de393 | ||
|
|
a80dd5d001 | ||
|
|
7e0077f293 | ||
|
|
1eb035e6ad | ||
|
|
bdd14ac517 | ||
|
|
63e5d00bdf | ||
|
|
c25fb3d74f | ||
|
|
572d41325b | ||
|
|
e02120cdf7 | ||
|
|
7b1221a74d | ||
|
|
d6b4f994ad | ||
|
|
66a41dafc4 | ||
|
|
f19b9c155e | ||
|
|
5d016fdb6b | ||
|
|
cae9924fd9 | ||
|
|
089f28e8ba | ||
|
|
dcb44d3a09 | ||
|
|
f99cba0b92 | ||
|
|
e10dc0dace | ||
|
|
49a89d636b | ||
|
|
b94f91b9b2 | ||
|
|
06a17c8511 | ||
|
|
eebd70aaa6 | ||
|
|
779c775df9 | ||
|
|
1d4d2925c2 | ||
|
|
b67db7c2d5 | ||
|
|
18953b2998 | ||
|
|
527b7eb074 | ||
|
|
34a97c447f | ||
|
|
1c525e20b1 | ||
|
|
0bcce50285 | ||
|
|
96e0b2a2b1 | ||
|
|
f3794d2c42 | ||
|
|
97fd736923 | ||
|
|
ba950a3d1f | ||
|
|
dad5a902d3 | ||
|
|
3e33fbcd35 | ||
|
|
444555d3e4 | ||
|
|
6a74f524e5 | ||
|
|
9c5b59ac01 | ||
|
|
1840601f8e | ||
|
|
88962af8b2 | ||
|
|
df22cdf6e9 | ||
|
|
041446005b | ||
|
|
7c39a9b0e1 | ||
|
|
05cd92f766 | ||
|
|
d724e7226c | ||
|
|
738c782f02 | ||
|
|
33b05c859a | ||
|
|
ed369d7223 | ||
|
|
fb32bb12b1 | ||
|
|
3e996035ea | ||
|
|
ae8a94d615 | ||
|
|
69a74a567b | ||
|
|
1b46d4f09a | ||
|
|
c6f13892e0 | ||
|
|
05e0f32345 | ||
|
|
8171e6de13 | ||
|
|
b042495f37 |
No files matched your search
@@ -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!
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 \
|
||||
"${@}"
|
||||
@@ -2,4 +2,5 @@
|
||||
*.gcno
|
||||
*.gcov
|
||||
redis.info
|
||||
KeyDB.info
|
||||
lcov-html
|
||||
+11
-7
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
*
|
||||
|
||||
@@ -85,4 +85,6 @@ struct fastlock
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
static_assert(offsetof(struct fastlock, m_ticket) == 64, "ensure padding is correct");
|
||||
#endif
|
||||
+18
-18
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
void freeNestedHashObject(robj_roptr o);
|
||||
|
||||
void nhsetCommand(client *c);
|
||||
void nhgetCommand(client *c);
|
||||
+7
-7
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
@@ -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]
|
||||
}
|
||||
}}}
|
||||
@@ -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
|
||||
|
||||
@@ -67,6 +67,7 @@ start_server {tags {"introspection"}} {
|
||||
io-threads-do-reads
|
||||
tcp-backlog
|
||||
always-show-logo
|
||||
enable-motd
|
||||
syslog-enabled
|
||||
cluster-enabled
|
||||
aclfile
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user