Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f83a89f82a | ||
|
|
bf905a78f0 | ||
|
|
fad9483fc9 | ||
|
|
6b6e6f6c1e | ||
|
|
b0a0f03b96 | ||
|
|
f1982ca63d | ||
|
|
25e18a7de9 | ||
|
|
bb090b6d20 | ||
|
|
a9c4e3970f | ||
|
|
d5ecc2347a | ||
|
|
58971a430d | ||
|
|
1512274f4c | ||
|
|
e310d33c12 | ||
|
|
ad32ba0343 | ||
|
|
4e30a5b4e5 | ||
|
|
c8516f3dc4 | ||
|
|
aae0fdc114 | ||
|
|
a19d0f67b9 | ||
|
|
971f69fe1f | ||
|
|
7468c691ad | ||
|
|
620c2ec141 | ||
|
|
3081b6f98b | ||
|
|
8630339e43 | ||
|
|
528d10091f | ||
|
|
149c3cf3dd | ||
|
|
f12c3cf9dd | ||
|
|
1f08a0efb3 | ||
|
|
6cf85d1d01 | ||
|
|
b5101a460d | ||
|
|
a5784ef09e | ||
|
|
122ec25bf0 | ||
|
|
d013ad9eba | ||
|
|
d918ca6fa5 | ||
|
|
852819180f | ||
|
|
218111eb72 | ||
|
|
1531cfb7ee | ||
|
|
16a89f21dc | ||
|
|
79e5160e37 | ||
|
|
510e966dc0 | ||
|
|
8238d3207e | ||
|
|
e37bc4ad40 | ||
|
|
0f78b8b917 | ||
|
|
19061fe04f | ||
|
|
9c901eb65f | ||
|
|
ae33ff3a6e | ||
|
|
59dbd625c1 | ||
|
|
8eb4e355b9 | ||
|
|
8b76b01bfe | ||
|
|
ec57b4b024 | ||
|
|
46d6caa47c | ||
|
|
97b65ce19c | ||
|
|
55d0ef40b0 | ||
|
|
a6c56416fc | ||
|
|
cd3c482d5e | ||
|
|
c5cbb77fc7 | ||
|
|
d7f21f02b3 | ||
|
|
87dc8ce9b3 | ||
|
|
2e22aa1a9c | ||
|
|
8c56dbf971 | ||
|
|
a78169e9e0 | ||
|
|
39a60c8120 | ||
|
|
c57d8760bd | ||
|
|
3eed2a8780 | ||
|
|
23b0268a7f | ||
|
|
3d0de946bb | ||
|
|
866b8ead91 | ||
|
|
aa274960ff | ||
|
|
54d6eb851b | ||
|
|
86d7276f24 | ||
|
|
0918651438 | ||
|
|
e633254ccf | ||
|
|
34f6d94701 | ||
|
|
1e9b8d853d | ||
|
|
2903c56bda | ||
|
|
6b490e33c4 | ||
|
|
891f4a38f6 | ||
|
|
433f763814 | ||
|
|
402b0d0bd1 | ||
|
|
19c3d5b743 | ||
|
|
1212f22858 | ||
|
|
c64aec9ce7 | ||
|
|
a1fb0be1d7 | ||
|
|
bea09a7fa6 | ||
|
|
fb4ee7f0c5 | ||
|
|
842dd85b26 | ||
|
|
f674e83215 | ||
|
|
48d591a010 | ||
|
|
d9d3d3065b | ||
|
|
340a723b87 | ||
|
|
a72ab70fee | ||
|
|
3d053dbb6d | ||
|
|
5baeb14cf3 | ||
|
|
1c0c436757 | ||
|
|
90d720e7a5 | ||
|
|
6ab6a97fe6 | ||
|
|
647a66ebba | ||
|
|
463ccf8664 | ||
|
|
8d7d2be24f | ||
|
|
63a0ffd36a | ||
|
|
a2245f8ff1 | ||
|
|
dd5b105c73 | ||
|
|
74591fb5bd | ||
|
|
9814b2a5f3 | ||
|
|
30091dc29f | ||
|
|
9bf7f302a7 | ||
|
|
ea35a81c42 | ||
|
|
6f4f36c0fb | ||
|
|
a2dbd9bd97 | ||
|
|
f468e653b5 | ||
|
|
b1c7e3393d | ||
|
|
1c0913dc4e | ||
|
|
9f3679880a | ||
|
|
ba809f26d4 | ||
|
|
0a6090bfd8 | ||
|
|
bcac165fab | ||
|
|
843de8b786 | ||
|
|
5ba908fa5f | ||
|
|
5c5197fe4f | ||
|
|
2d450d1315 | ||
|
|
162208f94d | ||
|
|
4e38ced488 | ||
|
|
bc36404c79 | ||
|
|
d3d96b041a | ||
|
|
fa97ef34ca | ||
|
|
4a5057000a | ||
|
|
487601d85d | ||
|
|
3ccdcbc088 | ||
|
|
d044e33c20 | ||
|
|
9e67691ffb | ||
|
|
6de5d25062 | ||
|
|
d490752d58 | ||
|
|
c24e32041b | ||
|
|
f8a9708aa7 | ||
|
|
75648f99a5 | ||
|
|
4de88828d9 | ||
|
|
acba2fc9b4 | ||
|
|
ec0b6bd2c3 | ||
|
|
822a992f91 | ||
|
|
ad223e2042 | ||
|
|
31110f1caf | ||
|
|
270a111433 | ||
|
|
0f0f787a37 | ||
|
|
4cc43a96f6 |
No files matched your search
+4
-3
@@ -4,11 +4,11 @@ matrix:
|
||||
include:
|
||||
- os: linux
|
||||
script: make
|
||||
env: COMPILER_NAME=gcc CXX=g++-5 CC=gcc-5
|
||||
env: COMPILER_NAME=g++-6 CXX=g++-6 CC=gcc-6
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- g++-5
|
||||
- g++-6
|
||||
- nasm
|
||||
- uuid-dev
|
||||
sources: &sources
|
||||
@@ -16,10 +16,11 @@ matrix:
|
||||
- ubuntu-toolchain-r-test
|
||||
- os: linux
|
||||
script: make MALLOC=libc
|
||||
env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8 CXXFLAGS="-stdlib=libc++" LDFLAGS="-stdlib=libc++"
|
||||
env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8 CXXFLAGS="-I/usr/include/libcxxabi/" LDFLAGS="-lc++"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libc++abi-dev
|
||||
- clang-3.8
|
||||
- libc++-dev
|
||||
- libc++abi-dev
|
||||
|
||||
Vendored
-56
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"zmalloc.h": "c",
|
||||
"stat.h": "c",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"cctype": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"list": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"fstream": "cpp",
|
||||
"functional": "cpp",
|
||||
"future": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"memory": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"ostream": "cpp",
|
||||
"ratio": "cpp",
|
||||
"scoped_allocator": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"thread": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"utility": "cpp"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||

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