Compare commits
178
Commits
csc2
...
precise-timeout
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae05457825 | ||
|
|
2ff748b2cd | ||
|
|
62bc877386 | ||
|
|
1bc5b6570b | ||
|
|
2ea7f0ecad | ||
|
|
3b29556a0c | ||
|
|
c4d7f30e25 | ||
|
|
57fa355e56 | ||
|
|
f15042dbf0 | ||
|
|
643bc48a00 | ||
|
|
ec007559ff | ||
|
|
c80d81c80a | ||
|
|
61de1c1146 | ||
|
|
918086e253 | ||
|
|
38514e3c8d | ||
|
|
493a7f9823 | ||
|
|
19f5be231d | ||
|
|
7c5dc07016 | ||
|
|
b9e5be5f56 | ||
|
|
89f46f0fa1 | ||
|
|
d1788a5ddb | ||
|
|
34d5982bd5 | ||
|
|
a2732291cd | ||
|
|
2dab5015b7 | ||
|
|
4c08ae3ff6 | ||
|
|
fa9aa90813 | ||
|
|
5634ee973c | ||
|
|
93bb42a0b5 | ||
|
|
1e16b9384d | ||
|
|
5497a44037 | ||
|
|
f9c56dbb09 | ||
|
|
86691ccff5 | ||
|
|
f16eaadd4f | ||
|
|
c1295bb9f1 | ||
|
|
c52415b219 | ||
|
|
82e1807769 | ||
|
|
f6029fb925 | ||
|
|
af5167b7f3 | ||
|
|
d9de9d5478 | ||
|
|
b3a97004f4 | ||
|
|
29b9d0a245 | ||
|
|
771df8a436 | ||
|
|
9321c7871f | ||
|
|
bce9a68b39 | ||
|
|
8609e68161 | ||
|
|
15338ab694 | ||
|
|
f1e4af2c29 | ||
|
|
c46c76a399 | ||
|
|
a6a0e05a1a | ||
|
|
573c4673ee | ||
|
|
681c6f21b7 | ||
|
|
606a01df70 | ||
|
|
453e01a091 | ||
|
|
ebf1acd33c | ||
|
|
0628030bf4 | ||
|
|
cf6cbbb881 | ||
|
|
3c95c92b0f | ||
|
|
22ae0ddf15 | ||
|
|
b1db7acf72 | ||
|
|
5babacad9b | ||
|
|
a5f5091041 | ||
|
|
2091d73ef5 | ||
|
|
513931dfea | ||
|
|
63c4697b46 | ||
|
|
7d703de7e9 | ||
|
|
336458d4b5 | ||
|
|
bd28dbee0e | ||
|
|
2bd360feca | ||
|
|
374b1192a3 | ||
|
|
106a57c5ae | ||
|
|
13707f988b | ||
|
|
af347a8c86 | ||
|
|
27641ee490 | ||
|
|
1b72f4b749 | ||
|
|
2b7b74ad68 | ||
|
|
45ee620e9c | ||
|
|
4983eb6a23 | ||
|
|
3bab69e921 | ||
|
|
94376f46ad | ||
|
|
d16bb64fa4 | ||
|
|
ff024e3064 | ||
|
|
f0acdee4c5 | ||
|
|
2f3bfd1c13 | ||
|
|
5e2319c326 | ||
|
|
f88f8661ac | ||
|
|
d3e8f3fb62 | ||
|
|
bd07f121b9 | ||
|
|
6dd8de1774 | ||
|
|
7ca81170c7 | ||
|
|
98b23cce29 | ||
|
|
c8ae90fef6 | ||
|
|
8a3e9a6d44 | ||
|
|
86cbddc475 | ||
|
|
4ccb56d0e6 | ||
|
|
d7f18f5019 | ||
|
|
f0d9a4e1a4 | ||
|
|
d4e375fc1e | ||
|
|
45f318d2ff | ||
|
|
cf31602bce | ||
|
|
7e64aa80d8 | ||
|
|
9abdc089f8 | ||
|
|
43f39f2e37 | ||
|
|
ec73881a4d | ||
|
|
d8ab7e8012 | ||
|
|
e77db59660 | ||
|
|
652c532292 | ||
|
|
ca825701dc | ||
|
|
f7e05dc755 | ||
|
|
4f29753726 | ||
|
|
37612118c8 | ||
|
|
049969971e | ||
|
|
d2c5f80e2e | ||
|
|
79de8f3c25 | ||
|
|
685dcaa1e6 | ||
|
|
2f1a1c3835 | ||
|
|
c913be0464 | ||
|
|
537893420b | ||
|
|
1ab5508324 | ||
|
|
319a6831a9 | ||
|
|
07acdd4aa8 | ||
|
|
b6378edcd6 | ||
|
|
3fbb41ecc9 | ||
|
|
73305861f5 | ||
|
|
0a643efa0c | ||
|
|
376a806bfb | ||
|
|
a6c7e61e61 | ||
|
|
62adabd0e0 | ||
|
|
28bde89172 | ||
|
|
743cfc0ad6 | ||
|
|
9947956d7b | ||
|
|
8341a7d472 | ||
|
|
e78c4e813c | ||
|
|
c6954de3ea | ||
|
|
8a14fff545 | ||
|
|
a8d70ac568 | ||
|
|
ab8c383082 | ||
|
|
c552fad6d4 | ||
|
|
155526a44e | ||
|
|
e741b0c257 | ||
|
|
9fe02ce1cd | ||
|
|
42c4d6aec7 | ||
|
|
c8c78bd4b9 | ||
|
|
770cb0ba97 | ||
|
|
485425cec7 | ||
|
|
7d4ebe114f | ||
|
|
6fff2cf9b6 | ||
|
|
c1b5220ee7 | ||
|
|
d63a435362 | ||
|
|
df45fed050 | ||
|
|
20eeddfb8a | ||
|
|
ddb80bb3d4 | ||
|
|
8a44b2cc7e | ||
|
|
090bc0c1a3 | ||
|
|
c21c23bbba | ||
|
|
8aa0b19d83 | ||
|
|
f54bb2a330 | ||
|
|
dc8f947d7c | ||
|
|
67dbc5f365 | ||
|
|
256ec6c52f | ||
|
|
ef764dde1c | ||
|
|
46216b0e83 | ||
|
|
5c73a6e206 | ||
|
|
c8c3281f06 | ||
|
|
28ef18a894 | ||
|
|
774d8cd721 | ||
|
|
1c7a2269d6 | ||
|
|
6f2240aefd | ||
|
|
bd60c11bd8 | ||
|
|
dd05b7f67f | ||
|
|
67ee87522a | ||
|
|
3f6d00d387 | ||
|
|
1d4ea00d12 | ||
|
|
3848849013 | ||
|
|
a3b0d8631f | ||
|
|
af02478ba0 | ||
|
|
337dcde9fa | ||
|
|
dfb598cf33 | ||
|
|
5e0fe4c77c |
+11
-10
@@ -3,11 +3,8 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, ubuntu-16.04]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
test-ubuntu-latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
@@ -15,13 +12,17 @@ jobs:
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5
|
||||
make test
|
||||
./runtest --clients 2 --verbose
|
||||
|
||||
build-ubuntu-old:
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: make
|
||||
|
||||
build-macos-latest:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [macos-latest, macOS-10.14]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
|
||||
@@ -32,3 +32,4 @@ deps/lua/src/liblua.a
|
||||
*.dSYM
|
||||
Makefile.dep
|
||||
.vscode/*
|
||||
.idea/*
|
||||
|
||||
Vendored
+24
-1
@@ -21,7 +21,7 @@ So what usually happens is either:
|
||||
|
||||
The result is a pollution of binaries without line editing support.
|
||||
|
||||
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
|
||||
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
|
||||
|
||||
## Terminals, in 2010.
|
||||
|
||||
@@ -126,6 +126,24 @@ Linenoise has direct support for persisting the history into an history
|
||||
file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do
|
||||
just that. Both functions return -1 on error and 0 on success.
|
||||
|
||||
## Mask mode
|
||||
|
||||
Sometimes it is useful to allow the user to type passwords or other
|
||||
secrets that should not be displayed. For such situations linenoise supports
|
||||
a "mask mode" that will just replace the characters the user is typing
|
||||
with `*` characters, like in the following example:
|
||||
|
||||
$ ./linenoise_example
|
||||
hello> get mykey
|
||||
echo: 'get mykey'
|
||||
hello> /mask
|
||||
hello> *********
|
||||
|
||||
You can enable and disable mask mode using the following two functions:
|
||||
|
||||
void linenoiseMaskModeEnable(void);
|
||||
void linenoiseMaskModeDisable(void);
|
||||
|
||||
## Completion
|
||||
|
||||
Linenoise supports completion, which is the ability to complete the user
|
||||
@@ -222,3 +240,8 @@ Sometimes you may want to clear the screen as a result of something the
|
||||
user typed. You can do this by calling the following function:
|
||||
|
||||
void linenoiseClearScreen(void);
|
||||
|
||||
## Related projects
|
||||
|
||||
* [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like UTF-8 support, Windows support and other features. Uses C++ instead of C as development language.
|
||||
* [Linenoise-swift](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift.
|
||||
|
||||
Vendored
+5
@@ -55,6 +55,7 @@ int main(int argc, char **argv) {
|
||||
*
|
||||
* The typed string is returned as a malloc() allocated string by
|
||||
* linenoise, so the user needs to free() it. */
|
||||
|
||||
while((line = linenoise("hello> ")) != NULL) {
|
||||
/* Do something with the string. */
|
||||
if (line[0] != '\0' && line[0] != '/') {
|
||||
@@ -65,6 +66,10 @@ int main(int argc, char **argv) {
|
||||
/* The "/historylen" command will change the history len. */
|
||||
int len = atoi(line+11);
|
||||
linenoiseHistorySetMaxLen(len);
|
||||
} else if (!strncmp(line, "/mask", 5)) {
|
||||
linenoiseMaskModeEnable();
|
||||
} else if (!strncmp(line, "/unmask", 7)) {
|
||||
linenoiseMaskModeDisable();
|
||||
} else if (line[0] == '/') {
|
||||
printf("Unreconized command: %s\n", line);
|
||||
}
|
||||
|
||||
Vendored
+29
-3
@@ -125,6 +125,7 @@ static linenoiseHintsCallback *hintsCallback = NULL;
|
||||
static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
|
||||
|
||||
static struct termios orig_termios; /* In order to restore at exit.*/
|
||||
static int maskmode = 0; /* Show "***" instead of input. For passwords. */
|
||||
static int rawmode = 0; /* For atexit() function to check if restore is needed*/
|
||||
static int mlmode = 0; /* Multi line mode. Default is single line. */
|
||||
static int atexit_registered = 0; /* Register atexit just 1 time. */
|
||||
@@ -197,6 +198,19 @@ FILE *lndebug_fp = NULL;
|
||||
|
||||
/* ======================= Low level terminal handling ====================== */
|
||||
|
||||
/* Enable "mask mode". When it is enabled, instead of the input that
|
||||
* the user is typing, the terminal will just display a corresponding
|
||||
* number of asterisks, like "****". This is useful for passwords and other
|
||||
* secrets that should not be displayed. */
|
||||
void linenoiseMaskModeEnable(void) {
|
||||
maskmode = 1;
|
||||
}
|
||||
|
||||
/* Disable mask mode. */
|
||||
void linenoiseMaskModeDisable(void) {
|
||||
maskmode = 0;
|
||||
}
|
||||
|
||||
/* Set if to use or not the multi line mode. */
|
||||
void linenoiseSetMultiLine(int ml) {
|
||||
mlmode = ml;
|
||||
@@ -485,6 +499,8 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
|
||||
if (bold == 1 && color == -1) color = 37;
|
||||
if (color != -1 || bold != 0)
|
||||
snprintf(seq,64,"\033[%d;%d;49m",bold,color);
|
||||
else
|
||||
seq[0] = '\0';
|
||||
abAppend(ab,seq,strlen(seq));
|
||||
abAppend(ab,hint,hintlen);
|
||||
if (color != -1 || bold != 0)
|
||||
@@ -523,7 +539,11 @@ static void refreshSingleLine(struct linenoiseState *l) {
|
||||
abAppend(&ab,seq,strlen(seq));
|
||||
/* Write the prompt and the current buffer content */
|
||||
abAppend(&ab,l->prompt,strlen(l->prompt));
|
||||
abAppend(&ab,buf,len);
|
||||
if (maskmode == 1) {
|
||||
while (len--) abAppend(&ab,"*",1);
|
||||
} else {
|
||||
abAppend(&ab,buf,len);
|
||||
}
|
||||
/* Show hits if any. */
|
||||
refreshShowHints(&ab,l,plen);
|
||||
/* Erase to right */
|
||||
@@ -577,7 +597,12 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
|
||||
/* Write the prompt and the current buffer content */
|
||||
abAppend(&ab,l->prompt,strlen(l->prompt));
|
||||
abAppend(&ab,l->buf,l->len);
|
||||
if (maskmode == 1) {
|
||||
unsigned int i;
|
||||
for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
|
||||
} else {
|
||||
abAppend(&ab,l->buf,l->len);
|
||||
}
|
||||
|
||||
/* Show hits if any. */
|
||||
refreshShowHints(&ab,l,plen);
|
||||
@@ -645,7 +670,8 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) {
|
||||
if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
|
||||
/* Avoid a full update of the line in the
|
||||
* trivial case. */
|
||||
if (write(l->ofd,&c,1) == -1) return -1;
|
||||
char d = (maskmode==1) ? '*' : c;
|
||||
if (write(l->ofd,&d,1) == -1) return -1;
|
||||
} else {
|
||||
refreshLine(l);
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -65,6 +65,8 @@ int linenoiseHistoryLoad(const char *filename);
|
||||
void linenoiseClearScreen(void);
|
||||
void linenoiseSetMultiLine(int ml);
|
||||
void linenoisePrintKeyCodes(void);
|
||||
void linenoiseMaskModeEnable(void);
|
||||
void linenoiseMaskModeDisable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Vendored
+6
-4
@@ -89,12 +89,14 @@ typedef struct Header {
|
||||
} Header;
|
||||
|
||||
|
||||
static int getnum (const char **fmt, int df) {
|
||||
static int getnum (lua_State *L, const char **fmt, int df) {
|
||||
if (!isdigit(**fmt)) /* no number? */
|
||||
return df; /* return default value */
|
||||
else {
|
||||
int a = 0;
|
||||
do {
|
||||
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
|
||||
luaL_error(L, "integral size overflow");
|
||||
a = a*10 + *((*fmt)++) - '0';
|
||||
} while (isdigit(**fmt));
|
||||
return a;
|
||||
@@ -115,9 +117,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
|
||||
case 'f': return sizeof(float);
|
||||
case 'd': return sizeof(double);
|
||||
case 'x': return 1;
|
||||
case 'c': return getnum(fmt, 1);
|
||||
case 'c': return getnum(L, fmt, 1);
|
||||
case 'i': case 'I': {
|
||||
int sz = getnum(fmt, sizeof(int));
|
||||
int sz = getnum(L, fmt, sizeof(int));
|
||||
if (sz > MAXINTSIZE)
|
||||
luaL_error(L, "integral size %d is larger than limit of %d",
|
||||
sz, MAXINTSIZE);
|
||||
@@ -150,7 +152,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
|
||||
case '>': h->endian = BIG; return;
|
||||
case '<': h->endian = LITTLE; return;
|
||||
case '!': {
|
||||
int a = getnum(fmt, MAXALIGN);
|
||||
int a = getnum(L, fmt, MAXALIGN);
|
||||
if (!isp2(a))
|
||||
luaL_error(L, "alignment %d is not a power of 2", a);
|
||||
h->align = a;
|
||||
|
||||
+17
-4
@@ -142,7 +142,8 @@ tcp-keepalive 300
|
||||
# server to connected clients, masters or cluster peers. These files should be
|
||||
# PEM formatted.
|
||||
#
|
||||
# tls-cert-file redis.crt tls-key-file redis.key
|
||||
# tls-cert-file redis.crt
|
||||
# tls-key-file redis.key
|
||||
|
||||
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
|
||||
#
|
||||
@@ -175,8 +176,7 @@ tcp-keepalive 300
|
||||
# tls-cluster yes
|
||||
|
||||
# Explicitly specify TLS versions to support. Allowed values are case insensitive
|
||||
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
|
||||
# "default" which is currently >= TLSv1.1.
|
||||
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1)
|
||||
#
|
||||
# tls-protocols TLSv1.2
|
||||
|
||||
@@ -321,6 +321,19 @@ rdbchecksum yes
|
||||
# The filename where to dump the DB
|
||||
dbfilename dump.rdb
|
||||
|
||||
# Remove RDB files used by replication in instances without persistence
|
||||
# enabled. By default this option is disabled, however there are environments
|
||||
# where for regulations or other security concerns, RDB files persisted on
|
||||
# disk by masters in order to feed replicas, or stored on disk by replicas
|
||||
# in order to load them for the initial synchronization, should be deleted
|
||||
# ASAP. Note that this option ONLY WORKS in instances that have both AOF
|
||||
# and RDB persistence disabled, otherwise is completely ignored.
|
||||
#
|
||||
# An alternative (and sometimes better) way to obtain the same effect is
|
||||
# to use diskless replication on both master and replicas instances. However
|
||||
# in the case of replicas, diskless is not always an option.
|
||||
rdb-del-sync-files no
|
||||
|
||||
# The working directory.
|
||||
#
|
||||
# The DB will be written inside this directory, with the filename specified
|
||||
@@ -1615,7 +1628,7 @@ hz 10
|
||||
# offers, and enables by default, the ability to use an adaptive HZ value
|
||||
# which will temporary raise when there are many connected clients.
|
||||
#
|
||||
# When dynamic HZ is enabled, the actual configured HZ will be used as
|
||||
# When dynamic HZ is enabled, the actual configured HZ will be used
|
||||
# as a baseline, but multiples of the configured HZ value will be actually
|
||||
# used as needed once more clients are connected. In this way an idle
|
||||
# instance will use very little CPU time while a busy instance will be
|
||||
|
||||
@@ -102,6 +102,18 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
#
|
||||
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
|
||||
|
||||
# sentinel auth-user <master-name> <username>
|
||||
#
|
||||
# This is useful in order to authenticate to instances having ACL capabilities,
|
||||
# that is, running Redis 6.0 or greater. When just auth-pass is provided the
|
||||
# Sentinel instance will authenticate to Redis using the old "AUTH <pass>"
|
||||
# method. When also an username is provided, it will use "AUTH <user> <pass>".
|
||||
# In the Redis servers side, the ACL to provide just minimal access to
|
||||
# Sentinel instances, should be configured along the following lines:
|
||||
#
|
||||
# user sentinel-user >somepassword +client +subscribe +publish \
|
||||
# +ping +info +multi +slaveof +config +client +exec on
|
||||
|
||||
# sentinel down-after-milliseconds <master-name> <milliseconds>
|
||||
#
|
||||
# Number of milliseconds the master (or any attached replica or sentinel) should
|
||||
@@ -112,6 +124,14 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
# Default is 30 seconds.
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
|
||||
# requirepass <password>
|
||||
#
|
||||
# You can configure Sentinel itself to require a password, however when doing
|
||||
# so Sentinel will try to authenticate with the same password to all the
|
||||
# other Sentinels. So you need to configure all your Sentinels in a given
|
||||
# group with the same "requirepass" password. Check the following documentation
|
||||
# for more info: https://redis.io/topics/sentinel
|
||||
|
||||
# sentinel parallel-syncs <master-name> <numreplicas>
|
||||
#
|
||||
# How many replicas we can reconfigure to point to the new replica simultaneously
|
||||
|
||||
+2
-2
@@ -208,9 +208,9 @@ REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-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 lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o
|
||||
REDIS_CHECK_RDB_NAME=redis-check-rdb
|
||||
REDIS_CHECK_AOF_NAME=redis-check-aof
|
||||
|
||||
|
||||
@@ -899,16 +899,6 @@ char *ACLSetUserStringError(void) {
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
/* Return the first password of the default user or NULL.
|
||||
* This function is needed for backward compatibility with the old
|
||||
* directive "requirepass" when Redis supported a single global
|
||||
* password. */
|
||||
sds ACLDefaultUserFirstPassword(void) {
|
||||
if (listLength(DefaultUser->passwords) == 0) return NULL;
|
||||
listNode *first = listFirst(DefaultUser->passwords);
|
||||
return listNodeValue(first);
|
||||
}
|
||||
|
||||
/* Initialize the default user, that will always exist for all the process
|
||||
* lifetime. */
|
||||
void ACLInitDefaultUser(void) {
|
||||
@@ -925,6 +915,7 @@ void ACLInit(void) {
|
||||
UsersToLoad = listCreate();
|
||||
ACLLog = listCreate();
|
||||
ACLInitDefaultUser();
|
||||
server.requirepass = NULL; /* Only used for backward compatibility. */
|
||||
}
|
||||
|
||||
/* Check the username and password pair and return C_OK if they are valid,
|
||||
@@ -1830,6 +1821,7 @@ void aclCommand(client *c) {
|
||||
case ACL_DENIED_CMD: reasonstr="command"; break;
|
||||
case ACL_DENIED_KEY: reasonstr="key"; break;
|
||||
case ACL_DENIED_AUTH: reasonstr="auth"; break;
|
||||
default: reasonstr="unknown";
|
||||
}
|
||||
addReplyBulkCString(c,reasonstr);
|
||||
|
||||
|
||||
@@ -135,6 +135,14 @@ void aeDeleteEventLoop(aeEventLoop *eventLoop) {
|
||||
aeApiFree(eventLoop);
|
||||
zfree(eventLoop->events);
|
||||
zfree(eventLoop->fired);
|
||||
|
||||
/* Free the time events list. */
|
||||
aeTimeEvent *next_te, *te = eventLoop->timeEventHead;
|
||||
while (te) {
|
||||
next_te = te->next;
|
||||
zfree(te);
|
||||
te = next_te;
|
||||
}
|
||||
zfree(eventLoop);
|
||||
}
|
||||
|
||||
@@ -456,6 +464,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
if (!invert && fe->mask & mask & AE_READABLE) {
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
|
||||
}
|
||||
|
||||
/* Fire the writable event. */
|
||||
@@ -468,8 +477,11 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
|
||||
/* If we have to invert the call, fire the readable event now
|
||||
* after the writable one. */
|
||||
if (invert && fe->mask & mask & AE_READABLE) {
|
||||
if (!fired || fe->wfileProc != fe->rfileProc) {
|
||||
if (invert) {
|
||||
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
|
||||
if ((fe->mask & mask & AE_READABLE) &&
|
||||
(!fired || fe->wfileProc != fe->rfileProc))
|
||||
{
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
}
|
||||
|
||||
@@ -1212,12 +1212,13 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
/* Use the XADD MAXLEN 0 trick to generate an empty stream if
|
||||
* the key we are serializing is an empty string, which is possible
|
||||
* for the Stream type. */
|
||||
id.ms = 0; id.seq = 1;
|
||||
if (rioWriteBulkCount(r,'*',7) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"0",1) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&id) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"x",1) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"y",1) == 0) return 0;
|
||||
}
|
||||
@@ -1798,14 +1799,15 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error condition. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated by signal %d", bysignal);
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
char *ascii_logo =
|
||||
const char *ascii_logo =
|
||||
" _._ \n"
|
||||
" _.-``__ ''-._ \n"
|
||||
" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n"
|
||||
|
||||
+21
-1
@@ -902,6 +902,9 @@ void bitposCommand(client *c) {
|
||||
* OVERFLOW [WRAP|SAT|FAIL]
|
||||
*/
|
||||
|
||||
#define BITFIELD_FLAG_NONE 0
|
||||
#define BITFIELD_FLAG_READONLY (1<<0)
|
||||
|
||||
struct bitfieldOp {
|
||||
uint64_t offset; /* Bitfield offset. */
|
||||
int64_t i64; /* Increment amount (INCRBY) or SET value */
|
||||
@@ -911,7 +914,10 @@ struct bitfieldOp {
|
||||
int sign; /* True if signed, otherwise unsigned op. */
|
||||
};
|
||||
|
||||
void bitfieldCommand(client *c) {
|
||||
/* This implements both the BITFIELD command and the BITFIELD_RO command
|
||||
* when flags is set to BITFIELD_FLAG_READONLY: in this case only the
|
||||
* GET subcommand is allowed, other subcommands will return an error. */
|
||||
void bitfieldGeneric(client *c, int flags) {
|
||||
robj *o;
|
||||
size_t bitoffset;
|
||||
int j, numops = 0, changes = 0;
|
||||
@@ -999,6 +1005,12 @@ void bitfieldCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (flags & BITFIELD_FLAG_READONLY) {
|
||||
zfree(ops);
|
||||
addReplyError(c, "BITFIELD_RO only supports the GET subcommand");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lookup by making room up to the farest bit reached by
|
||||
* this operation. */
|
||||
if ((o = lookupStringForBitCommand(c,
|
||||
@@ -1129,3 +1141,11 @@ void bitfieldCommand(client *c) {
|
||||
}
|
||||
zfree(ops);
|
||||
}
|
||||
|
||||
void bitfieldCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_NONE);
|
||||
}
|
||||
|
||||
void bitfieldroCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_READONLY);
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ void blockClient(client *c, int btype) {
|
||||
c->btype = btype;
|
||||
server.blocked_clients++;
|
||||
server.blocked_clients_by_type[btype]++;
|
||||
addClientToShortTimeoutTable(c);
|
||||
}
|
||||
|
||||
/* This function is called in the beforeSleep() function of the event loop
|
||||
|
||||
+9
-5
@@ -681,9 +681,10 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* or schedule it for later depending on connection implementation.
|
||||
*/
|
||||
if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node connection: %s",
|
||||
connGetLastError(conn));
|
||||
if (connGetState(conn) == CONN_STATE_ERROR)
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node connection: %s",
|
||||
connGetLastError(conn));
|
||||
connClose(conn);
|
||||
return;
|
||||
}
|
||||
@@ -4261,7 +4262,7 @@ void clusterCommand(client *c) {
|
||||
"FORGET <node-id> -- Remove a node from the cluster.",
|
||||
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
|
||||
"FLUSHSLOTS -- Delete current node own slots information.",
|
||||
"INFO - Return onformation about the cluster.",
|
||||
"INFO - Return information about the cluster.",
|
||||
"KEYSLOT <key> -- Return the hash slot for <key>.",
|
||||
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
|
||||
"MYID -- Return the node id.",
|
||||
@@ -4272,6 +4273,7 @@ void clusterCommand(client *c) {
|
||||
"SET-config-epoch <epoch> - Set config epoch of current node.",
|
||||
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
||||
"REPLICAS <node-id> -- Return <node-id> replicas.",
|
||||
"SAVECONFIG - Force saving cluster configuration on disk.",
|
||||
"SLOTS -- Return information about slots range mappings. Each range is made of:",
|
||||
" start, end, master and replicas IP addresses, ports and ids",
|
||||
NULL
|
||||
@@ -4981,6 +4983,7 @@ void restoreCommand(client *c) {
|
||||
}
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",c->argv[1],c->db->id);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -5327,6 +5330,7 @@ try_again:
|
||||
/* No COPY option: remove the local key, signal the change. */
|
||||
dbDelete(c->db,kv[j]);
|
||||
signalModifiedKey(c->db,kv[j]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",kv[j],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
/* Populate the argument vector to replace the old one. */
|
||||
@@ -5489,7 +5493,7 @@ void readwriteCommand(client *c) {
|
||||
* already "down" but it is fragile to rely on the update of the global state,
|
||||
* so we also handle it here.
|
||||
*
|
||||
* CLUSTER_REDIR_DOWN_STATE and CLUSTER_REDIR_DOWN_RO_STATE if the cluster is
|
||||
* CLUSTER_REDIR_DOWN_STATE and CLUSTER_REDIR_DOWN_RO_STATE if the cluster is
|
||||
* down but the user attempts to execute a command that addresses one or more keys. */
|
||||
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *error_code) {
|
||||
clusterNode *n = NULL;
|
||||
|
||||
+23
-14
@@ -108,12 +108,12 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
|
||||
/* Generic config infrastructure function pointers
|
||||
* int is_valid_fn(val, err)
|
||||
* Return 1 when val is valid, and 0 when invalid.
|
||||
* Optionslly set err to a static error string.
|
||||
* Optionally set err to a static error string.
|
||||
* int update_fn(val, prev, err)
|
||||
* This function is called only for CONFIG SET command (not at config file parsing)
|
||||
* It is called after the actual config is applied,
|
||||
* Return 1 for success, and 0 for failure.
|
||||
* Optionslly set err to a static error string.
|
||||
* Optionally set err to a static error string.
|
||||
* On failure the config change will be reverted.
|
||||
*/
|
||||
|
||||
@@ -411,11 +411,15 @@ void loadServerConfigFromString(char *config) {
|
||||
goto loaderr;
|
||||
}
|
||||
/* The old "requirepass" directive just translates to setting
|
||||
* a password to the default user. */
|
||||
* a password to the default user. The only thing we do
|
||||
* additionally is to remember the cleartext password in this
|
||||
* case, for backward compatibility with Redis <= 5. */
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
sdsfree(server.requirepass);
|
||||
server.requirepass = sdsnew(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
|
||||
/* DEAD OPTION */
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
|
||||
@@ -518,7 +522,8 @@ void loadServerConfigFromString(char *config) {
|
||||
return;
|
||||
|
||||
loaderr:
|
||||
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n");
|
||||
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (Redis %s) ***\n",
|
||||
REDIS_VERSION);
|
||||
fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
|
||||
fprintf(stderr, ">>> '%s'\n", lines[i]);
|
||||
fprintf(stderr, "%s\n", err);
|
||||
@@ -622,11 +627,15 @@ void configSetCommand(client *c) {
|
||||
config_set_special_field("requirepass") {
|
||||
if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
|
||||
/* The old "requirepass" directive just translates to setting
|
||||
* a password to the default user. */
|
||||
* a password to the default user. The only thing we do
|
||||
* additionally is to remember the cleartext password in this
|
||||
* case, for backward compatibility with Redis <= 5. */
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)o->ptr);
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
sdsfree(server.requirepass);
|
||||
server.requirepass = sdsnew(o->ptr);
|
||||
} config_set_special_field("save") {
|
||||
int vlen, j;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
@@ -729,7 +738,7 @@ void configSetCommand(client *c) {
|
||||
* config_set_memory_field(name,var) */
|
||||
} config_set_memory_field(
|
||||
"client-query-buffer-limit",server.client_max_querybuf_len) {
|
||||
/* Everyhing else is an error... */
|
||||
/* Everything else is an error... */
|
||||
} config_set_else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
@@ -898,7 +907,7 @@ void configGetCommand(client *c) {
|
||||
}
|
||||
if (stringmatch(pattern,"requirepass",1)) {
|
||||
addReplyBulkCString(c,"requirepass");
|
||||
sds password = ACLDefaultUserFirstPassword();
|
||||
sds password = server.requirepass;
|
||||
if (password) {
|
||||
addReplyBulkCBuffer(c,password,sdslen(password));
|
||||
} else {
|
||||
@@ -1340,7 +1349,7 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
|
||||
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, char *option) {
|
||||
int force = 1;
|
||||
sds line;
|
||||
sds password = ACLDefaultUserFirstPassword();
|
||||
sds password = server.requirepass;
|
||||
|
||||
/* If there is no password set, we don't want the requirepass option
|
||||
* to be present in the configuration at all. */
|
||||
@@ -1666,16 +1675,15 @@ static int enumConfigSet(typeData data, sds value, int update, char **err) {
|
||||
sds enumerr = sdsnew("argument must be one of the following: ");
|
||||
configEnum *enumNode = data.enumd.enum_value;
|
||||
while(enumNode->name != NULL) {
|
||||
enumerr = sdscatlen(enumerr, enumNode->name, strlen(enumNode->name));
|
||||
enumerr = sdscatlen(enumerr, enumNode->name,
|
||||
strlen(enumNode->name));
|
||||
enumerr = sdscatlen(enumerr, ", ", 2);
|
||||
enumNode++;
|
||||
}
|
||||
sdsrange(enumerr,0,-3); /* Remove final ", ". */
|
||||
|
||||
enumerr[sdslen(enumerr) - 2] = '\0';
|
||||
|
||||
/* Make sure we don't overrun the fixed buffer */
|
||||
enumerr[LOADBUF_SIZE - 1] = '\0';
|
||||
strncpy(loadbuf, enumerr, LOADBUF_SIZE);
|
||||
loadbuf[LOADBUF_SIZE - 1] = '\0';
|
||||
|
||||
sdsfree(enumerr);
|
||||
*err = loadbuf;
|
||||
@@ -2084,6 +2092,7 @@ standardConfig configs[] = {
|
||||
createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, server.always_show_logo, 0, NULL, NULL),
|
||||
createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, server.protected_mode, 1, NULL, NULL),
|
||||
createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, server.rdb_compression, 1, NULL, NULL),
|
||||
createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, server.rdb_del_sync_files, 0, NULL, NULL),
|
||||
createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, server.activerehashing, 1, NULL, NULL),
|
||||
createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, server.stop_writes_on_bgsave_err, 1, NULL, NULL),
|
||||
createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, server.dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/
|
||||
@@ -2160,7 +2169,6 @@ standardConfig configs[] = {
|
||||
createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.key_load_delay, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("tracking-table-max-fill", NULL, MODIFIABLE_CONFIG, 0, 100, server.tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 10% tracking table max number of keys tracked. */
|
||||
createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, server.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */
|
||||
createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ),
|
||||
createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves),
|
||||
@@ -2195,6 +2203,7 @@ standardConfig configs[] = {
|
||||
createSizeTConfig("stream-node-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.stream_node_max_bytes, 4096, MEMORY_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */
|
||||
|
||||
/* Other configs */
|
||||
createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */
|
||||
|
||||
+9
-3
@@ -152,7 +152,7 @@ static void connSocketClose(connection *conn) {
|
||||
/* If called from within a handler, schedule the close but
|
||||
* keep the connection until the handler returns.
|
||||
*/
|
||||
if (conn->flags & CONN_FLAG_IN_HANDLER) {
|
||||
if (connHasRefs(conn)) {
|
||||
conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
|
||||
return;
|
||||
}
|
||||
@@ -183,10 +183,16 @@ static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
|
||||
}
|
||||
|
||||
static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||
int ret = C_OK;
|
||||
|
||||
if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
if (!callHandler(conn, accept_handler)) return C_ERR;
|
||||
return C_OK;
|
||||
|
||||
connIncrRefs(conn);
|
||||
if (!callHandler(conn, accept_handler)) ret = C_ERR;
|
||||
connDecrRefs(conn);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Register a write handler, to be called when the connection is writable.
|
||||
|
||||
+11
-4
@@ -45,9 +45,8 @@ typedef enum {
|
||||
CONN_STATE_ERROR
|
||||
} ConnectionState;
|
||||
|
||||
#define CONN_FLAG_IN_HANDLER (1<<0) /* A handler execution is in progress */
|
||||
#define CONN_FLAG_CLOSE_SCHEDULED (1<<1) /* Closed scheduled by a handler */
|
||||
#define CONN_FLAG_WRITE_BARRIER (1<<2) /* Write barrier requested */
|
||||
#define CONN_FLAG_CLOSE_SCHEDULED (1<<0) /* Closed scheduled by a handler */
|
||||
#define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */
|
||||
|
||||
typedef void (*ConnectionCallbackFunc)(struct connection *conn);
|
||||
|
||||
@@ -70,7 +69,8 @@ typedef struct ConnectionType {
|
||||
struct connection {
|
||||
ConnectionType *type;
|
||||
ConnectionState state;
|
||||
int flags;
|
||||
short int flags;
|
||||
short int refs;
|
||||
int last_errno;
|
||||
void *private_data;
|
||||
ConnectionCallbackFunc conn_handler;
|
||||
@@ -88,6 +88,13 @@ struct connection {
|
||||
* connAccept() may directly call accept_handler(), or return and call it
|
||||
* at a later time. This behavior is a bit awkward but aims to reduce the need
|
||||
* to wait for the next event loop, if no additional handshake is required.
|
||||
*
|
||||
* IMPORTANT: accept_handler may decide to close the connection, calling connClose().
|
||||
* To make this safe, the connection is only marked with CONN_FLAG_CLOSE_SCHEDULED
|
||||
* in this case, and connAccept() returns with an error.
|
||||
*
|
||||
* connAccept() callers must always check the return value and on error (C_ERR)
|
||||
* a connClose() must be called.
|
||||
*/
|
||||
|
||||
static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||
|
||||
+28
-25
@@ -37,46 +37,49 @@
|
||||
* implementations (currently sockets in connection.c and TLS in tls.c).
|
||||
*
|
||||
* Currently helpers implement the mechanisms for invoking connection
|
||||
* handlers, tracking in-handler states and dealing with deferred
|
||||
* destruction (if invoked by a handler).
|
||||
* handlers and tracking connection references, to allow safe destruction
|
||||
* of connections from within a handler.
|
||||
*/
|
||||
|
||||
/* Called whenever a handler is invoked on a connection and sets the
|
||||
* CONN_FLAG_IN_HANDLER flag to indicate we're in a handler context.
|
||||
/* Incremenet connection references.
|
||||
*
|
||||
* An attempt to close a connection while CONN_FLAG_IN_HANDLER is
|
||||
* set will result with deferred close, i.e. setting the CONN_FLAG_CLOSE_SCHEDULED
|
||||
* instead of destructing it.
|
||||
* Inside a connection handler, we guarantee refs >= 1 so it is always
|
||||
* safe to connClose().
|
||||
*
|
||||
* In other cases where we don't want to prematurely lose the connection,
|
||||
* it can go beyond 1 as well; currently it is only done by connAccept().
|
||||
*/
|
||||
static inline void enterHandler(connection *conn) {
|
||||
conn->flags |= CONN_FLAG_IN_HANDLER;
|
||||
static inline void connIncrRefs(connection *conn) {
|
||||
conn->refs++;
|
||||
}
|
||||
|
||||
/* Called whenever a handler returns. This unsets the CONN_FLAG_IN_HANDLER
|
||||
* flag and performs actual close/destruction if a deferred close was
|
||||
* scheduled by the handler.
|
||||
/* Decrement connection references.
|
||||
*
|
||||
* Note that this is not intended to provide any automatic free logic!
|
||||
* callHandler() takes care of that for the common flows, and anywhere an
|
||||
* explicit connIncrRefs() is used, the caller is expected to take care of
|
||||
* that.
|
||||
*/
|
||||
static inline int exitHandler(connection *conn) {
|
||||
conn->flags &= ~CONN_FLAG_IN_HANDLER;
|
||||
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||
connClose(conn);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
||||
static inline void connDecrRefs(connection *conn) {
|
||||
conn->refs--;
|
||||
}
|
||||
|
||||
static inline int connHasRefs(connection *conn) {
|
||||
return conn->refs;
|
||||
}
|
||||
|
||||
/* Helper for connection implementations to call handlers:
|
||||
* 1. Mark the handler in use.
|
||||
* 1. Increment refs to protect the connection.
|
||||
* 2. Execute the handler (if set).
|
||||
* 3. Mark the handler as NOT in use and perform deferred close if was
|
||||
* requested by the handler at any time.
|
||||
* 3. Decrement refs and perform deferred close, if refs==0.
|
||||
*/
|
||||
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
|
||||
conn->flags |= CONN_FLAG_IN_HANDLER;
|
||||
connIncrRefs(conn);
|
||||
if (handler) handler(conn);
|
||||
conn->flags &= ~CONN_FLAG_IN_HANDLER;
|
||||
connDecrRefs(conn);
|
||||
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||
connClose(conn);
|
||||
if (!connHasRefs(conn)) connClose(conn);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
||||
@@ -1296,13 +1296,17 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
propagateExpire(db,key,server.lazyfree_lazy_expire);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",key,db->id);
|
||||
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
if (retval) signalModifiedKey(db,key);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* API to get key arguments from commands
|
||||
* ---------------------------------------------------------------------------*/
|
||||
#define MAX_KEYS_BUFFER 256
|
||||
static int getKeysTempBuffer[MAX_KEYS_BUFFER];
|
||||
|
||||
/* The base case is to use the keys position as given in the command table
|
||||
* (firstkey, lastkey, step). */
|
||||
@@ -1317,7 +1321,12 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
|
||||
last = cmd->lastkey;
|
||||
if (last < 0) last = argc+last;
|
||||
keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
|
||||
|
||||
int count = ((last - cmd->firstkey)+1);
|
||||
keys = getKeysTempBuffer;
|
||||
if (count > MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*count);
|
||||
|
||||
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
|
||||
if (j >= argc) {
|
||||
/* Modules commands, and standard commands with a not fixed number
|
||||
@@ -1327,7 +1336,7 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
* return no keys and expect the command implementation to report
|
||||
* an arity or syntax error. */
|
||||
if (cmd->flags & CMD_MODULE || cmd->arity < 0) {
|
||||
zfree(keys);
|
||||
getKeysFreeResult(keys);
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
} else {
|
||||
@@ -1363,7 +1372,8 @@ int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *nu
|
||||
|
||||
/* Free the result of getKeysFromCommand. */
|
||||
void getKeysFreeResult(int *result) {
|
||||
zfree(result);
|
||||
if (result != getKeysTempBuffer)
|
||||
zfree(result);
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from following commands:
|
||||
@@ -1384,7 +1394,9 @@ int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *nu
|
||||
/* Keys in z{union,inter}store come from two places:
|
||||
* argv[1] = storage key,
|
||||
* argv[3...n] = keys to intersect */
|
||||
keys = zmalloc(sizeof(int)*(num+1));
|
||||
keys = getKeysTempBuffer;
|
||||
if (num+1>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*(num+1));
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
@@ -1410,7 +1422,10 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
|
||||
*numkeys = num;
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
@@ -1431,7 +1446,7 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
UNUSED(cmd);
|
||||
|
||||
num = 0;
|
||||
keys = zmalloc(sizeof(int)*2); /* Alloc 2 places for the worst case. */
|
||||
keys = getKeysTempBuffer; /* Alloc 2 places for the worst case. */
|
||||
|
||||
keys[num++] = 1; /* <sort-key> is always present. */
|
||||
|
||||
@@ -1489,7 +1504,10 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
|
||||
}
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
|
||||
for (i = 0; i < num; i++) keys[i] = first+i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
@@ -1522,7 +1540,9 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
* argv[1] = key,
|
||||
* argv[5...n] = stored key if present
|
||||
*/
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
|
||||
/* Add all key positions to keys[] */
|
||||
keys[0] = 1;
|
||||
@@ -1540,7 +1560,7 @@ int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys
|
||||
UNUSED(cmd);
|
||||
|
||||
if (argc >= 3 && !strcasecmp(argv[1]->ptr,"usage")) {
|
||||
keys = zmalloc(sizeof(int) * 1);
|
||||
keys = getKeysTempBuffer;
|
||||
keys[0] = 2;
|
||||
*numkeys = 1;
|
||||
return keys;
|
||||
@@ -1587,7 +1607,10 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
num /= 2; /* We have half the keys as there are arguments because
|
||||
there are also the IDs, one per key. */
|
||||
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
|
||||
for (i = streams_pos+1; i < argc-num; i++) keys[i-streams_pos-1] = i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
|
||||
+5
-1
@@ -363,6 +363,7 @@ void debugCommand(client *c) {
|
||||
"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.",
|
||||
"LUA-ALWAYS-REPLICATE-COMMANDS <0|1> -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.",
|
||||
"OBJECT <key> -- Show low level info about key and associated value.",
|
||||
"OOM -- Crash the server simulating an out-of-memory error.",
|
||||
"PANIC -- Crash the server simulating a panic.",
|
||||
"POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.",
|
||||
"RELOAD -- Save the RDB on disk and reload it back in memory.",
|
||||
@@ -684,9 +685,12 @@ NULL
|
||||
sds stats = sdsempty();
|
||||
char buf[4096];
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK)
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK) {
|
||||
sdsfree(stats);
|
||||
return;
|
||||
}
|
||||
if (dbid < 0 || dbid >= server.dbnum) {
|
||||
sdsfree(stats);
|
||||
addReplyError(c,"Out of range database");
|
||||
return;
|
||||
}
|
||||
|
||||
+67
-29
@@ -5,8 +5,8 @@
|
||||
* We do that by scanning the keyspace and for each pointer we have, we can try to
|
||||
* ask the allocator if moving it to a new address will help reduce fragmentation.
|
||||
*
|
||||
* Copyright (c) 2017, Oran Agra
|
||||
* Copyright (c) 2017, Redis Labs, Inc
|
||||
* Copyright (c) 2020, Oran Agra
|
||||
* Copyright (c) 2020, Redis Labs, Inc
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -408,25 +408,32 @@ dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sd
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head, *newnode;
|
||||
long activeDefragQuickListNode(quicklist *ql, quicklistNode **node_ref) {
|
||||
quicklistNode *newnode, *node = *node_ref;
|
||||
long defragged = 0;
|
||||
unsigned char *newzl;
|
||||
if ((newnode = activeDefragAlloc(node))) {
|
||||
if (newnode->prev)
|
||||
newnode->prev->next = newnode;
|
||||
else
|
||||
ql->head = newnode;
|
||||
if (newnode->next)
|
||||
newnode->next->prev = newnode;
|
||||
else
|
||||
ql->tail = newnode;
|
||||
*node_ref = node = newnode;
|
||||
defragged++;
|
||||
}
|
||||
if ((newzl = activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
return defragged;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head;
|
||||
long defragged = 0;
|
||||
while (node) {
|
||||
if ((newnode = activeDefragAlloc(node))) {
|
||||
if (newnode->prev)
|
||||
newnode->prev->next = newnode;
|
||||
else
|
||||
ql->head = newnode;
|
||||
if (newnode->next)
|
||||
newnode->next->prev = newnode;
|
||||
else
|
||||
ql->tail = newnode;
|
||||
node = newnode;
|
||||
defragged++;
|
||||
}
|
||||
if ((newzl = activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
defragged += activeDefragQuickListNode(ql, &node);
|
||||
node = node->next;
|
||||
}
|
||||
return defragged;
|
||||
@@ -440,12 +447,48 @@ void defragLater(redisDb *db, dictEntry *kde) {
|
||||
listAddNodeTail(db->defrag_later, key);
|
||||
}
|
||||
|
||||
long scanLaterList(robj *ob) {
|
||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||
long scanLaterList(robj *ob, unsigned long *cursor, long long endtime, long long *defragged) {
|
||||
quicklist *ql = ob->ptr;
|
||||
quicklistNode *node;
|
||||
long iterations = 0;
|
||||
int bookmark_failed = 0;
|
||||
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST)
|
||||
return 0;
|
||||
server.stat_active_defrag_scanned+=ql->len;
|
||||
return activeDefragQuickListNodes(ql);
|
||||
|
||||
if (*cursor == 0) {
|
||||
/* if cursor is 0, we start new iteration */
|
||||
node = ql->head;
|
||||
} else {
|
||||
node = quicklistBookmarkFind(ql, "_AD");
|
||||
if (!node) {
|
||||
/* if the bookmark was deleted, it means we reached the end. */
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
(*cursor)++;
|
||||
while (node) {
|
||||
(*defragged) += activeDefragQuickListNode(ql, &node);
|
||||
server.stat_active_defrag_scanned++;
|
||||
if (++iterations > 128 && !bookmark_failed) {
|
||||
if (ustime() > endtime) {
|
||||
if (!quicklistBookmarkCreate(&ql, "_AD", node)) {
|
||||
bookmark_failed = 1;
|
||||
} else {
|
||||
ob->ptr = ql; /* bookmark creation may have re-allocated the quicklist */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
iterations = 0;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
quicklistBookmarkDelete(ql, "_AD");
|
||||
*cursor = 0;
|
||||
return bookmark_failed? 1: 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
@@ -638,7 +681,8 @@ int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime,
|
||||
void *newdata = activeDefragAlloc(ri.data);
|
||||
if (newdata)
|
||||
raxSetData(ri.node, ri.data=newdata), (*defragged)++;
|
||||
if (++iterations > 16) {
|
||||
server.stat_active_defrag_scanned++;
|
||||
if (++iterations > 128) {
|
||||
if (ustime() > endtime) {
|
||||
serverAssert(ri.key_len==sizeof(last));
|
||||
memcpy(last,ri.key,ri.key_len);
|
||||
@@ -900,8 +944,7 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
if (de) {
|
||||
robj *ob = dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
server.stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
return scanLaterList(ob, cursor, endtime, &server.stat_active_defrag_hits);
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
server.stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
@@ -961,11 +1004,6 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
if (defragLaterItem(de, &defrag_later_cursor, endtime))
|
||||
quit = 1; /* time is up, we didn't finish all the work */
|
||||
|
||||
/* Don't start a new BIG key in this loop, this is because the
|
||||
* next key can be a list, and scanLaterList must be done in once cycle */
|
||||
if (!defrag_later_cursor)
|
||||
quit = 1;
|
||||
|
||||
/* Once in 16 scan iterations, 512 pointer reallocations, or 64 fields
|
||||
* (if we have a lot of pointers in one hash bucket, or rehashing),
|
||||
* check if we reached the time limit. */
|
||||
|
||||
@@ -871,6 +871,10 @@ unsigned long dictScan(dict *d,
|
||||
|
||||
if (dictSize(d) == 0) return 0;
|
||||
|
||||
/* Having a safe iterator means no rehashing can happen, see _dictRehashStep.
|
||||
* This is needed in case the scan callback tries to do dictFind or alike. */
|
||||
d->iterators++;
|
||||
|
||||
if (!dictIsRehashing(d)) {
|
||||
t0 = &(d->ht[0]);
|
||||
m0 = t0->sizemask;
|
||||
@@ -937,6 +941,9 @@ unsigned long dictScan(dict *d,
|
||||
} while (v & (m0 ^ m1));
|
||||
}
|
||||
|
||||
/* undo the ++ at the top */
|
||||
d->iterators--;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ int THPGetAnonHugePagesSize(void) {
|
||||
/* ---------------------------- Latency API --------------------------------- */
|
||||
|
||||
/* Latency monitor initialization. We just need to create the dictionary
|
||||
* of time series, each time serie is craeted on demand in order to avoid
|
||||
* of time series, each time serie is created on demand in order to avoid
|
||||
* having a fixed list to maintain. */
|
||||
void latencyMonitorInit(void) {
|
||||
server.latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
|
||||
|
||||
+118
-42
@@ -730,6 +730,7 @@ int64_t commandFlagsFromString(char *s) {
|
||||
else if (!strcasecmp(t,"random")) flags |= CMD_RANDOM;
|
||||
else if (!strcasecmp(t,"allow-stale")) flags |= CMD_STALE;
|
||||
else if (!strcasecmp(t,"no-monitor")) flags |= CMD_SKIP_MONITOR;
|
||||
else if (!strcasecmp(t,"no-slowlog")) flags |= CMD_SKIP_SLOWLOG;
|
||||
else if (!strcasecmp(t,"fast")) flags |= CMD_FAST;
|
||||
else if (!strcasecmp(t,"no-auth")) flags |= CMD_NO_AUTH;
|
||||
else if (!strcasecmp(t,"getkeys-api")) flags |= CMD_MODULE_GETKEYS;
|
||||
@@ -781,6 +782,8 @@ int64_t commandFlagsFromString(char *s) {
|
||||
* this means.
|
||||
* * **"no-monitor"**: Don't propagate the command on monitor. Use this if
|
||||
* the command has sensible data among the arguments.
|
||||
* * **"no-slowlog"**: Don't log this command in the slowlog. Use this if
|
||||
* the command has sensible data among the arguments.
|
||||
* * **"fast"**: The command time complexity is not greater
|
||||
* than O(log(N)) where N is the size of the collection or
|
||||
* anything else representing the normal scalability
|
||||
@@ -1039,6 +1042,17 @@ RedisModuleString *RM_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll
|
||||
return RM_CreateString(ctx,buf,len);
|
||||
}
|
||||
|
||||
/* Like RedisModule_CreatString(), but creates a string starting from a double
|
||||
* integer instead of taking a buffer and its length.
|
||||
*
|
||||
* The returned string must be released with RedisModule_FreeString() or by
|
||||
* enabling automatic memory management. */
|
||||
RedisModuleString *RM_CreateStringFromDouble(RedisModuleCtx *ctx, double d) {
|
||||
char buf[128];
|
||||
size_t len = d2string(buf,sizeof(buf),d);
|
||||
return RM_CreateString(ctx,buf,len);
|
||||
}
|
||||
|
||||
/* Like RedisModule_CreatString(), but creates a string starting from a long
|
||||
* double.
|
||||
*
|
||||
@@ -1781,7 +1795,12 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
|
||||
* current request context (whether the client is a Lua script or in a MULTI),
|
||||
* and about the Redis instance in general, i.e replication and persistence.
|
||||
*
|
||||
* The available flags are:
|
||||
* It is possible to call this function even with a NULL context, however
|
||||
* in this case the following flags will not be reported:
|
||||
*
|
||||
* * LUA, MULTI, REPLICATED, DIRTY (see below for more info).
|
||||
*
|
||||
* Available flags and their meaning:
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_LUA: The command is running in a Lua script
|
||||
*
|
||||
@@ -1834,20 +1853,22 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
|
||||
int flags = 0;
|
||||
/* Client specific flags */
|
||||
if (ctx->client) {
|
||||
if (ctx->client->flags & CLIENT_LUA)
|
||||
flags |= REDISMODULE_CTX_FLAGS_LUA;
|
||||
if (ctx->client->flags & CLIENT_MULTI)
|
||||
flags |= REDISMODULE_CTX_FLAGS_MULTI;
|
||||
/* Module command recieved from MASTER, is replicated. */
|
||||
if (ctx->client->flags & CLIENT_MASTER)
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICATED;
|
||||
}
|
||||
if (ctx) {
|
||||
if (ctx->client) {
|
||||
if (ctx->client->flags & CLIENT_LUA)
|
||||
flags |= REDISMODULE_CTX_FLAGS_LUA;
|
||||
if (ctx->client->flags & CLIENT_MULTI)
|
||||
flags |= REDISMODULE_CTX_FLAGS_MULTI;
|
||||
/* Module command recieved from MASTER, is replicated. */
|
||||
if (ctx->client->flags & CLIENT_MASTER)
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICATED;
|
||||
}
|
||||
|
||||
/* For DIRTY flags, we need the blocked client if used */
|
||||
client *c = ctx->blocked_client ? ctx->blocked_client->client : ctx->client;
|
||||
if (c && (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC))) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_MULTI_DIRTY;
|
||||
/* For DIRTY flags, we need the blocked client if used */
|
||||
client *c = ctx->blocked_client ? ctx->blocked_client->client : ctx->client;
|
||||
if (c && (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC))) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_MULTI_DIRTY;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.cluster_enabled)
|
||||
@@ -3526,6 +3547,8 @@ void moduleTypeNameByID(char *name, uint64_t moduleid) {
|
||||
* // Optional fields
|
||||
* .digest = myType_DigestCallBack,
|
||||
* .mem_usage = myType_MemUsageCallBack,
|
||||
* .aux_load = myType_AuxRDBLoadCallBack,
|
||||
* .aux_save = myType_AuxRDBSaveCallBack,
|
||||
* }
|
||||
*
|
||||
* * **rdb_load**: A callback function pointer that loads data from RDB files.
|
||||
@@ -3533,6 +3556,10 @@ void moduleTypeNameByID(char *name, uint64_t moduleid) {
|
||||
* * **aof_rewrite**: A callback function pointer that rewrites data as commands.
|
||||
* * **digest**: A callback function pointer that is used for `DEBUG DIGEST`.
|
||||
* * **free**: A callback function pointer that can free a type value.
|
||||
* * **aux_save**: A callback function pointer that saves out of keyspace data to RDB files.
|
||||
* 'when' argument is either REDISMODULE_AUX_BEFORE_RDB or REDISMODULE_AUX_AFTER_RDB.
|
||||
* * **aux_load**: A callback function pointer that loads out of keyspace data from RDB files.
|
||||
* Similar to aux_save, returns REDISMODULE_OK on success, and ERR otherwise.
|
||||
*
|
||||
* The **digest* and **mem_usage** methods should currently be omitted since
|
||||
* they are not yet implemented inside the Redis modules core.
|
||||
@@ -4287,12 +4314,15 @@ void unblockClientFromModule(client *c) {
|
||||
* We must call moduleUnblockClient in order to free privdata and
|
||||
* RedisModuleBlockedClient.
|
||||
*
|
||||
* Note that clients implementing threads and working with private data,
|
||||
* should make sure to stop the threads or protect the private data
|
||||
* in some other way in the disconnection and timeout callback, because
|
||||
* here we are going to free the private data associated with the
|
||||
* blocked client. */
|
||||
if (!bc->unblocked)
|
||||
* Note that we only do that for clients that are blocked on keys, for which
|
||||
* the contract is that the module should not call RM_UnblockClient under
|
||||
* normal circumstances.
|
||||
* Clients implementing threads and working with private data should be
|
||||
* aware that calling RM_UnblockClient for every blocked client is their
|
||||
* responsibility, and if they fail to do so memory may leak. Ideally they
|
||||
* should implement the disconnect and timeout callbacks and call
|
||||
* RM_UnblockClient, but any other way is also acceptable. */
|
||||
if (bc->blocked_on_keys && !bc->unblocked)
|
||||
moduleUnblockClient(c);
|
||||
|
||||
bc->client = NULL;
|
||||
@@ -4406,6 +4436,10 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
|
||||
*
|
||||
* free_privdata: called in order to free the private data that is passed
|
||||
* by RedisModule_UnblockClient() call.
|
||||
*
|
||||
* Note: RedisModule_UnblockClient should be called for every blocked client,
|
||||
* even if client was killed, timed-out or disconnected. Failing to do so
|
||||
* will result in memory leaks.
|
||||
*/
|
||||
RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) {
|
||||
return moduleBlockClient(ctx,reply_callback,timeout_callback,free_privdata,timeout_ms, NULL,0,NULL);
|
||||
@@ -4460,7 +4494,15 @@ RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc
|
||||
* freed using the free_privdata callback provided by the user.
|
||||
*
|
||||
* However the reply callback will be able to access the argument vector of
|
||||
* the command, so the private data is often not needed. */
|
||||
* the command, so the private data is often not needed.
|
||||
*
|
||||
* Note: Under normal circumstances RedisModule_UnblockClient should not be
|
||||
* called for clients that are blocked on keys (Either the key will
|
||||
* become ready or a timeout will occur). If for some reason you do want
|
||||
* to call RedisModule_UnblockClient it is possible: Client will be
|
||||
* handled as if it were timed-out (You must implement the timeout
|
||||
* callback in that case).
|
||||
*/
|
||||
RedisModuleBlockedClient *RM_BlockClientOnKeys(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) {
|
||||
return moduleBlockClient(ctx,reply_callback,timeout_callback,free_privdata,timeout_ms, keys,numkeys,privdata);
|
||||
}
|
||||
@@ -4722,9 +4764,9 @@ int RM_BlockedClientDisconnected(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* To call non-reply APIs, the thread safe context must be prepared with:
|
||||
*
|
||||
* RedisModule_ThreadSafeCallStart(ctx);
|
||||
* RedisModule_ThreadSafeContextLock(ctx);
|
||||
* ... make your call here ...
|
||||
* RedisModule_ThreadSafeCallStop(ctx);
|
||||
* RedisModule_ThreadSafeContextUnlock(ctx);
|
||||
*
|
||||
* This is not needed when using `RedisModule_Reply*` functions, assuming
|
||||
* that a blocked client was used when the context was created, otherwise
|
||||
@@ -6199,7 +6241,7 @@ int moduleUnregisterUsedAPI(RedisModule *module) {
|
||||
RedisModule *used = ln->value;
|
||||
listNode *ln = listSearchKey(used->usedby,module);
|
||||
if (ln) {
|
||||
listDelNode(module->using,ln);
|
||||
listDelNode(used->usedby,ln);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@@ -6491,24 +6533,32 @@ void RM_ScanCursorDestroy(RedisModuleScanCursor *cursor) {
|
||||
zfree(cursor);
|
||||
}
|
||||
|
||||
/* Scan api that allows a module to scan all the keys and value in the selected db.
|
||||
/* Scan API that allows a module to scan all the keys and value in
|
||||
* the selected db.
|
||||
*
|
||||
* Callback for scan implementation.
|
||||
* void scan_callback(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata);
|
||||
* - ctx - the redis module context provided to for the scan.
|
||||
* - keyname - owned by the caller and need to be retained if used after this function.
|
||||
* - key - holds info on the key and value, it is provided as best effort, in some cases it might
|
||||
* be NULL, in which case the user should (can) use RedisModule_OpenKey (and CloseKey too).
|
||||
* when it is provided, it is owned by the caller and will be free when the callback returns.
|
||||
* - privdata - the user data provided to RedisModule_Scan.
|
||||
* void scan_callback(RedisModuleCtx *ctx, RedisModuleString *keyname,
|
||||
* RedisModuleKey *key, void *privdata);
|
||||
* ctx - the redis module context provided to for the scan.
|
||||
* keyname - owned by the caller and need to be retained if used after this
|
||||
* function.
|
||||
*
|
||||
* key - holds info on the key and value, it is provided as best effort, in
|
||||
* some cases it might be NULL, in which case the user should (can) use
|
||||
* RedisModule_OpenKey (and CloseKey too).
|
||||
* when it is provided, it is owned by the caller and will be free when the
|
||||
* callback returns.
|
||||
*
|
||||
* privdata - the user data provided to RedisModule_Scan.
|
||||
*
|
||||
* The way it should be used:
|
||||
* RedisModuleCursor *c = RedisModule_ScanCursorCreate();
|
||||
* while(RedisModule_Scan(ctx, c, callback, privateData));
|
||||
* RedisModule_ScanCursorDestroy(c);
|
||||
*
|
||||
* It is also possible to use this API from another thread while the lock is acquired durring
|
||||
* the actuall call to RM_Scan:
|
||||
* It is also possible to use this API from another thread while the lock
|
||||
* is acquired durring the actuall call to RM_Scan:
|
||||
*
|
||||
* RedisModuleCursor *c = RedisModule_ScanCursorCreate();
|
||||
* RedisModule_ThreadSafeContextLock(ctx);
|
||||
* while(RedisModule_Scan(ctx, c, callback, privateData)){
|
||||
@@ -6518,9 +6568,26 @@ void RM_ScanCursorDestroy(RedisModuleScanCursor *cursor) {
|
||||
* }
|
||||
* RedisModule_ScanCursorDestroy(c);
|
||||
*
|
||||
* The function will return 1 if there are more elements to scan and 0 otherwise,
|
||||
* possibly setting errno if the call failed.
|
||||
* It is also possible to restart and existing cursor using RM_CursorRestart. */
|
||||
* The function will return 1 if there are more elements to scan and
|
||||
* 0 otherwise, possibly setting errno if the call failed.
|
||||
*
|
||||
* It is also possible to restart and existing cursor using RM_CursorRestart.
|
||||
*
|
||||
* IMPORTANT: This API is very similar to the Redis SCAN command from the
|
||||
* point of view of the guarantees it provides. This means that the API
|
||||
* may report duplicated keys, but guarantees to report at least one time
|
||||
* every key that was there from the start to the end of the scanning process.
|
||||
*
|
||||
* NOTE: If you do database changes within the callback, you should be aware
|
||||
* that the internal state of the database may change. For instance it is safe
|
||||
* to delete or modify the current key, but may not be safe to delete any
|
||||
* other key.
|
||||
* Moreover playing with the Redis keyspace while iterating may have the
|
||||
* effect of returning more duplicates. A safe pattern is to store the keys
|
||||
* names you want to modify elsewhere, and perform the actions on the keys
|
||||
* later when the iteration is complete. Howerver this can cost a lot of
|
||||
* memory, so it may make sense to just operate on the current key when
|
||||
* possible during the iteration, given that this is safe. */
|
||||
int RM_Scan(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata) {
|
||||
if (cursor->done) {
|
||||
errno = ENOENT;
|
||||
@@ -6598,9 +6665,17 @@ static void moduleScanKeyCallback(void *privdata, const dictEntry *de) {
|
||||
* RedisModule_CloseKey(key);
|
||||
* RedisModule_ScanCursorDestroy(c);
|
||||
*
|
||||
* The function will return 1 if there are more elements to scan and 0 otherwise,
|
||||
* possibly setting errno if the call failed.
|
||||
* It is also possible to restart and existing cursor using RM_CursorRestart. */
|
||||
* The function will return 1 if there are more elements to scan and 0 otherwise,
|
||||
* possibly setting errno if the call failed.
|
||||
* It is also possible to restart and existing cursor using RM_CursorRestart.
|
||||
*
|
||||
* NOTE: Certain operations are unsafe while iterating the object. For instance
|
||||
* while the API guarantees to return at least one time all the elements that
|
||||
* are present in the data structure consistently from the start to the end
|
||||
* of the iteration (see HSCAN and similar commands documentation), the more
|
||||
* you play with the elements, the more duplicates you may get. In general
|
||||
* deleting the current element of the data structure is safe, while removing
|
||||
* the key you are iterating is not safe. */
|
||||
int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata) {
|
||||
if (key == NULL || key->value == NULL) {
|
||||
errno = EINVAL;
|
||||
@@ -6706,7 +6781,7 @@ int RM_Fork(RedisModuleForkDoneHandler cb, void *user_data) {
|
||||
server.module_child_pid = childpid;
|
||||
moduleForkInfo.done_handler = cb;
|
||||
moduleForkInfo.done_handler_user_data = user_data;
|
||||
serverLog(LL_NOTICE, "Module fork started pid: %d ", childpid);
|
||||
serverLog(LL_VERBOSE, "Module fork started pid: %d ", childpid);
|
||||
}
|
||||
return childpid;
|
||||
}
|
||||
@@ -6729,7 +6804,7 @@ int TerminateModuleForkChild(int child_pid, int wait) {
|
||||
server.module_child_pid != child_pid) return C_ERR;
|
||||
|
||||
int statloc;
|
||||
serverLog(LL_NOTICE,"Killing running module fork child: %ld",
|
||||
serverLog(LL_VERBOSE,"Killing running module fork child: %ld",
|
||||
(long) server.module_child_pid);
|
||||
if (kill(server.module_child_pid,SIGUSR1) != -1 && wait) {
|
||||
while(wait4(server.module_child_pid,&statloc,0,NULL) !=
|
||||
@@ -7666,6 +7741,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(CreateStringFromCallReply);
|
||||
REGISTER_API(CreateString);
|
||||
REGISTER_API(CreateStringFromLongLong);
|
||||
REGISTER_API(CreateStringFromDouble);
|
||||
REGISTER_API(CreateStringFromLongDouble);
|
||||
REGISTER_API(CreateStringFromString);
|
||||
REGISTER_API(CreateStringPrintf);
|
||||
|
||||
+99
-19
@@ -36,6 +36,7 @@
|
||||
|
||||
static void setProtocolError(const char *errstr, client *c);
|
||||
int postponeClientRead(client *c);
|
||||
int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */
|
||||
|
||||
/* Return the size consumed from the allocator, for the specified SDS string,
|
||||
* including internal fragmentation. This function is used in order to compute
|
||||
@@ -123,7 +124,8 @@ client *createClient(connection *conn) {
|
||||
c->ctime = c->lastinteraction = server.unixtime;
|
||||
/* If the default user does not require authentication, the user is
|
||||
* directly authenticated. */
|
||||
c->authenticated = (c->user->flags & USER_FLAG_NOPASS) != 0;
|
||||
c->authenticated = (c->user->flags & USER_FLAG_NOPASS) &&
|
||||
!(c->user->flags & USER_FLAG_DISABLED);
|
||||
c->replstate = REPL_STATE_NONE;
|
||||
c->repl_put_online_on_ack = 0;
|
||||
c->reploff = 0;
|
||||
@@ -784,7 +786,7 @@ void clientAcceptHandler(connection *conn) {
|
||||
serverLog(LL_WARNING,
|
||||
"Error accepting a client connection: %s",
|
||||
connGetLastError(conn));
|
||||
freeClient(c);
|
||||
freeClientAsync(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -826,7 +828,7 @@ void clientAcceptHandler(connection *conn) {
|
||||
/* Nothing to do, Just to avoid the warning... */
|
||||
}
|
||||
server.stat_rejected_conn++;
|
||||
freeClient(c);
|
||||
freeClientAsync(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -885,9 +887,10 @@ static void acceptCommonHandler(connection *conn, int flags, char *ip) {
|
||||
*/
|
||||
if (connAccept(conn, clientAcceptHandler) == C_ERR) {
|
||||
char conninfo[100];
|
||||
serverLog(LL_WARNING,
|
||||
"Error accepting a client connection: %s (conn: %s)",
|
||||
connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
|
||||
if (connGetState(conn) == CONN_STATE_ERROR)
|
||||
serverLog(LL_WARNING,
|
||||
"Error accepting a client connection: %s (conn: %s)",
|
||||
connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
|
||||
freeClient(connGetPrivateData(conn));
|
||||
return;
|
||||
}
|
||||
@@ -1365,6 +1368,12 @@ void resetClient(client *c) {
|
||||
if (!(c->flags & CLIENT_MULTI) && prevcmd != askingCommand)
|
||||
c->flags &= ~CLIENT_ASKING;
|
||||
|
||||
/* We do the same for the CACHING command as well. It also affects
|
||||
* the next command or transaction executed, in a way very similar
|
||||
* to ASKING. */
|
||||
if (!(c->flags & CLIENT_MULTI) && prevcmd != clientCommand)
|
||||
c->flags &= ~CLIENT_TRACKING_CACHING;
|
||||
|
||||
/* Remove the CLIENT_REPLY_SKIP flag if any so that the reply
|
||||
* to the next command will be sent, but set the flag if the command
|
||||
* we just processed was "CLIENT REPLY SKIP". */
|
||||
@@ -2044,7 +2053,7 @@ void clientCommand(client *c) {
|
||||
"REPLY (on|off|skip) -- Control the replies sent to the current connection.",
|
||||
"SETNAME <name> -- Assign the name <name> to the current connection.",
|
||||
"UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
|
||||
"TRACKING (on|off) [REDIRECT <id>] -- Enable client keys tracking for client side caching.",
|
||||
"TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first] [PREFIX second] [OPTIN] [OPTOUT]... -- Enable client keys tracking for client side caching.",
|
||||
"GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.",
|
||||
NULL
|
||||
};
|
||||
@@ -2221,9 +2230,9 @@ NULL
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"tracking") && c->argc >= 3) {
|
||||
/* CLIENT TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first]
|
||||
* [PREFIX second] ... */
|
||||
* [PREFIX second] [OPTIN] [OPTOUT] ... */
|
||||
long long redir = 0;
|
||||
int bcast = 0;
|
||||
uint64_t options = 0;
|
||||
robj **prefix = NULL;
|
||||
size_t numprefix = 0;
|
||||
|
||||
@@ -2233,18 +2242,34 @@ NULL
|
||||
|
||||
if (!strcasecmp(c->argv[j]->ptr,"redirect") && moreargs) {
|
||||
j++;
|
||||
if (redir != 0) {
|
||||
addReplyError(c,"A client can only redirect to a single "
|
||||
"other client");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j],&redir,NULL) !=
|
||||
C_OK) return;
|
||||
C_OK)
|
||||
{
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
/* We will require the client with the specified ID to exist
|
||||
* right now, even if it is possible that it gets disconnected
|
||||
* later. Still a valid sanity check. */
|
||||
if (lookupClientByID(redir) == NULL) {
|
||||
addReplyError(c,"The client ID you want redirect to "
|
||||
"does not exist");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"bcast")) {
|
||||
bcast++;
|
||||
options |= CLIENT_TRACKING_BCAST;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"optin")) {
|
||||
options |= CLIENT_TRACKING_OPTIN;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"optout")) {
|
||||
options |= CLIENT_TRACKING_OPTOUT;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"prefix") && moreargs) {
|
||||
j++;
|
||||
prefix = zrealloc(prefix,sizeof(robj*)*(numprefix+1));
|
||||
@@ -2260,7 +2285,7 @@ NULL
|
||||
if (!strcasecmp(c->argv[2]->ptr,"on")) {
|
||||
/* Before enabling tracking, make sure options are compatible
|
||||
* among each other and with the current state of the client. */
|
||||
if (!bcast && numprefix) {
|
||||
if (!(options & CLIENT_TRACKING_BCAST) && numprefix) {
|
||||
addReplyError(c,
|
||||
"PREFIX option requires BCAST mode to be enabled");
|
||||
zfree(prefix);
|
||||
@@ -2269,7 +2294,8 @@ NULL
|
||||
|
||||
if (c->flags & CLIENT_TRACKING) {
|
||||
int oldbcast = !!(c->flags & CLIENT_TRACKING_BCAST);
|
||||
if (oldbcast != bcast) {
|
||||
int newbcast = !!(options & CLIENT_TRACKING_BCAST);
|
||||
if (oldbcast != newbcast) {
|
||||
addReplyError(c,
|
||||
"You can't switch BCAST mode on/off before disabling "
|
||||
"tracking for this client, and then re-enabling it with "
|
||||
@@ -2278,7 +2304,17 @@ NULL
|
||||
return;
|
||||
}
|
||||
}
|
||||
enableTracking(c,redir,bcast,prefix,numprefix);
|
||||
|
||||
if (options & CLIENT_TRACKING_BCAST &&
|
||||
options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT))
|
||||
{
|
||||
addReplyError(c,
|
||||
"OPTIN and OPTOUT are not compatible with BCAST");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
enableTracking(c,redir,options,prefix,numprefix);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"off")) {
|
||||
disableTracking(c);
|
||||
} else {
|
||||
@@ -2288,6 +2324,36 @@ NULL
|
||||
}
|
||||
zfree(prefix);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"caching") && c->argc >= 3) {
|
||||
if (!(c->flags & CLIENT_TRACKING)) {
|
||||
addReplyError(c,"CLIENT CACHING can be called only when the "
|
||||
"client is in tracking mode with OPTIN or "
|
||||
"OPTOUT mode enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
char *opt = c->argv[2]->ptr;
|
||||
if (!strcasecmp(opt,"yes")) {
|
||||
if (c->flags & CLIENT_TRACKING_OPTIN) {
|
||||
c->flags |= CLIENT_TRACKING_CACHING;
|
||||
} else {
|
||||
addReplyError(c,"CLIENT CACHING YES is only valid when tracking is enabled in OPTIN mode.");
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(opt,"no")) {
|
||||
if (c->flags & CLIENT_TRACKING_OPTOUT) {
|
||||
c->flags |= CLIENT_TRACKING_CACHING;
|
||||
} else {
|
||||
addReplyError(c,"CLIENT CACHING NO is only valid when tracking is enabled in OPTOUT mode.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Common reply for when we succeeded. */
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"getredir") && c->argc == 2) {
|
||||
/* CLIENT GETREDIR */
|
||||
if (c->flags & CLIENT_TRACKING) {
|
||||
@@ -2675,6 +2741,12 @@ int clientsArePaused(void) {
|
||||
int processEventsWhileBlocked(void) {
|
||||
int iterations = 4; /* See the function top-comment. */
|
||||
int count = 0;
|
||||
|
||||
/* Note: when we are processing events while blocked (for instance during
|
||||
* busy Lua scripts), we set a global flag. When such flag is set, we
|
||||
* avoid handling the read part of clients using threaded I/O.
|
||||
* See https://github.com/antirez/redis/issues/6988 for more info. */
|
||||
ProcessingEventsWhileBlocked = 1;
|
||||
while (iterations--) {
|
||||
int events = 0;
|
||||
events += aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
@@ -2682,6 +2754,7 @@ int processEventsWhileBlocked(void) {
|
||||
if (!events) break;
|
||||
count += events;
|
||||
}
|
||||
ProcessingEventsWhileBlocked = 0;
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -2907,6 +2980,7 @@ int handleClientsWithPendingWritesUsingThreads(void) {
|
||||
int postponeClientRead(client *c) {
|
||||
if (io_threads_active &&
|
||||
server.io_threads_do_reads &&
|
||||
!ProcessingEventsWhileBlocked &&
|
||||
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ)))
|
||||
{
|
||||
c->flags |= CLIENT_PENDING_READ;
|
||||
@@ -2968,16 +3042,22 @@ int handleClientsWithPendingReadsUsingThreads(void) {
|
||||
if (tio_debug) printf("I/O READ All threads finshed\n");
|
||||
|
||||
/* Run the list of clients again to process the new buffers. */
|
||||
listRewind(server.clients_pending_read,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
while(listLength(server.clients_pending_read)) {
|
||||
ln = listFirst(server.clients_pending_read);
|
||||
client *c = listNodeValue(ln);
|
||||
c->flags &= ~CLIENT_PENDING_READ;
|
||||
listDelNode(server.clients_pending_read,ln);
|
||||
|
||||
if (c->flags & CLIENT_PENDING_COMMAND) {
|
||||
c->flags &= ~ CLIENT_PENDING_COMMAND;
|
||||
processCommandAndResetClient(c);
|
||||
c->flags &= ~CLIENT_PENDING_COMMAND;
|
||||
if (processCommandAndResetClient(c) == C_ERR) {
|
||||
/* If the client is no longer valid, we avoid
|
||||
* processing the client later. So we just go
|
||||
* to the next. */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
processInputBufferAndReplicate(c);
|
||||
}
|
||||
listEmpty(server.clients_pending_read);
|
||||
return processed;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1102,13 +1102,13 @@ sds getMemoryDoctorReport(void) {
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Allocator fss is higher than 1.1 and 10MB ? */
|
||||
/* Allocator rss is higher than 1.1 and 10MB ? */
|
||||
if (mh->allocator_rss > 1.1 && mh->allocator_rss_bytes > 10<<20) {
|
||||
high_alloc_rss = 1;
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Non-Allocator fss is higher than 1.1 and 10MB ? */
|
||||
/* Non-Allocator rss is higher than 1.1 and 10MB ? */
|
||||
if (mh->rss_extra > 1.1 && mh->rss_extra_bytes > 10<<20) {
|
||||
high_proc_rss = 1;
|
||||
num_reports++;
|
||||
|
||||
+148
-2
@@ -70,6 +70,12 @@ static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
|
||||
} while (0);
|
||||
#endif
|
||||
|
||||
/* Bookmarks forward declarations */
|
||||
#define QL_MAX_BM ((1 << QL_BM_BITS)-1)
|
||||
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name);
|
||||
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node);
|
||||
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm);
|
||||
|
||||
/* Simple way to give quicklistEntry structs default values with one call. */
|
||||
#define initEntry(e) \
|
||||
do { \
|
||||
@@ -100,10 +106,11 @@ quicklist *quicklistCreate(void) {
|
||||
quicklist->count = 0;
|
||||
quicklist->compress = 0;
|
||||
quicklist->fill = -2;
|
||||
quicklist->bookmark_count = 0;
|
||||
return quicklist;
|
||||
}
|
||||
|
||||
#define COMPRESS_MAX (1 << 16)
|
||||
#define COMPRESS_MAX (1 << QL_COMP_BITS)
|
||||
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
if (compress > COMPRESS_MAX) {
|
||||
compress = COMPRESS_MAX;
|
||||
@@ -113,7 +120,7 @@ void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
quicklist->compress = compress;
|
||||
}
|
||||
|
||||
#define FILL_MAX (1 << 15)
|
||||
#define FILL_MAX (1 << (QL_FILL_BITS-1))
|
||||
void quicklistSetFill(quicklist *quicklist, int fill) {
|
||||
if (fill > FILL_MAX) {
|
||||
fill = FILL_MAX;
|
||||
@@ -169,6 +176,7 @@ void quicklistRelease(quicklist *quicklist) {
|
||||
quicklist->len--;
|
||||
current = next;
|
||||
}
|
||||
quicklistBookmarksClear(quicklist);
|
||||
zfree(quicklist);
|
||||
}
|
||||
|
||||
@@ -578,6 +586,15 @@ quicklist *quicklistCreateFromZiplist(int fill, int compress,
|
||||
|
||||
REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
|
||||
quicklistNode *node) {
|
||||
/* Update the bookmark if any */
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByNode(quicklist, node);
|
||||
if (bm) {
|
||||
bm->node = node->next;
|
||||
/* if the bookmark was to the last node, delete it. */
|
||||
if (!bm->node)
|
||||
_quicklistBookmarkDelete(quicklist, bm);
|
||||
}
|
||||
|
||||
if (node->next)
|
||||
node->next->prev = node->prev;
|
||||
if (node->prev)
|
||||
@@ -1410,6 +1427,87 @@ void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
|
||||
}
|
||||
}
|
||||
|
||||
/* Create or update a bookmark in the list which will be updated to the next node
|
||||
* automatically when the one referenced gets deleted.
|
||||
* Returns 1 on success (creation of new bookmark or override of an existing one).
|
||||
* Returns 0 on failure (reached the maximum supported number of bookmarks).
|
||||
* NOTE: use short simple names, so that string compare on find is quick.
|
||||
* NOTE: bookmakrk creation may re-allocate the quicklist, so the input pointer
|
||||
may change and it's the caller responsibilty to update the reference.
|
||||
*/
|
||||
int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node) {
|
||||
quicklist *ql = *ql_ref;
|
||||
if (ql->bookmark_count >= QL_MAX_BM)
|
||||
return 0;
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
|
||||
if (bm) {
|
||||
bm->node = node;
|
||||
return 1;
|
||||
}
|
||||
ql = zrealloc(ql, sizeof(quicklist) + (ql->bookmark_count+1) * sizeof(quicklistBookmark));
|
||||
*ql_ref = ql;
|
||||
ql->bookmarks[ql->bookmark_count].node = node;
|
||||
ql->bookmarks[ql->bookmark_count].name = zstrdup(name);
|
||||
ql->bookmark_count++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Find the quicklist node referenced by a named bookmark.
|
||||
* When the bookmarked node is deleted the bookmark is updated to the next node,
|
||||
* and if that's the last node, the bookmark is deleted (so find returns NULL). */
|
||||
quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name) {
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
|
||||
if (!bm) return NULL;
|
||||
return bm->node;
|
||||
}
|
||||
|
||||
/* Delete a named bookmark.
|
||||
* returns 0 if bookmark was not found, and 1 if deleted.
|
||||
* Note that the bookmark memory is not freed yet, and is kept for future use. */
|
||||
int quicklistBookmarkDelete(quicklist *ql, const char *name) {
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
|
||||
if (!bm)
|
||||
return 0;
|
||||
_quicklistBookmarkDelete(ql, bm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name) {
|
||||
unsigned i;
|
||||
for (i=0; i<ql->bookmark_count; i++) {
|
||||
if (!strcmp(ql->bookmarks[i].name, name)) {
|
||||
return &ql->bookmarks[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node) {
|
||||
unsigned i;
|
||||
for (i=0; i<ql->bookmark_count; i++) {
|
||||
if (ql->bookmarks[i].node == node) {
|
||||
return &ql->bookmarks[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm) {
|
||||
int index = bm - ql->bookmarks;
|
||||
zfree(bm->name);
|
||||
ql->bookmark_count--;
|
||||
memmove(bm, bm+1, (ql->bookmark_count - index)* sizeof(*bm));
|
||||
/* NOTE: We do not shrink (realloc) the quicklist yet (to avoid resonance,
|
||||
* it may be re-used later (a call to realloc may NOP). */
|
||||
}
|
||||
|
||||
void quicklistBookmarksClear(quicklist *ql) {
|
||||
while (ql->bookmark_count)
|
||||
zfree(ql->bookmarks[--ql->bookmark_count].name);
|
||||
/* NOTE: We do not shrink (realloc) the quick list. main use case for this
|
||||
* function is just before releasing the allocation. */
|
||||
}
|
||||
|
||||
/* The rest of this file is test cases and test helpers. */
|
||||
#ifdef REDIS_TEST
|
||||
#include <stdint.h>
|
||||
@@ -2641,6 +2739,54 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
printf("Compressions: %0.2f seconds.\n", (float)(stop - start) / 1000);
|
||||
printf("\n");
|
||||
|
||||
TEST("bookmark get updated to next item") {
|
||||
quicklist *ql = quicklistNew(1, 0);
|
||||
quicklistPushTail(ql, "1", 1);
|
||||
quicklistPushTail(ql, "2", 1);
|
||||
quicklistPushTail(ql, "3", 1);
|
||||
quicklistPushTail(ql, "4", 1);
|
||||
quicklistPushTail(ql, "5", 1);
|
||||
assert(ql->len==5);
|
||||
/* add two bookmarks, one pointing to the node before the last. */
|
||||
assert(quicklistBookmarkCreate(&ql, "_dummy", ql->head->next));
|
||||
assert(quicklistBookmarkCreate(&ql, "_test", ql->tail->prev));
|
||||
/* test that the bookmark returns the right node, delete it and see that the bookmark points to the last node */
|
||||
assert(quicklistBookmarkFind(ql, "_test") == ql->tail->prev);
|
||||
assert(quicklistDelRange(ql, -2, 1));
|
||||
assert(quicklistBookmarkFind(ql, "_test") == ql->tail);
|
||||
/* delete the last node, and see that the bookmark was deleted. */
|
||||
assert(quicklistDelRange(ql, -1, 1));
|
||||
assert(quicklistBookmarkFind(ql, "_test") == NULL);
|
||||
/* test that other bookmarks aren't affected */
|
||||
assert(quicklistBookmarkFind(ql, "_dummy") == ql->head->next);
|
||||
assert(quicklistBookmarkFind(ql, "_missing") == NULL);
|
||||
assert(ql->len==3);
|
||||
quicklistBookmarksClear(ql); /* for coverage */
|
||||
assert(quicklistBookmarkFind(ql, "_dummy") == NULL);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
TEST("bookmark limit") {
|
||||
int i;
|
||||
quicklist *ql = quicklistNew(1, 0);
|
||||
quicklistPushHead(ql, "1", 1);
|
||||
for (i=0; i<QL_MAX_BM; i++)
|
||||
assert(quicklistBookmarkCreate(&ql, genstr("",i), ql->head));
|
||||
/* when all bookmarks are used, creation fails */
|
||||
assert(!quicklistBookmarkCreate(&ql, "_test", ql->head));
|
||||
/* delete one and see that we can now create another */
|
||||
assert(quicklistBookmarkDelete(ql, "0"));
|
||||
assert(quicklistBookmarkCreate(&ql, "_test", ql->head));
|
||||
/* delete one and see that the rest survive */
|
||||
assert(quicklistBookmarkDelete(ql, "_test"));
|
||||
for (i=1; i<QL_MAX_BM; i++)
|
||||
assert(quicklistBookmarkFind(ql, genstr("",i)) == ql->head);
|
||||
/* make sure the deleted ones are indeed gone */
|
||||
assert(!quicklistBookmarkFind(ql, "0"));
|
||||
assert(!quicklistBookmarkFind(ql, "_test"));
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
if (!err)
|
||||
printf("ALL TESTS PASSED!\n");
|
||||
else
|
||||
|
||||
+43
-3
@@ -28,6 +28,8 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdint.h> // for UINTPTR_MAX
|
||||
|
||||
#ifndef __QUICKLIST_H__
|
||||
#define __QUICKLIST_H__
|
||||
|
||||
@@ -64,19 +66,51 @@ typedef struct quicklistLZF {
|
||||
char compressed[];
|
||||
} quicklistLZF;
|
||||
|
||||
/* Bookmarks are padded with realloc at the end of of the quicklist struct.
|
||||
* They should only be used for very big lists if thousands of nodes were the
|
||||
* excess memory usage is negligible, and there's a real need to iterate on them
|
||||
* in portions.
|
||||
* When not used, they don't add any memory overhead, but when used and then
|
||||
* deleted, some overhead remains (to avoid resonance).
|
||||
* The number of bookmarks used should be kept to minimum since it also adds
|
||||
* overhead on node deletion (searching for a bookmark to update). */
|
||||
typedef struct quicklistBookmark {
|
||||
quicklistNode *node;
|
||||
char *name;
|
||||
} quicklistBookmark;
|
||||
|
||||
#if UINTPTR_MAX == 0xffffffff
|
||||
/* 32-bit */
|
||||
# define QL_FILL_BITS 14
|
||||
# define QL_COMP_BITS 14
|
||||
# define QL_BM_BITS 4
|
||||
#elif UINTPTR_MAX == 0xffffffffffffffff
|
||||
/* 64-bit */
|
||||
# define QL_FILL_BITS 16
|
||||
# define QL_COMP_BITS 16
|
||||
# define QL_BM_BITS 4 /* we can encode more, but we rather limit the user
|
||||
since they cause performance degradation. */
|
||||
#else
|
||||
# error unknown arch bits count
|
||||
#endif
|
||||
|
||||
/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
|
||||
* 'count' is the number of total entries.
|
||||
* 'len' is the number of quicklist nodes.
|
||||
* 'compress' is: -1 if compression disabled, otherwise it's the number
|
||||
* of quicklistNodes to leave uncompressed at ends of quicklist.
|
||||
* 'fill' is the user-requested (or default) fill factor. */
|
||||
* 'fill' is the user-requested (or default) fill factor.
|
||||
* 'bookmakrs are an optional feature that is used by realloc this struct,
|
||||
* so that they don't consume memory when not used. */
|
||||
typedef struct quicklist {
|
||||
quicklistNode *head;
|
||||
quicklistNode *tail;
|
||||
unsigned long count; /* total count of all entries in all ziplists */
|
||||
unsigned long len; /* number of quicklistNodes */
|
||||
int fill : 16; /* fill factor for individual nodes */
|
||||
unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
|
||||
int fill : QL_FILL_BITS; /* fill factor for individual nodes */
|
||||
unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
|
||||
unsigned int bookmark_count: QL_BM_BITS;
|
||||
quicklistBookmark bookmarks[];
|
||||
} quicklist;
|
||||
|
||||
typedef struct quicklistIter {
|
||||
@@ -158,6 +192,12 @@ unsigned long quicklistCount(const quicklist *ql);
|
||||
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
|
||||
size_t quicklistGetLzf(const quicklistNode *node, void **data);
|
||||
|
||||
/* bookmarks */
|
||||
int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node);
|
||||
int quicklistBookmarkDelete(quicklist *ql, const char *name);
|
||||
quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name);
|
||||
void quicklistBookmarksClear(quicklist *ql);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int quicklistTest(int argc, char *argv[]);
|
||||
#endif
|
||||
|
||||
@@ -2195,7 +2195,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
io.ver = 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
if (mt->aux_load(&io,moduleid&1023, when) || io.error) {
|
||||
if (mt->aux_load(&io,moduleid&1023, when) != REDISMODULE_OK || io.error) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
exit(1);
|
||||
@@ -2231,7 +2231,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the slave. */
|
||||
if (server.masterhost == NULL && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
|
||||
if (iAmMaster() && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
|
||||
decrRefCount(key);
|
||||
decrRefCount(val);
|
||||
} else {
|
||||
|
||||
+29
-4
@@ -229,6 +229,7 @@ static struct config {
|
||||
int hotkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
char *auth;
|
||||
int askpass;
|
||||
char *user;
|
||||
int output; /* output mode, see OUTPUT_* defines */
|
||||
sds mb_delim;
|
||||
@@ -1291,7 +1292,11 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
(argc == 3 && !strcasecmp(command,"latency") &&
|
||||
!strcasecmp(argv[1],"graph")) ||
|
||||
(argc == 2 && !strcasecmp(command,"latency") &&
|
||||
!strcasecmp(argv[1],"doctor")))
|
||||
!strcasecmp(argv[1],"doctor")) ||
|
||||
/* Format PROXY INFO command for Redis Cluster Proxy:
|
||||
* https://github.com/artix75/redis-cluster-proxy */
|
||||
(argc >= 2 && !strcasecmp(command,"proxy") &&
|
||||
!strcasecmp(argv[1],"info")))
|
||||
{
|
||||
output_raw = 1;
|
||||
}
|
||||
@@ -1450,6 +1455,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.dbnum = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
||||
config.no_auth_warning = 1;
|
||||
} else if (!strcmp(argv[i], "--askpass")) {
|
||||
config.askpass = 1;
|
||||
} else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
|
||||
&& !lastarg)
|
||||
{
|
||||
@@ -1688,8 +1695,11 @@ static void usage(void) {
|
||||
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
|
||||
" variable to pass this password more safely\n"
|
||||
" (if both are used, this argument takes predecence).\n"
|
||||
" -user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
|
||||
" -pass <password> Alias of -a for consistency with the new --user option.\n"
|
||||
" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
|
||||
" --pass <password> Alias of -a for consistency with the new --user option.\n"
|
||||
" --askpass Force user to input password with mask from STDIN.\n"
|
||||
" If this argument is used, '-a' and " REDIS_CLI_AUTH_ENV "\n"
|
||||
" environment variable will be ignored.\n"
|
||||
" -u <uri> Server URI.\n"
|
||||
" -r <repeat> Execute specified command N times.\n"
|
||||
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
||||
@@ -1983,6 +1993,8 @@ static void repl(void) {
|
||||
if (config.eval) {
|
||||
config.eval_ldb = 1;
|
||||
config.output = OUTPUT_RAW;
|
||||
sdsfreesplitres(argv,argc);
|
||||
linenoiseFree(line);
|
||||
return; /* Return to evalMode to restart the session. */
|
||||
} else {
|
||||
printf("Use 'restart' only in Lua debugging mode.");
|
||||
@@ -7737,7 +7749,7 @@ static void LRUTestMode(void) {
|
||||
* to fill the target instance easily. */
|
||||
start_cycle = mstime();
|
||||
long long hits = 0, misses = 0;
|
||||
while(mstime() - start_cycle < 1000) {
|
||||
while(mstime() - start_cycle < LRU_CYCLE_PERIOD) {
|
||||
/* Write cycle. */
|
||||
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
|
||||
char val[6];
|
||||
@@ -7858,6 +7870,13 @@ static void intrinsicLatencyMode(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static sds askPassword() {
|
||||
linenoiseMaskModeEnable();
|
||||
sds auth = linenoise("Please input password: ");
|
||||
linenoiseMaskModeDisable();
|
||||
return auth;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Program main()
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -7894,6 +7913,7 @@ int main(int argc, char **argv) {
|
||||
config.hotkeys = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.askpass = 0;
|
||||
config.user = NULL;
|
||||
config.eval = NULL;
|
||||
config.eval_ldb = 0;
|
||||
@@ -7935,6 +7955,10 @@ int main(int argc, char **argv) {
|
||||
|
||||
parseEnv();
|
||||
|
||||
if (config.askpass) {
|
||||
config.auth = askPassword();
|
||||
}
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
if (config.tls) {
|
||||
ERR_load_crypto_strings();
|
||||
@@ -8045,3 +8069,4 @@ int main(int argc, char **argv) {
|
||||
return noninteractive(argc,convertToSds(argc,argv));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -467,6 +467,7 @@ size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *r
|
||||
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
|
||||
@@ -726,6 +727,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(CreateStringFromCallReply);
|
||||
REDISMODULE_GET_API(CreateString);
|
||||
REDISMODULE_GET_API(CreateStringFromLongLong);
|
||||
REDISMODULE_GET_API(CreateStringFromDouble);
|
||||
REDISMODULE_GET_API(CreateStringFromLongDouble);
|
||||
REDISMODULE_GET_API(CreateStringFromString);
|
||||
REDISMODULE_GET_API(CreateStringPrintf);
|
||||
|
||||
+153
-4
@@ -45,6 +45,11 @@ void replicationSendAck(void);
|
||||
void putSlaveOnline(client *slave);
|
||||
int cancelReplicationHandshake(void);
|
||||
|
||||
/* We take a global flag to remember if this instance generated an RDB
|
||||
* because of replication, so that we can remove the RDB file in case
|
||||
* the instance is configured to have no persistence. */
|
||||
int RDBGeneratedByReplication = 0;
|
||||
|
||||
/* --------------------------- Utility functions ---------------------------- */
|
||||
|
||||
/* Return the pointer to a string representing the slave ip:listening_port
|
||||
@@ -74,6 +79,34 @@ char *replicationGetSlaveName(client *c) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Plain unlink() can block for quite some time in order to actually apply
|
||||
* the file deletion to the filesystem. This call removes the file in a
|
||||
* background thread instead. We actually just do close() in the thread,
|
||||
* by using the fact that if there is another instance of the same file open,
|
||||
* the foreground unlink() will not really do anything, and deleting the
|
||||
* file will only happen once the last reference is lost. */
|
||||
int bg_unlink(const char *filename) {
|
||||
int fd = open(filename,O_RDONLY|O_NONBLOCK);
|
||||
if (fd == -1) {
|
||||
/* Can't open the file? Fall back to unlinking in the main thread. */
|
||||
return unlink(filename);
|
||||
} else {
|
||||
/* The following unlink() will not do anything since file
|
||||
* is still open. */
|
||||
int retval = unlink(filename);
|
||||
if (retval == -1) {
|
||||
/* If we got an unlink error, we just return it, closing the
|
||||
* new reference we have to the file. */
|
||||
int old_errno = errno;
|
||||
close(fd); /* This would overwrite our errno. So we saved it. */
|
||||
errno = old_errno;
|
||||
return -1;
|
||||
}
|
||||
bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)fd,NULL,NULL);
|
||||
return 0; /* Success. */
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------- MASTER -------------------------------- */
|
||||
|
||||
void createReplicationBacklog(void) {
|
||||
@@ -129,6 +162,7 @@ void feedReplicationBacklog(void *ptr, size_t len) {
|
||||
unsigned char *p = ptr;
|
||||
|
||||
server.master_repl_offset += len;
|
||||
server.master_repl_meaningful_offset = server.master_repl_offset;
|
||||
|
||||
/* This is a circular buffer, so write as much data we can at every
|
||||
* iteration and rewind the "idx" index if we reach the limit. */
|
||||
@@ -591,6 +625,14 @@ int startBgsaveForReplication(int mincapa) {
|
||||
retval = C_ERR;
|
||||
}
|
||||
|
||||
/* If we succeeded to start a BGSAVE with disk target, let's remember
|
||||
* this fact, so that we can later delete the file if needed. Note
|
||||
* that we don't set the flag to 1 if the feature is disabled, otherwise
|
||||
* it would never be cleared: the file is not deleted. This way if
|
||||
* the user enables it later with CONFIG SET, we are fine. */
|
||||
if (retval == C_OK && !socket_target && server.rdb_del_sync_files)
|
||||
RDBGeneratedByReplication = 1;
|
||||
|
||||
/* If we failed to BGSAVE, remove the slaves waiting for a full
|
||||
* resynchronization from the list of slaves, inform them with
|
||||
* an error about what happened, close the connection ASAP. */
|
||||
@@ -883,6 +925,53 @@ void putSlaveOnline(client *slave) {
|
||||
replicationGetSlaveName(slave));
|
||||
}
|
||||
|
||||
/* We call this function periodically to remove an RDB file that was
|
||||
* generated because of replication, in an instance that is otherwise
|
||||
* without any persistence. We don't want instances without persistence
|
||||
* to take RDB files around, this violates certain policies in certain
|
||||
* environments. */
|
||||
void removeRDBUsedToSyncReplicas(void) {
|
||||
/* If the feature is disabled, return ASAP but also clear the
|
||||
* RDBGeneratedByReplication flag in case it was set. Otherwise if the
|
||||
* feature was enabled, but gets disabled later with CONFIG SET, the
|
||||
* flag may remain set to one: then next time the feature is re-enabled
|
||||
* via CONFIG SET we have have it set even if no RDB was generated
|
||||
* because of replication recently. */
|
||||
if (!server.rdb_del_sync_files) {
|
||||
RDBGeneratedByReplication = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (allPersistenceDisabled() && RDBGeneratedByReplication) {
|
||||
client *slave;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
int delrdb = 1;
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
slave = ln->value;
|
||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START ||
|
||||
slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END ||
|
||||
slave->replstate == SLAVE_STATE_SEND_BULK)
|
||||
{
|
||||
delrdb = 0;
|
||||
break; /* No need to check the other replicas. */
|
||||
}
|
||||
}
|
||||
if (delrdb) {
|
||||
struct stat sb;
|
||||
if (lstat(server.rdb_filename,&sb) != -1) {
|
||||
RDBGeneratedByReplication = 0;
|
||||
serverLog(LL_NOTICE,
|
||||
"Removing the RDB file used to feed replicas "
|
||||
"in a persistence-less instance");
|
||||
bg_unlink(server.rdb_filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendBulkToSlave(connection *conn) {
|
||||
client *slave = connGetPrivateData(conn);
|
||||
char buf[PROTO_IOBUF_LEN];
|
||||
@@ -894,7 +983,8 @@ void sendBulkToSlave(connection *conn) {
|
||||
if (slave->replpreamble) {
|
||||
nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble));
|
||||
if (nwritten == -1) {
|
||||
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
|
||||
serverLog(LL_VERBOSE,
|
||||
"Write error sending RDB preamble to replica: %s",
|
||||
connGetLastError(conn));
|
||||
freeClient(slave);
|
||||
return;
|
||||
@@ -1352,7 +1442,7 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags
|
||||
/* Asynchronously read the SYNC payload we receive from a master */
|
||||
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
|
||||
void readSyncBulkPayload(connection *conn) {
|
||||
char buf[4096];
|
||||
char buf[PROTO_IOBUF_LEN];
|
||||
ssize_t nread, readlen, nwritten;
|
||||
int use_diskless_load = useDisklessLoad();
|
||||
redisDb *diskless_load_backup = NULL;
|
||||
@@ -1639,12 +1729,25 @@ void readSyncBulkPayload(connection *conn) {
|
||||
"Failed trying to load the MASTER synchronization "
|
||||
"DB from disk");
|
||||
cancelReplicationHandshake();
|
||||
if (server.rdb_del_sync_files && allPersistenceDisabled()) {
|
||||
serverLog(LL_NOTICE,"Removing the RDB file obtained from "
|
||||
"the master. This replica has persistence "
|
||||
"disabled");
|
||||
bg_unlink(server.rdb_filename);
|
||||
}
|
||||
/* Note that there's no point in restarting the AOF on sync failure,
|
||||
it'll be restarted when sync succeeds or replica promoted. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Cleanup. */
|
||||
if (server.rdb_del_sync_files && allPersistenceDisabled()) {
|
||||
serverLog(LL_NOTICE,"Removing the RDB file obtained from "
|
||||
"the master. This replica has persistence "
|
||||
"disabled");
|
||||
bg_unlink(server.rdb_filename);
|
||||
}
|
||||
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
close(server.repl_transfer_fd);
|
||||
server.repl_transfer_fd = -1;
|
||||
@@ -1666,6 +1769,7 @@ void readSyncBulkPayload(connection *conn) {
|
||||
* we are starting a new history. */
|
||||
memcpy(server.replid,server.master->replid,sizeof(server.replid));
|
||||
server.master_repl_offset = server.master->reploff;
|
||||
server.master_repl_meaningful_offset = server.master->reploff;
|
||||
clearReplicationId2();
|
||||
|
||||
/* Let's create the replication backlog if needed. Slaves need to
|
||||
@@ -2182,6 +2286,10 @@ void syncWithMaster(connection *conn) {
|
||||
|
||||
if (psync_result == PSYNC_CONTINUE) {
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
|
||||
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
|
||||
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections.\n");
|
||||
redisCommunicateSystemd("READY=1\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2619,9 +2727,39 @@ void replicationCacheMaster(client *c) {
|
||||
* current offset if no data was lost during the failover. So we use our
|
||||
* current replication ID and offset in order to synthesize a cached master. */
|
||||
void replicationCacheMasterUsingMyself(void) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Before turning into a replica, using my own master parameters "
|
||||
"to synthesize a cached master: I may be able to synchronize with "
|
||||
"the new master with just a partial transfer.");
|
||||
|
||||
/* This will be used to populate the field server.master->reploff
|
||||
* by replicationCreateMasterClient(). We'll later set the created
|
||||
* master as server.cached_master, so the replica will use such
|
||||
* offset for PSYNC. */
|
||||
server.master_initial_offset = server.master_repl_offset;
|
||||
|
||||
/* However if the "meaningful" offset, that is the offset without
|
||||
* the final PINGs in the stream, is different, use this instead:
|
||||
* often when the master is no longer reachable, replicas will never
|
||||
* receive the PINGs, however the master will end with an incremented
|
||||
* offset because of the PINGs and will not be able to incrementally
|
||||
* PSYNC with the new master. */
|
||||
if (server.master_repl_offset > server.master_repl_meaningful_offset) {
|
||||
long long delta = server.master_repl_offset -
|
||||
server.master_repl_meaningful_offset;
|
||||
serverLog(LL_NOTICE,
|
||||
"Using the meaningful offset %lld instead of %lld to exclude "
|
||||
"the final PINGs (%lld bytes difference)",
|
||||
server.master_repl_meaningful_offset,
|
||||
server.master_repl_offset,
|
||||
delta);
|
||||
server.master_initial_offset = server.master_repl_meaningful_offset;
|
||||
server.repl_backlog_histlen -= delta;
|
||||
if (server.repl_backlog_histlen < 0) server.repl_backlog_histlen = 0;
|
||||
}
|
||||
|
||||
/* The master client we create can be set to any DBID, because
|
||||
* the new master will start its replication stream with SELECT. */
|
||||
server.master_initial_offset = server.master_repl_offset;
|
||||
replicationCreateMasterClient(NULL,-1);
|
||||
|
||||
/* Use our own ID / offset. */
|
||||
@@ -2631,7 +2769,6 @@ void replicationCacheMasterUsingMyself(void) {
|
||||
unlinkClient(server.master);
|
||||
server.cached_master = server.master;
|
||||
server.master = NULL;
|
||||
serverLog(LL_NOTICE,"Before turning into a replica, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.");
|
||||
}
|
||||
|
||||
/* Free a cached master, called when there are no longer the conditions for
|
||||
@@ -3011,10 +3148,18 @@ void replicationCron(void) {
|
||||
clientsArePaused();
|
||||
|
||||
if (!manual_failover_in_progress) {
|
||||
long long before_ping = server.master_repl_meaningful_offset;
|
||||
ping_argv[0] = createStringObject("PING",4);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb,
|
||||
ping_argv, 1);
|
||||
decrRefCount(ping_argv[0]);
|
||||
/* The server.master_repl_meaningful_offset variable represents
|
||||
* the offset of the replication stream without the pending PINGs.
|
||||
* This is useful to set the right replication offset for PSYNC
|
||||
* when the master is turned into a replica. Otherwise pending
|
||||
* PINGs may not allow it to perform an incremental sync with the
|
||||
* new master. */
|
||||
server.master_repl_meaningful_offset = before_ping;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3149,6 +3294,10 @@ void replicationCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove the RDB file used for replication if Redis is not running
|
||||
* with any persistence. */
|
||||
removeRDBUsedToSyncReplicas();
|
||||
|
||||
/* Refresh the number of slaves with lag <= min-slaves-max-lag. */
|
||||
refreshGoodSlavesCount();
|
||||
replication_cron_loops++; /* Incremented with frequency 1 HZ. */
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#define __SDS_H
|
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024)
|
||||
const char *SDS_NOINIT;
|
||||
extern const char *SDS_NOINIT;
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
@@ -36,7 +36,12 @@
|
||||
* the include of your alternate allocator if needed (not needed in order
|
||||
* to use the default libc allocator). */
|
||||
|
||||
#ifndef __SDS_ALLOC_H__
|
||||
#define __SDS_ALLOC_H__
|
||||
|
||||
#include "zmalloc.h"
|
||||
#define s_malloc zmalloc
|
||||
#define s_realloc zrealloc
|
||||
#define s_free zfree
|
||||
|
||||
#endif
|
||||
|
||||
+39
-8
@@ -205,7 +205,8 @@ typedef struct sentinelRedisInstance {
|
||||
dict *slaves; /* Slaves for this master instance. */
|
||||
unsigned int quorum;/* Number of sentinels that need to agree on failure. */
|
||||
int parallel_syncs; /* How many slaves to reconfigure at same time. */
|
||||
char *auth_pass; /* Password to use for AUTH against master & slaves. */
|
||||
char *auth_pass; /* Password to use for AUTH against master & replica. */
|
||||
char *auth_user; /* Username for ACLs AUTH against master & replica. */
|
||||
|
||||
/* Slave specific. */
|
||||
mstime_t master_link_down_time; /* Slave replication link down time. */
|
||||
@@ -1231,6 +1232,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
SENTINEL_DEFAULT_DOWN_AFTER;
|
||||
ri->master_link_down_time = 0;
|
||||
ri->auth_pass = NULL;
|
||||
ri->auth_user = NULL;
|
||||
ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY;
|
||||
ri->slave_reconf_sent_time = 0;
|
||||
ri->slave_master_host = NULL;
|
||||
@@ -1289,6 +1291,7 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) {
|
||||
sdsfree(ri->slave_master_host);
|
||||
sdsfree(ri->leader);
|
||||
sdsfree(ri->auth_pass);
|
||||
sdsfree(ri->auth_user);
|
||||
sdsfree(ri->info);
|
||||
releaseSentinelAddr(ri->addr);
|
||||
dictRelease(ri->renamed_commands);
|
||||
@@ -1654,19 +1657,19 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
ri->failover_timeout = atoi(argv[2]);
|
||||
if (ri->failover_timeout <= 0)
|
||||
return "negative or zero time parameter.";
|
||||
} else if (!strcasecmp(argv[0],"parallel-syncs") && argc == 3) {
|
||||
} else if (!strcasecmp(argv[0],"parallel-syncs") && argc == 3) {
|
||||
/* parallel-syncs <name> <milliseconds> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->parallel_syncs = atoi(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"notification-script") && argc == 3) {
|
||||
} else if (!strcasecmp(argv[0],"notification-script") && argc == 3) {
|
||||
/* notification-script <name> <path> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
if (access(argv[2],X_OK) == -1)
|
||||
return "Notification script seems non existing or non executable.";
|
||||
ri->notification_script = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"client-reconfig-script") && argc == 3) {
|
||||
} else if (!strcasecmp(argv[0],"client-reconfig-script") && argc == 3) {
|
||||
/* client-reconfig-script <name> <path> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
@@ -1674,11 +1677,16 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
return "Client reconfiguration script seems non existing or "
|
||||
"non executable.";
|
||||
ri->client_reconfig_script = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"auth-pass") && argc == 3) {
|
||||
} else if (!strcasecmp(argv[0],"auth-pass") && argc == 3) {
|
||||
/* auth-pass <name> <password> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->auth_pass = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"auth-user") && argc == 3) {
|
||||
/* auth-user <name> <username> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->auth_user = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"current-epoch") && argc == 2) {
|
||||
/* current-epoch <epoch> */
|
||||
unsigned long long current_epoch = strtoull(argv[1],NULL,10);
|
||||
@@ -1836,7 +1844,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
|
||||
/* sentinel auth-pass */
|
||||
/* sentinel auth-pass & auth-user */
|
||||
if (master->auth_pass) {
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel auth-pass %s %s",
|
||||
@@ -1844,6 +1852,13 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
|
||||
if (master->auth_user) {
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel auth-user %s %s",
|
||||
master->name, master->auth_user);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
|
||||
/* sentinel config-epoch */
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel config-epoch %s %llu",
|
||||
@@ -1968,19 +1983,29 @@ werr:
|
||||
* will disconnect and reconnect the link and so forth. */
|
||||
void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
|
||||
char *auth_pass = NULL;
|
||||
char *auth_user = NULL;
|
||||
|
||||
if (ri->flags & SRI_MASTER) {
|
||||
auth_pass = ri->auth_pass;
|
||||
auth_user = ri->auth_user;
|
||||
} else if (ri->flags & SRI_SLAVE) {
|
||||
auth_pass = ri->master->auth_pass;
|
||||
auth_user = ri->master->auth_user;
|
||||
} else if (ri->flags & SRI_SENTINEL) {
|
||||
auth_pass = ACLDefaultUserFirstPassword();
|
||||
auth_pass = server.requirepass;
|
||||
auth_user = NULL;
|
||||
}
|
||||
|
||||
if (auth_pass) {
|
||||
if (auth_pass && auth_user == NULL) {
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s",
|
||||
sentinelInstanceMapCommand(ri,"AUTH"),
|
||||
auth_pass) == C_OK) ri->link->pending_commands++;
|
||||
} else if (auth_pass && auth_user) {
|
||||
/* If we also have an username, use the ACL-style AUTH command
|
||||
* with two arguments, username and password. */
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s %s",
|
||||
sentinelInstanceMapCommand(ri,"AUTH"),
|
||||
auth_user, auth_pass) == C_OK) ri->link->pending_commands++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3522,6 +3547,12 @@ void sentinelSetCommand(client *c) {
|
||||
sdsfree(ri->auth_pass);
|
||||
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"auth-user") && moreargs > 0) {
|
||||
/* auth-user <username> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
sdsfree(ri->auth_user);
|
||||
ri->auth_user = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"quorum") && moreargs > 0) {
|
||||
/* quorum <count> */
|
||||
robj *o = c->argv[++j];
|
||||
|
||||
+174
-40
@@ -238,6 +238,10 @@ struct redisCommand redisCommandTable[] = {
|
||||
"write use-memory @bitmap",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"bitfield_ro",bitfieldroCommand,-2,
|
||||
"read-only fast @bitmap",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"setrange",setrangeCommand,4,
|
||||
"write use-memory @string",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
@@ -1455,12 +1459,149 @@ void updateDictResizePolicy(void) {
|
||||
dictDisableResize();
|
||||
}
|
||||
|
||||
/* Return true if there are no active children processes doing RDB saving,
|
||||
* AOF rewriting, or some side process spawned by a loaded module. */
|
||||
int hasActiveChildProcess() {
|
||||
return server.rdb_child_pid != -1 ||
|
||||
server.aof_child_pid != -1 ||
|
||||
server.module_child_pid != -1;
|
||||
}
|
||||
|
||||
/* Return true if this instance has persistence completely turned off:
|
||||
* both RDB and AOF are disabled. */
|
||||
int allPersistenceDisabled(void) {
|
||||
return server.saveparamslen == 0 && server.aof_state == AOF_OFF;
|
||||
}
|
||||
|
||||
/* ========================== Clients timeouts ============================= */
|
||||
|
||||
/* Check if this blocked client timedout (does nothing if the client is
|
||||
* not blocked right now). If so send a reply, unblock it, and return 1.
|
||||
* Otherwise 0 is returned and no operation is performed. */
|
||||
int checkBlockedClientTimeout(client *c, mstime_t now) {
|
||||
if (c->flags & CLIENT_BLOCKED &&
|
||||
c->bpop.timeout != 0
|
||||
&& c->bpop.timeout < now)
|
||||
{
|
||||
/* Handle blocking operation specific timeout. */
|
||||
replyToBlockedClientTimedOut(c);
|
||||
unblockClient(c);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for timeouts. Returns non-zero if the client was terminated.
|
||||
* The function gets the current time in milliseconds as argument since
|
||||
* it gets called multiple times in a loop, so calling gettimeofday() for
|
||||
* each iteration would be costly without any actual gain. */
|
||||
int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
|
||||
time_t now = now_ms/1000;
|
||||
|
||||
if (server.maxidletime &&
|
||||
/* This handles the idle clients connection timeout if set. */
|
||||
!(c->flags & CLIENT_SLAVE) && /* No timeout for slaves and monitors */
|
||||
!(c->flags & CLIENT_MASTER) && /* No timeout for masters */
|
||||
!(c->flags & CLIENT_BLOCKED) && /* No timeout for BLPOP */
|
||||
!(c->flags & CLIENT_PUBSUB) && /* No timeout for Pub/Sub clients */
|
||||
(now - c->lastinteraction > server.maxidletime))
|
||||
{
|
||||
serverLog(LL_VERBOSE,"Closing idle client");
|
||||
freeClient(c);
|
||||
return 1;
|
||||
} else if (c->flags & CLIENT_BLOCKED) {
|
||||
/* Blocked OPS timeout is handled with milliseconds resolution.
|
||||
* However note that the actual resolution is limited by
|
||||
* server.hz. So for short timeouts (less than SERVER_SHORT_TIMEOUT
|
||||
* milliseconds) we populate a Radix tree and handle such timeouts
|
||||
* in clientsHandleShortTimeout(). */
|
||||
if (checkBlockedClientTimeout(c,now_ms)) return 0;
|
||||
|
||||
/* Cluster: handle unblock & redirect of clients blocked
|
||||
* into keys no longer served by this server. */
|
||||
if (server.cluster_enabled) {
|
||||
if (clusterRedirectBlockedClientIfNeeded(c))
|
||||
unblockClient(c);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* For shor timeouts, less than < CLIENT_SHORT_TIMEOUT milliseconds, we
|
||||
* populate a radix tree of 128 bit keys composed as such:
|
||||
*
|
||||
* [8 byte big endian expire time]+[8 byte client ID]
|
||||
*
|
||||
* We don't do any cleanup in the Radix tree: when we run the clients that
|
||||
* reached the timeout already, if they are no longer existing or no longer
|
||||
* blocked with such timeout, we just go forward.
|
||||
*
|
||||
* Every time a client blocks with a short timeout, we add the client in
|
||||
* the tree. In beforeSleep() we call clientsHandleShortTimeout() to run
|
||||
* the tree and unblock the clients.
|
||||
*
|
||||
* Design hint: why we block only clients with short timeouts? For frugality:
|
||||
* Clients blocking for 30 seconds usually don't need to be unblocked
|
||||
* precisely, and anyway for the nature of Redis to *guarantee* unblock time
|
||||
* precision is hard, so we can avoid putting a large number of clients in
|
||||
* the radix tree without a good reason. This idea also has a role in memory
|
||||
* usage as well given that we don't do cleanup, the shorter a client timeout,
|
||||
* the less time it will stay in the radix tree. */
|
||||
|
||||
#define CLIENT_ST_KEYLEN 16 /* 8 bytes mstime + 8 bytes client ID. */
|
||||
|
||||
/* Given client ID and timeout, write the resulting radix tree key in buf. */
|
||||
void encodeTimeoutKey(unsigned char *buf, uint64_t timeout, uint64_t id) {
|
||||
timeout = htonu64(timeout);
|
||||
memcpy(buf,&timeout,sizeof(timeout));
|
||||
memcpy(buf+8,&id,sizeof(id));
|
||||
}
|
||||
|
||||
/* Given a key encoded with encodeTimeoutKey(), resolve the fields and write
|
||||
* the timeout into *toptr and the client ID into *idptr. */
|
||||
void decodeTimeoutKey(unsigned char *buf, uint64_t *toptr, uint64_t *idptr) {
|
||||
memcpy(toptr,buf,sizeof(*toptr));
|
||||
*toptr = ntohu64(*toptr);
|
||||
memcpy(idptr,buf+8,sizeof(*idptr));
|
||||
}
|
||||
|
||||
/* Add the specified client id / timeout as a key in the radix tree we use
|
||||
* to handle short timeouts. The client is not added to the list if its
|
||||
* timeout is longer than CLIENT_SHORT_TIMEOUT milliseconds. */
|
||||
void addClientToShortTimeoutTable(client *c) {
|
||||
if (c->bpop.timeout == 0 ||
|
||||
c->bpop.timeout - mstime() > CLIENT_SHORT_TIMEOUT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
uint64_t timeout = c->bpop.timeout;
|
||||
uint64_t id = c->id;
|
||||
unsigned char buf[CLIENT_ST_KEYLEN];
|
||||
encodeTimeoutKey(buf,timeout,id);
|
||||
raxTryInsert(server.clients_timeout_table,buf,sizeof(buf),NULL,NULL);
|
||||
}
|
||||
|
||||
/* This function is called in beforeSleep() in order to unblock ASAP clients
|
||||
* that are waiting in blocking operations with a short timeout set. */
|
||||
void clientsHandleShortTimeout(void) {
|
||||
if (raxSize(server.clients_timeout_table) == 0) return;
|
||||
uint64_t now = mstime();
|
||||
raxIterator ri;
|
||||
raxStart(&ri,server.clients_timeout_table);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
|
||||
while(raxNext(&ri)) {
|
||||
uint64_t id, timeout;
|
||||
decodeTimeoutKey(ri.key,&timeout,&id);
|
||||
if (timeout >= now) break; /* All the timeouts are in the future. */
|
||||
client *c = lookupClientByID(id);
|
||||
if (c) checkBlockedClientTimeout(c,now);
|
||||
raxRemove(server.clients_timeout_table,ri.key,ri.key_len,NULL);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================= Cron: called every 100 ms ======================== */
|
||||
|
||||
/* Add a sample to the operations per second array of samples. */
|
||||
@@ -1490,42 +1631,6 @@ long long getInstantaneousMetric(int metric) {
|
||||
return sum / STATS_METRIC_SAMPLES;
|
||||
}
|
||||
|
||||
/* Check for timeouts. Returns non-zero if the client was terminated.
|
||||
* The function gets the current time in milliseconds as argument since
|
||||
* it gets called multiple times in a loop, so calling gettimeofday() for
|
||||
* each iteration would be costly without any actual gain. */
|
||||
int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
|
||||
time_t now = now_ms/1000;
|
||||
|
||||
if (server.maxidletime &&
|
||||
!(c->flags & CLIENT_SLAVE) && /* no timeout for slaves and monitors */
|
||||
!(c->flags & CLIENT_MASTER) && /* no timeout for masters */
|
||||
!(c->flags & CLIENT_BLOCKED) && /* no timeout for BLPOP */
|
||||
!(c->flags & CLIENT_PUBSUB) && /* no timeout for Pub/Sub clients */
|
||||
(now - c->lastinteraction > server.maxidletime))
|
||||
{
|
||||
serverLog(LL_VERBOSE,"Closing idle client");
|
||||
freeClient(c);
|
||||
return 1;
|
||||
} else if (c->flags & CLIENT_BLOCKED) {
|
||||
/* Blocked OPS timeout is handled with milliseconds resolution.
|
||||
* However note that the actual resolution is limited by
|
||||
* server.hz. */
|
||||
|
||||
if (c->bpop.timeout != 0 && c->bpop.timeout < now_ms) {
|
||||
/* Handle blocking operation specific timeout. */
|
||||
replyToBlockedClientTimedOut(c);
|
||||
unblockClient(c);
|
||||
} else if (server.cluster_enabled) {
|
||||
/* Cluster: handle unblock & redirect of clients blocked
|
||||
* into keys no longer served by this server. */
|
||||
if (clusterRedirectBlockedClientIfNeeded(c))
|
||||
unblockClient(c);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The client query buffer is an sds.c string that can end with a lot of
|
||||
* free space not used, this function reclaims space if needed.
|
||||
*
|
||||
@@ -1642,6 +1747,9 @@ void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
|
||||
*/
|
||||
#define CLIENTS_CRON_MIN_ITERATIONS 5
|
||||
void clientsCron(void) {
|
||||
/* Unblock short timeout clients ASAP. */
|
||||
clientsHandleShortTimeout();
|
||||
|
||||
/* Try to process at least numclients/server.hz of clients
|
||||
* per call. Since normally (if there are no big latency events) this
|
||||
* function is called server.hz times per second, in the average case we
|
||||
@@ -1683,7 +1791,7 @@ void databasesCron(void) {
|
||||
/* Expire keys by random sampling. Not required for slaves
|
||||
* as master will synthesize DELs for us. */
|
||||
if (server.active_expire_enabled) {
|
||||
if (server.masterhost == NULL) {
|
||||
if (iAmMaster()) {
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
|
||||
} else {
|
||||
expireSlaveKeys();
|
||||
@@ -2080,8 +2188,15 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
UNUSED(eventLoop);
|
||||
|
||||
/* Handle precise timeouts of blocked clients. */
|
||||
clientsHandleShortTimeout();
|
||||
|
||||
/* We should handle pending reads clients ASAP after event loop. */
|
||||
handleClientsWithPendingReadsUsingThreads();
|
||||
|
||||
/* Handle TLS pending data. (must be done before flushAppendOnlyFile) */
|
||||
tlsProcessPendingData();
|
||||
|
||||
/* If tls still has pending unread data don't sleep at all. */
|
||||
aeSetDontWait(server.el, tlsHasPendingData());
|
||||
|
||||
@@ -2149,7 +2264,6 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
void afterSleep(struct aeEventLoop *eventLoop) {
|
||||
UNUSED(eventLoop);
|
||||
if (moduleCount()) moduleAcquireGIL();
|
||||
handleClientsWithPendingReadsUsingThreads();
|
||||
}
|
||||
|
||||
/* =========================== Server initialization ======================== */
|
||||
@@ -2341,6 +2455,7 @@ void initServerConfig(void) {
|
||||
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
|
||||
server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
|
||||
server.master_repl_offset = 0;
|
||||
server.master_repl_meaningful_offset = 0;
|
||||
|
||||
/* Replication partial resync backlog */
|
||||
server.repl_backlog = NULL;
|
||||
@@ -2706,6 +2821,7 @@ void initServer(void) {
|
||||
server.monitors = listCreate();
|
||||
server.clients_pending_write = listCreate();
|
||||
server.clients_pending_read = listCreate();
|
||||
server.clients_timeout_table = raxNew();
|
||||
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
|
||||
server.unblocked_clients = listCreate();
|
||||
server.ready_keys = listCreate();
|
||||
@@ -3370,7 +3486,7 @@ int processCommand(client *c) {
|
||||
/* Check if the user is authenticated. This check is skipped in case
|
||||
* the default user is flagged as "nopass" and is active. */
|
||||
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
|
||||
DefaultUser->flags & USER_FLAG_DISABLED) &&
|
||||
(DefaultUser->flags & USER_FLAG_DISABLED)) &&
|
||||
!c->authenticated;
|
||||
if (auth_required) {
|
||||
/* AUTH and HELLO and no auth modules are valid even in
|
||||
@@ -3495,6 +3611,7 @@ int processCommand(client *c) {
|
||||
!(c->flags & CLIENT_MASTER) &&
|
||||
c->cmd->flags & CMD_WRITE)
|
||||
{
|
||||
flagTransaction(c);
|
||||
addReply(c, shared.roslaveerr);
|
||||
return C_OK;
|
||||
}
|
||||
@@ -3533,11 +3650,19 @@ int processCommand(client *c) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Lua script too slow? Only allow a limited number of commands. */
|
||||
/* Lua script too slow? Only allow a limited number of commands.
|
||||
* Note that we need to allow the transactions commands, otherwise clients
|
||||
* sending a transaction with pipelining without error checking, may have
|
||||
* the MULTI plus a few initial commands refused, then the timeout
|
||||
* condition resolves, and the bottom-half of the transaction gets
|
||||
* executed, see Github PR #7022. */
|
||||
if (server.lua_timedout &&
|
||||
c->cmd->proc != authCommand &&
|
||||
c->cmd->proc != helloCommand &&
|
||||
c->cmd->proc != replconfCommand &&
|
||||
c->cmd->proc != multiCommand &&
|
||||
c->cmd->proc != execCommand &&
|
||||
c->cmd->proc != discardCommand &&
|
||||
!(c->cmd->proc == shutdownCommand &&
|
||||
c->argc == 2 &&
|
||||
tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
|
||||
@@ -3773,6 +3898,7 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_SLOWLOG, "skip_slowlog");
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking");
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast");
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_NO_AUTH, "no_auth");
|
||||
if ((cmd->getkeys_proc && !(cmd->flags & CMD_MODULE)) ||
|
||||
cmd->flags & CMD_MODULE_GETKEYS)
|
||||
{
|
||||
@@ -4374,6 +4500,7 @@ sds genRedisInfoString(const char *section) {
|
||||
"master_replid:%s\r\n"
|
||||
"master_replid2:%s\r\n"
|
||||
"master_repl_offset:%lld\r\n"
|
||||
"master_repl_meaningful_offset:%lld\r\n"
|
||||
"second_repl_offset:%lld\r\n"
|
||||
"repl_backlog_active:%d\r\n"
|
||||
"repl_backlog_size:%lld\r\n"
|
||||
@@ -4382,6 +4509,7 @@ sds genRedisInfoString(const char *section) {
|
||||
server.replid,
|
||||
server.replid2,
|
||||
server.master_repl_offset,
|
||||
server.master_repl_meaningful_offset,
|
||||
server.second_replid_offset,
|
||||
server.repl_backlog != NULL,
|
||||
server.repl_backlog_size,
|
||||
@@ -4759,6 +4887,7 @@ void loadDataFromDisk(void) {
|
||||
{
|
||||
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
||||
server.master_repl_offset = rsi.repl_offset;
|
||||
server.master_repl_meaningful_offset = rsi.repl_offset;
|
||||
/* If we are a slave, create a cached master from this
|
||||
* information, in order to allow partial resynchronizations
|
||||
* with masters. */
|
||||
@@ -4852,6 +4981,11 @@ int redisIsSupervised(int mode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iAmMaster(void) {
|
||||
return ((!server.cluster_enabled && server.masterhost == NULL) ||
|
||||
(server.cluster_enabled && nodeIsMaster(server.cluster->myself)));
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
struct timeval tv;
|
||||
|
||||
+21
-2
@@ -248,6 +248,10 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
perform client side caching. */
|
||||
#define CLIENT_TRACKING_BROKEN_REDIR (1ULL<<32) /* Target client is invalid. */
|
||||
#define CLIENT_TRACKING_BCAST (1ULL<<33) /* Tracking in BCAST mode. */
|
||||
#define CLIENT_TRACKING_OPTIN (1ULL<<34) /* Tracking in opt-in mode. */
|
||||
#define CLIENT_TRACKING_OPTOUT (1ULL<<35) /* Tracking in opt-out mode. */
|
||||
#define CLIENT_TRACKING_CACHING (1ULL<<36) /* CACHING yes/no was given,
|
||||
depending on optin/optout mode. */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@@ -273,6 +277,9 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
buffer configuration. Just the first
|
||||
three: normal, slave, pubsub. */
|
||||
|
||||
/* Other client related defines. */
|
||||
#define CLIENT_SHORT_TIMEOUT 2000 /* See clientsHandleShortTimeout(). */
|
||||
|
||||
/* Slave replication state. Used in server.repl_state for slaves to remember
|
||||
* what to do next. */
|
||||
#define REPL_STATE_NONE 0 /* No active replication */
|
||||
@@ -1063,6 +1070,7 @@ struct redisServer {
|
||||
list *clients_pending_read; /* Client has pending read socket buffers. */
|
||||
list *slaves, *monitors; /* List of slaves and MONITORs */
|
||||
client *current_client; /* Current client executing the command. */
|
||||
rax *clients_timeout_table; /* Radix tree for clients with short timeout. */
|
||||
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
|
||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||
int clients_paused; /* True if clients are currently paused */
|
||||
@@ -1198,6 +1206,8 @@ struct redisServer {
|
||||
char *rdb_filename; /* Name of RDB file */
|
||||
int rdb_compression; /* Use compression in RDB? */
|
||||
int rdb_checksum; /* Use RDB checksum? */
|
||||
int rdb_del_sync_files; /* Remove RDB files used only for SYNC if
|
||||
the instance does not use persistence. */
|
||||
time_t lastsave; /* Unix time of last successful save */
|
||||
time_t lastbgsave_try; /* Unix time of last attempted bgsave */
|
||||
time_t rdb_save_time_last; /* Time used by last RDB save run. */
|
||||
@@ -1235,6 +1245,7 @@ struct redisServer {
|
||||
char replid[CONFIG_RUN_ID_SIZE+1]; /* My current replication ID. */
|
||||
char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from master*/
|
||||
long long master_repl_offset; /* My current replication offset */
|
||||
long long master_repl_meaningful_offset; /* Offset minus latest PINGs. */
|
||||
long long second_replid_offset; /* Accept offsets up to this for replid2. */
|
||||
int slaveseldb; /* Last SELECTed DB in replication output */
|
||||
int repl_ping_slave_period; /* Master pings the slave every N seconds */
|
||||
@@ -1309,7 +1320,7 @@ struct redisServer {
|
||||
list *ready_keys; /* List of readyList structures for BLPOP & co */
|
||||
/* Client side caching. */
|
||||
unsigned int tracking_clients; /* # of clients with tracking enabled.*/
|
||||
int tracking_table_max_keys; /* Max number of keys in tracking table. */
|
||||
size_t tracking_table_max_keys; /* Max number of keys in tracking table. */
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
@@ -1389,6 +1400,9 @@ struct redisServer {
|
||||
/* ACLs */
|
||||
char *acl_filename; /* ACL Users file. NULL if not configured. */
|
||||
unsigned long acllog_max_len; /* Maximum length of the ACL LOG list. */
|
||||
sds requirepass; /* Remember the cleartext password set with the
|
||||
old "requirepass" directive for backward
|
||||
compatibility with Redis <= 5. */
|
||||
/* Assert & bug reporting */
|
||||
const char *assert_failed;
|
||||
const char *assert_file;
|
||||
@@ -1651,7 +1665,7 @@ void addReplyStatusFormat(client *c, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
/* Client side caching (tracking mode) */
|
||||
void enableTracking(client *c, uint64_t redirect_to, int bcast, robj **prefix, size_t numprefix);
|
||||
void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix);
|
||||
void disableTracking(client *c);
|
||||
void trackingRememberKeys(client *c);
|
||||
void trackingInvalidateKey(robj *keyobj);
|
||||
@@ -1782,6 +1796,7 @@ void loadingProgress(off_t pos);
|
||||
void stopLoading(int success);
|
||||
void startSaving(int rdbflags);
|
||||
void stopSaving(int success);
|
||||
int allPersistenceDisabled(void);
|
||||
|
||||
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
|
||||
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
|
||||
@@ -2125,6 +2140,7 @@ void disconnectAllBlockedClients(void);
|
||||
void handleClientsBlockedOnKeys(void);
|
||||
void signalKeyAsReady(redisDb *db, robj *key);
|
||||
void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, streamID *ids);
|
||||
void addClientToShortTimeoutTable(client *c);
|
||||
|
||||
/* expire.c -- Handling of expired keys */
|
||||
void activeExpireCycle(int type);
|
||||
@@ -2167,6 +2183,7 @@ void existsCommand(client *c);
|
||||
void setbitCommand(client *c);
|
||||
void getbitCommand(client *c);
|
||||
void bitfieldCommand(client *c);
|
||||
void bitfieldroCommand(client *c);
|
||||
void setrangeCommand(client *c);
|
||||
void getrangeCommand(client *c);
|
||||
void incrCommand(client *c);
|
||||
@@ -2383,4 +2400,6 @@ int tlsConfigure(redisTLSContextConfig *ctx_config);
|
||||
#define redisDebugMark() \
|
||||
printf("-- MARK %s:%d --\n", __FILE__, __LINE__)
|
||||
|
||||
int iAmMaster(void);
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -415,7 +415,7 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
/* Make sure a key with the name inputted exists, and that it's type is
|
||||
* indeed a set. Otherwise, return nil */
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.emptyset[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
/* If count is zero, serve an empty set ASAP to avoid special
|
||||
|
||||
+5
-5
@@ -848,7 +848,7 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
|
||||
argv[11] = createStringObject("JUSTID",6);
|
||||
argv[12] = createStringObject("LASTID",6);
|
||||
argv[13] = createObjectFromStreamID(&group->last_id);
|
||||
propagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
alsoPropagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[3]);
|
||||
decrRefCount(argv[4]);
|
||||
@@ -875,7 +875,7 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
|
||||
argv[2] = key;
|
||||
argv[3] = groupname;
|
||||
argv[4] = createObjectFromStreamID(&group->last_id);
|
||||
propagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
alsoPropagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
decrRefCount(argv[4]);
|
||||
@@ -1068,9 +1068,7 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
|
||||
* by the user by other means. In that case we signal it emitting
|
||||
* the ID but then a NULL entry for the fields. */
|
||||
addReplyArrayLen(c,2);
|
||||
streamID id;
|
||||
streamDecodeID(ri.key,&id);
|
||||
addReplyStreamID(c,&id);
|
||||
addReplyStreamID(c,&thisid);
|
||||
addReplyNullArray(c);
|
||||
} else {
|
||||
streamNACK *nack = ri.data;
|
||||
@@ -1850,6 +1848,8 @@ NULL
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-destroy",
|
||||
c->argv[2],c->db->id);
|
||||
/* We want to unblock any XREADGROUP consumers with -NOGROUP. */
|
||||
signalKeyAsReady(c->db,c->argv[2]);
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
|
||||
+28
-22
@@ -44,7 +44,7 @@
|
||||
rax *TrackingTable = NULL;
|
||||
rax *PrefixTable = NULL;
|
||||
uint64_t TrackingTableTotalItems = 0; /* Total number of IDs stored across
|
||||
the whole tracking table. This givesn
|
||||
the whole tracking table. This gives
|
||||
an hint about the total memory we
|
||||
are using server side for CSC. */
|
||||
robj *TrackingChannelName;
|
||||
@@ -93,7 +93,8 @@ void disableTracking(client *c) {
|
||||
if (c->flags & CLIENT_TRACKING) {
|
||||
server.tracking_clients--;
|
||||
c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR|
|
||||
CLIENT_TRACKING_BCAST);
|
||||
CLIENT_TRACKING_BCAST|CLIENT_TRACKING_OPTIN|
|
||||
CLIENT_TRACKING_OPTOUT|CLIENT_TRACKING_CACHING);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,10 +125,11 @@ void enableBcastTrackingForPrefix(client *c, char *prefix, size_t plen) {
|
||||
* eventually get freed, we'll send a message to the original client to
|
||||
* inform it of the condition. Multiple clients can redirect the invalidation
|
||||
* messages to the same client ID. */
|
||||
void enableTracking(client *c, uint64_t redirect_to, int bcast, robj **prefix, size_t numprefix) {
|
||||
void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix) {
|
||||
if (!(c->flags & CLIENT_TRACKING)) server.tracking_clients++;
|
||||
c->flags |= CLIENT_TRACKING;
|
||||
c->flags &= ~(CLIENT_TRACKING_BROKEN_REDIR|CLIENT_TRACKING_BCAST);
|
||||
c->flags &= ~(CLIENT_TRACKING_BROKEN_REDIR|CLIENT_TRACKING_BCAST|
|
||||
CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
|
||||
c->client_tracking_redirection = redirect_to;
|
||||
if (TrackingTable == NULL) {
|
||||
TrackingTable = raxNew();
|
||||
@@ -135,7 +137,7 @@ void enableTracking(client *c, uint64_t redirect_to, int bcast, robj **prefix, s
|
||||
TrackingChannelName = createStringObject("__redis__:invalidate",20);
|
||||
}
|
||||
|
||||
if (bcast) {
|
||||
if (options & CLIENT_TRACKING_BCAST) {
|
||||
c->flags |= CLIENT_TRACKING_BCAST;
|
||||
if (numprefix == 0) enableBcastTrackingForPrefix(c,"",0);
|
||||
for (size_t j = 0; j < numprefix; j++) {
|
||||
@@ -143,14 +145,23 @@ void enableTracking(client *c, uint64_t redirect_to, int bcast, robj **prefix, s
|
||||
enableBcastTrackingForPrefix(c,sdsprefix,sdslen(sdsprefix));
|
||||
}
|
||||
}
|
||||
c->flags |= options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
|
||||
}
|
||||
|
||||
/* This function is called after the excution of a readonly command in the
|
||||
* case the client 'c' has keys tracking enabled. It will populate the
|
||||
* tracking ivalidation table according to the keys the user fetched, so that
|
||||
* Redis will know what are the clients that should receive an invalidation
|
||||
* message with certain groups of keys are modified. */
|
||||
/* This function is called after the execution of a readonly command in the
|
||||
* case the client 'c' has keys tracking enabled and the tracking is not
|
||||
* in BCAST mode. It will populate the tracking invalidation table according
|
||||
* to the keys the user fetched, so that Redis will know what are the clients
|
||||
* that should receive an invalidation message with certain groups of keys
|
||||
* are modified. */
|
||||
void trackingRememberKeys(client *c) {
|
||||
/* Return if we are in optin/out mode and the right CACHING command
|
||||
* was/wasn't given in order to modify the default behavior. */
|
||||
uint64_t optin = c->flags & CLIENT_TRACKING_OPTIN;
|
||||
uint64_t optout = c->flags & CLIENT_TRACKING_OPTOUT;
|
||||
uint64_t caching_given = c->flags & CLIENT_TRACKING_CACHING;
|
||||
if ((optin && !caching_given) || (optout && caching_given)) return;
|
||||
|
||||
int numkeys;
|
||||
int *keys = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
|
||||
if (keys == NULL) return;
|
||||
@@ -260,7 +271,7 @@ void trackingInvalidateKey(robj *keyobj) {
|
||||
trackingRememberKeyToBroadcast(sdskey,sdslen(sdskey));
|
||||
|
||||
rax *ids = raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
|
||||
if (ids == raxNotFound) return;;
|
||||
if (ids == raxNotFound) return;
|
||||
|
||||
raxIterator ri;
|
||||
raxStart(&ri,ids);
|
||||
@@ -292,19 +303,12 @@ void trackingInvalidateKey(robj *keyobj) {
|
||||
}
|
||||
|
||||
/* This function is called when one or all the Redis databases are flushed
|
||||
* (dbid == -1 in case of FLUSHALL). Caching slots are not specific for
|
||||
* each DB but are global: currently what we do is sending a special
|
||||
* (dbid == -1 in case of FLUSHALL). Caching keys are not specific for
|
||||
* each DB but are global: currently what we do is send a special
|
||||
* notification to clients with tracking enabled, invalidating the caching
|
||||
* slot "-1", which means, "all the keys", in order to avoid flooding clients
|
||||
* key "", which means, "all the keys", in order to avoid flooding clients
|
||||
* with many invalidation messages for all the keys they may hold.
|
||||
*
|
||||
* However trying to flush the tracking table here is very costly:
|
||||
* we need scanning 16 million caching slots in the table to check
|
||||
* if they are used, this introduces a big delay. So what we do is to really
|
||||
* flush the table in the case of FLUSHALL. When a FLUSHDB is called instead
|
||||
* we just send the invalidation message to all the clients, but don't
|
||||
* flush the table: it will slowly get garbage collected as more keys
|
||||
* are modified in the used caching slots. */
|
||||
*/
|
||||
void freeTrackingRadixTree(void *rt) {
|
||||
raxFree(rt);
|
||||
}
|
||||
@@ -325,6 +329,7 @@ void trackingInvalidateKeysOnFlush(int dbid) {
|
||||
/* In case of FLUSHALL, reclaim all the memory used by tracking. */
|
||||
if (dbid == -1 && TrackingTable) {
|
||||
raxFreeWithCallback(TrackingTable,freeTrackingRadixTree);
|
||||
TrackingTable = raxNew();
|
||||
TrackingTableTotalItems = 0;
|
||||
}
|
||||
}
|
||||
@@ -441,5 +446,6 @@ uint64_t trackingGetTotalItems(void) {
|
||||
}
|
||||
|
||||
uint64_t trackingGetTotalKeys(void) {
|
||||
if (TrackingTable == NULL) return 0;
|
||||
return raxSize(TrackingTable);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 5
|
||||
}
|
||||
|
||||
test "Cluster should start ok" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
|
||||
proc find_non_empty_master {} {
|
||||
set master_id_no {}
|
||||
foreach_redis_id id {
|
||||
if {[RI $id role] eq {master} && [R $id dbsize] > 0} {
|
||||
set master_id_no $id
|
||||
}
|
||||
}
|
||||
return $master_id_no
|
||||
}
|
||||
|
||||
proc get_one_of_my_replica {id} {
|
||||
set replica_port [lindex [lindex [lindex [R $id role] 2] 0] 1]
|
||||
set replica_id_num [get_instance_id_by_port redis $replica_port]
|
||||
return $replica_id_num
|
||||
}
|
||||
|
||||
proc cluster_write_keys_with_expire {id ttl} {
|
||||
set prefix [randstring 20 20 alpha]
|
||||
set port [get_instance_attrib redis $id port]
|
||||
set cluster [redis_cluster 127.0.0.1:$port]
|
||||
for {set j 100} {$j < 200} {incr j} {
|
||||
$cluster setex key_expire.$j $ttl $prefix.$j
|
||||
}
|
||||
$cluster close
|
||||
}
|
||||
|
||||
proc test_slave_load_expired_keys {aof} {
|
||||
test "Slave expired keys is loaded when restarted: appendonly=$aof" {
|
||||
set master_id [find_non_empty_master]
|
||||
set replica_id [get_one_of_my_replica $master_id]
|
||||
|
||||
set master_dbsize [R $master_id dbsize]
|
||||
set slave_dbsize [R $replica_id dbsize]
|
||||
assert_equal $master_dbsize $slave_dbsize
|
||||
|
||||
set data_ttl 5
|
||||
cluster_write_keys_with_expire $master_id $data_ttl
|
||||
after 100
|
||||
set replica_dbsize_1 [R $replica_id dbsize]
|
||||
assert {$replica_dbsize_1 > $slave_dbsize}
|
||||
|
||||
R $replica_id config set appendonly $aof
|
||||
R $replica_id config rewrite
|
||||
|
||||
set start_time [clock seconds]
|
||||
set end_time [expr $start_time+$data_ttl+2]
|
||||
R $replica_id save
|
||||
set replica_dbsize_2 [R $replica_id dbsize]
|
||||
assert {$replica_dbsize_2 > $slave_dbsize}
|
||||
kill_instance redis $replica_id
|
||||
|
||||
set master_port [get_instance_attrib redis $master_id port]
|
||||
exec ../../../src/redis-cli -h 127.0.0.1 -p $master_port debug sleep [expr $data_ttl+3] > /dev/null &
|
||||
|
||||
while {[clock seconds] <= $end_time} {
|
||||
#wait for $data_ttl seconds
|
||||
}
|
||||
restart_instance redis $replica_id
|
||||
|
||||
wait_for_condition 200 50 {
|
||||
[R $replica_id ping] eq {PONG}
|
||||
} else {
|
||||
fail "replica #$replica_id not started"
|
||||
}
|
||||
|
||||
set replica_dbsize_3 [R $replica_id dbsize]
|
||||
assert {$replica_dbsize_3 > $slave_dbsize}
|
||||
}
|
||||
}
|
||||
|
||||
test_slave_load_expired_keys no
|
||||
after 5000
|
||||
test_slave_load_expired_keys yes
|
||||
@@ -0,0 +1,61 @@
|
||||
# Test the meaningful offset implementation to make sure masters
|
||||
# are able to PSYNC with replicas even if the replication stream
|
||||
# has pending PINGs at the end.
|
||||
|
||||
start_server {tags {"psync2"}} {
|
||||
start_server {} {
|
||||
# Config
|
||||
set debug_msg 0 ; # Enable additional debug messages
|
||||
|
||||
for {set j 0} {$j < 2} {incr j} {
|
||||
set R($j) [srv [expr 0-$j] client]
|
||||
set R_host($j) [srv [expr 0-$j] host]
|
||||
set R_port($j) [srv [expr 0-$j] port]
|
||||
$R($j) CONFIG SET repl-ping-replica-period 1
|
||||
if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"}
|
||||
}
|
||||
|
||||
# Setup replication
|
||||
test "PSYNC2 meaningful offset: setup" {
|
||||
$R(1) replicaof $R_host(0) $R_port(0)
|
||||
$R(0) set foo bar
|
||||
wait_for_condition 50 1000 {
|
||||
[$R(0) dbsize] == 1 && [$R(1) dbsize] == 1
|
||||
} else {
|
||||
fail "Replicas not replicating from master"
|
||||
}
|
||||
}
|
||||
|
||||
test "PSYNC2 meaningful offset: write and wait replication" {
|
||||
$R(0) INCR counter
|
||||
$R(0) INCR counter
|
||||
$R(0) INCR counter
|
||||
wait_for_condition 50 1000 {
|
||||
[$R(0) GET counter] eq [$R(1) GET counter]
|
||||
} else {
|
||||
fail "Master and replica don't agree about counter"
|
||||
}
|
||||
}
|
||||
|
||||
# In this test we'll make sure the replica will get stuck, but with
|
||||
# an active connection: this way the master will continue to send PINGs
|
||||
# every second (we modified the PING period earlier)
|
||||
test "PSYNC2 meaningful offset: pause replica and promote it" {
|
||||
$R(1) MULTI
|
||||
$R(1) DEBUG SLEEP 5
|
||||
$R(1) SLAVEOF NO ONE
|
||||
$R(1) EXEC
|
||||
$R(1) ping ; # Wait for it to return back available
|
||||
}
|
||||
|
||||
test "Make the old master a replica of the new one and check conditions" {
|
||||
set sync_partial [status $R(1) sync_partial_ok]
|
||||
assert {$sync_partial == 0}
|
||||
$R(0) REPLICAOF $R_host(1) $R_port(1)
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R(1) sync_partial_ok] == 1
|
||||
} else {
|
||||
fail "The new master was not able to partial sync"
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -114,6 +114,27 @@ start_server {} {
|
||||
}
|
||||
}
|
||||
|
||||
# wait for all the slaves to be in sync with the master
|
||||
set master_ofs [status $R($master_id) master_repl_offset]
|
||||
wait_for_condition 500 100 {
|
||||
$master_ofs == [status $R(0) master_repl_offset] &&
|
||||
$master_ofs == [status $R(1) master_repl_offset] &&
|
||||
$master_ofs == [status $R(2) master_repl_offset] &&
|
||||
$master_ofs == [status $R(3) master_repl_offset] &&
|
||||
$master_ofs == [status $R(4) master_repl_offset]
|
||||
} else {
|
||||
if {$debug_msg} {
|
||||
for {set j 0} {$j < 5} {incr j} {
|
||||
puts "$j: sync_full: [status $R($j) sync_full]"
|
||||
puts "$j: id1 : [status $R($j) master_replid]:[status $R($j) master_repl_offset]"
|
||||
puts "$j: id2 : [status $R($j) master_replid2]:[status $R($j) second_repl_offset]"
|
||||
puts "$j: backlog : firstbyte=[status $R($j) repl_backlog_first_byte_offset] len=[status $R($j) repl_backlog_histlen]"
|
||||
puts "---"
|
||||
}
|
||||
}
|
||||
fail "Slaves are not in sync with the master after too long time."
|
||||
}
|
||||
|
||||
# Put down the old master so that it cannot generate more
|
||||
# replication stream, this way in the next master switch, the time at
|
||||
# which we move slaves away is not important, each will have full
|
||||
|
||||
@@ -42,7 +42,7 @@ int fork_create(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
|
||||
/* child */
|
||||
RedisModule_Log(ctx, "notice", "fork child started");
|
||||
usleep(200000);
|
||||
usleep(500000);
|
||||
RedisModule_Log(ctx, "notice", "fork child exiting");
|
||||
RedisModule_ExitFromChild(code_to_exit_with);
|
||||
/* unreachable */
|
||||
|
||||
@@ -74,6 +74,7 @@ int test_ld_conv(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_ReplyWithError(ctx, err);
|
||||
goto final;
|
||||
}
|
||||
|
||||
/* Make sure we can't convert a string that has \0 in it */
|
||||
char buf[4] = "123";
|
||||
buf[1] = '\0';
|
||||
@@ -81,8 +82,11 @@ int test_ld_conv(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
long double ld3;
|
||||
if (RedisModule_StringToLongDouble(s3, &ld3) == REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithError(ctx, "Invalid string successfully converted to long double");
|
||||
RedisModule_FreeString(ctx, s3);
|
||||
goto final;
|
||||
}
|
||||
RedisModule_FreeString(ctx, s3);
|
||||
|
||||
RedisModule_ReplyWithLongDouble(ctx, ld2);
|
||||
final:
|
||||
RedisModule_FreeString(ctx, s1);
|
||||
|
||||
+92
-41
@@ -53,6 +53,7 @@ proc kill_server config {
|
||||
}
|
||||
|
||||
# kill server and wait for the process to be totally exited
|
||||
send_data_packet $::test_server_fd server-killing $pid
|
||||
catch {exec kill $pid}
|
||||
if {$::valgrind} {
|
||||
set max_wait 60000
|
||||
@@ -140,15 +141,30 @@ proc tags {tags code} {
|
||||
uplevel 1 $code
|
||||
set ::tags [lrange $::tags 0 end-[llength $tags]]
|
||||
}
|
||||
|
||||
# Write the configuration in the dictionary 'config' in the specified
|
||||
# file name.
|
||||
proc create_server_config_file {filename config} {
|
||||
set fp [open $filename w+]
|
||||
foreach directive [dict keys $config] {
|
||||
puts -nonewline $fp "$directive "
|
||||
puts $fp [dict get $config $directive]
|
||||
}
|
||||
close $fp
|
||||
}
|
||||
|
||||
proc start_server {options {code undefined}} {
|
||||
# If we are running against an external server, we just push the
|
||||
# host/port pair in the stack the first time
|
||||
if {$::external} {
|
||||
if {[llength $::servers] == 0} {
|
||||
set srv {}
|
||||
# In test_server_main(tests/test_helper.tcl:215~218), increase the value of start_port
|
||||
# and assign it to ::port through the `--port` option, so we need to reduce it.
|
||||
set baseport [expr {$::port-100}]
|
||||
dict set srv "host" $::host
|
||||
dict set srv "port" $::port
|
||||
set client [redis $::host $::port 0 $::tls]
|
||||
dict set srv "port" $baseport
|
||||
set client [redis $::host $baseport 0 $::tls]
|
||||
dict set srv "client" $client
|
||||
$client select 9
|
||||
|
||||
@@ -221,56 +237,91 @@ proc start_server {options {code undefined}} {
|
||||
|
||||
# write new configuration to temporary file
|
||||
set config_file [tmpfile redis.conf]
|
||||
set fp [open $config_file w+]
|
||||
foreach directive [dict keys $config] {
|
||||
puts -nonewline $fp "$directive "
|
||||
puts $fp [dict get $config $directive]
|
||||
}
|
||||
close $fp
|
||||
create_server_config_file $config_file $config
|
||||
|
||||
set stdout [format "%s/%s" [dict get $config "dir"] "stdout"]
|
||||
set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
|
||||
|
||||
if {$::valgrind} {
|
||||
set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
} elseif ($::stack_logging) {
|
||||
set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
} else {
|
||||
set pid [exec src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
}
|
||||
# We need a loop here to retry with different ports.
|
||||
set server_started 0
|
||||
while {$server_started == 0} {
|
||||
if {$::verbose} {
|
||||
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
|
||||
}
|
||||
|
||||
# Tell the test server about this new instance.
|
||||
send_data_packet $::test_server_fd server-spawned $pid
|
||||
send_data_packet $::test_server_fd "server-spawning" "port $::port"
|
||||
|
||||
# check that the server actually started
|
||||
# ugly but tries to be as fast as possible...
|
||||
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
|
||||
if {$::valgrind} {
|
||||
set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
} elseif ($::stack_logging) {
|
||||
set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
} else {
|
||||
set pid [exec src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
}
|
||||
|
||||
if {$::verbose} {
|
||||
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
|
||||
}
|
||||
# Tell the test server about this new instance.
|
||||
send_data_packet $::test_server_fd server-spawned $pid
|
||||
|
||||
if {$code ne "undefined"} {
|
||||
set serverisup [server_is_up $::host $::port $retrynum]
|
||||
} else {
|
||||
set serverisup 1
|
||||
}
|
||||
# check that the server actually started
|
||||
# ugly but tries to be as fast as possible...
|
||||
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
|
||||
|
||||
if {$::verbose} {
|
||||
puts ""
|
||||
}
|
||||
# Wait for actual startup
|
||||
set checkperiod 100; # Milliseconds
|
||||
set maxiter [expr {120*1000/100}] ; # Wait up to 2 minutes.
|
||||
set port_busy 0
|
||||
while {![info exists _pid]} {
|
||||
regexp {PID:\s(\d+)} [exec cat $stdout] _ _pid
|
||||
after $checkperiod
|
||||
incr maxiter -1
|
||||
if {$maxiter == 0} {
|
||||
start_server_error $config_file "No PID detected in log $stdout"
|
||||
puts "--- LOG CONTENT ---"
|
||||
puts [exec cat $stdout]
|
||||
puts "-------------------"
|
||||
break
|
||||
}
|
||||
|
||||
if {!$serverisup} {
|
||||
set err {}
|
||||
append err [exec cat $stdout] "\n" [exec cat $stderr]
|
||||
start_server_error $config_file $err
|
||||
return
|
||||
}
|
||||
# Check if the port is actually busy and the server failed
|
||||
# for this reason.
|
||||
if {[regexp {Could not create server TCP} [exec cat $stdout]]} {
|
||||
set port_busy 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# Wait for actual startup
|
||||
while {![info exists _pid]} {
|
||||
regexp {PID:\s(\d+)} [exec cat $stdout] _ _pid
|
||||
after 100
|
||||
# Sometimes we have to try a different port, even if we checked
|
||||
# for availability. Other test clients may grab the port before we
|
||||
# are able to do it for example.
|
||||
if {$port_busy} {
|
||||
puts "Port $::port was already busy, trying another port..."
|
||||
set ::port [find_available_port [expr {$::port+1}]]
|
||||
if {$::tls} {
|
||||
dict set config "tls-port" $::port
|
||||
} else {
|
||||
dict set config port $::port
|
||||
}
|
||||
create_server_config_file $config_file $config
|
||||
continue; # Try again
|
||||
}
|
||||
|
||||
if {$code ne "undefined"} {
|
||||
set serverisup [server_is_up $::host $::port $retrynum]
|
||||
} else {
|
||||
set serverisup 1
|
||||
}
|
||||
|
||||
if {$::verbose} {
|
||||
puts ""
|
||||
}
|
||||
|
||||
if {!$serverisup} {
|
||||
set err {}
|
||||
append err [exec cat $stdout] "\n" [exec cat $stderr]
|
||||
start_server_error $config_file $err
|
||||
return
|
||||
}
|
||||
set server_started 1
|
||||
}
|
||||
|
||||
# setup properties to be able to initialize a client object
|
||||
|
||||
+14
-5
@@ -47,6 +47,7 @@ set ::all_tests {
|
||||
integration/logging
|
||||
integration/psync2
|
||||
integration/psync2-reg
|
||||
integration/psync2-pingoff
|
||||
unit/pubsub
|
||||
unit/slowlog
|
||||
unit/scripting
|
||||
@@ -87,7 +88,7 @@ set ::file ""; # If set, runs only the tests in this comma separated list
|
||||
set ::curfile ""; # Hold the filename of the current suite
|
||||
set ::accurate 0; # If true runs fuzz tests with more iterations
|
||||
set ::force_failure 0
|
||||
set ::timeout 600; # 10 minutes without progresses will quit the test.
|
||||
set ::timeout 1200; # 20 minutes without progresses will quit the test.
|
||||
set ::last_progress [clock seconds]
|
||||
set ::active_servers {} ; # Pids of active Redis instances.
|
||||
set ::dont_clean 0
|
||||
@@ -289,7 +290,7 @@ proc read_from_test_client fd {
|
||||
puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)"
|
||||
lappend ::clients_time_history $elapsed $data
|
||||
signal_idle_client $fd
|
||||
set ::active_clients_task($fd) DONE
|
||||
set ::active_clients_task($fd) "(DONE) $data"
|
||||
} elseif {$status eq {ok}} {
|
||||
if {!$::quiet} {
|
||||
puts "\[[colorstr green $status]\]: $data"
|
||||
@@ -320,10 +321,16 @@ proc read_from_test_client fd {
|
||||
exit 1
|
||||
} elseif {$status eq {testing}} {
|
||||
set ::active_clients_task($fd) "(IN PROGRESS) $data"
|
||||
} elseif {$status eq {server-spawning}} {
|
||||
set ::active_clients_task($fd) "(SPAWNING SERVER) $data"
|
||||
} elseif {$status eq {server-spawned}} {
|
||||
lappend ::active_servers $data
|
||||
set ::active_clients_task($fd) "(SPAWNED SERVER) pid:$data"
|
||||
} elseif {$status eq {server-killing}} {
|
||||
set ::active_clients_task($fd) "(KILLING SERVER) pid:$data"
|
||||
} elseif {$status eq {server-killed}} {
|
||||
set ::active_servers [lsearch -all -inline -not -exact $::active_servers $data]
|
||||
set ::active_clients_task($fd) "(KILLED SERVER) pid:$data"
|
||||
} else {
|
||||
if {!$::quiet} {
|
||||
puts "\[$status\]: $data"
|
||||
@@ -333,7 +340,7 @@ proc read_from_test_client fd {
|
||||
|
||||
proc show_clients_state {} {
|
||||
# The following loop is only useful for debugging tests that may
|
||||
# enter an infinite loop. Commented out normally.
|
||||
# enter an infinite loop.
|
||||
foreach x $::active_clients {
|
||||
if {[info exist ::active_clients_task($x)]} {
|
||||
puts "$x => $::active_clients_task($x)"
|
||||
@@ -363,8 +370,6 @@ proc signal_idle_client fd {
|
||||
set ::active_clients \
|
||||
[lsearch -all -inline -not -exact $::active_clients $fd]
|
||||
|
||||
if 0 {show_clients_state}
|
||||
|
||||
# New unit to process?
|
||||
if {$::next_test != [llength $::all_tests]} {
|
||||
if {!$::quiet} {
|
||||
@@ -380,6 +385,7 @@ proc signal_idle_client fd {
|
||||
}
|
||||
} else {
|
||||
lappend ::idle_clients $fd
|
||||
set ::active_clients_task($fd) "SLEEPING, no more units to assign"
|
||||
if {[llength $::active_clients] == 0} {
|
||||
the_end
|
||||
}
|
||||
@@ -500,6 +506,9 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--host}} {
|
||||
set ::external 1
|
||||
set ::host $arg
|
||||
# If we use an external server, we can only set numclients to 1,
|
||||
# otherwise the port will be miscalculated.
|
||||
set ::numclients 1
|
||||
incr j
|
||||
} elseif {$opt eq {--port}} {
|
||||
set ::port $arg
|
||||
|
||||
@@ -248,4 +248,11 @@ start_server {tags {"acl"}} {
|
||||
r AUTH default ""
|
||||
assert {[llength [r ACL LOG]] == 5}
|
||||
}
|
||||
|
||||
test {When default user is off, new connections are not authenticated} {
|
||||
r ACL setuser default off
|
||||
catch {set rd1 [redis_deferring_client]} e
|
||||
r ACL setuser default on
|
||||
set e
|
||||
} {*NOAUTH*}
|
||||
}
|
||||
|
||||
@@ -199,3 +199,34 @@ start_server {tags {"bitops"}} {
|
||||
r del mystring
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
set slave [srv 0 client]
|
||||
|
||||
test {BITFIELD: setup slave} {
|
||||
$slave slaveof $master_host $master_port
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
|
||||
test {BITFIELD: write on master, read on slave} {
|
||||
$master del bits
|
||||
assert_equal 0 [$master bitfield bits set u8 0 255]
|
||||
assert_equal 255 [$master bitfield bits set u8 0 100]
|
||||
wait_for_ofs_sync $master $slave
|
||||
assert_equal 100 [$slave bitfield_ro bits get u8 0]
|
||||
}
|
||||
|
||||
test {BITFIELD_RO fails when write option is used} {
|
||||
catch {$slave bitfield_ro bits set u8 0 100 get u8 0} err
|
||||
assert_match {*ERR BITFIELD_RO only supports the GET subcommand*} $err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ start_server {tags {"memefficiency"}} {
|
||||
start_server {tags {"defrag"}} {
|
||||
if {[string match {*jemalloc*} [s mem_allocator]]} {
|
||||
test "Active defrag" {
|
||||
r config set save "" ;# prevent bgsave from interfereing with save below
|
||||
r config set hz 100
|
||||
r config set activedefrag no
|
||||
r config set active-defrag-threshold-lower 5
|
||||
r config set active-defrag-cycle-min 65
|
||||
@@ -46,8 +48,8 @@ start_server {tags {"defrag"}} {
|
||||
r config set active-defrag-ignore-bytes 2mb
|
||||
r config set maxmemory 100mb
|
||||
r config set maxmemory-policy allkeys-lru
|
||||
r debug populate 700000 asdf 150
|
||||
r debug populate 170000 asdf 300
|
||||
r debug populate 700000 asdf1 150
|
||||
r debug populate 170000 asdf2 300
|
||||
r ping ;# trigger eviction following the previous population
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
set frag [s allocator_frag_ratio]
|
||||
@@ -55,6 +57,11 @@ start_server {tags {"defrag"}} {
|
||||
puts "frag $frag"
|
||||
}
|
||||
assert {$frag >= 1.4}
|
||||
|
||||
r config set latency-monitor-threshold 5
|
||||
r latency reset
|
||||
r config set maxmemory 110mb ;# prevent further eviction (not to fail the digest test)
|
||||
set digest [r debug digest]
|
||||
catch {r config set activedefrag yes} e
|
||||
if {![string match {DISABLED*} $e]} {
|
||||
# Wait for the active defrag to start working (decision once a
|
||||
@@ -78,19 +85,37 @@ start_server {tags {"defrag"}} {
|
||||
# Test the the fragmentation is lower.
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
set frag [s allocator_frag_ratio]
|
||||
set max_latency 0
|
||||
foreach event [r latency latest] {
|
||||
lassign $event eventname time latency max
|
||||
if {$eventname == "active-defrag-cycle"} {
|
||||
set max_latency $max
|
||||
}
|
||||
}
|
||||
if {$::verbose} {
|
||||
puts "frag $frag"
|
||||
puts "max latency $max_latency"
|
||||
puts [r latency latest]
|
||||
puts [r latency history active-defrag-cycle]
|
||||
}
|
||||
assert {$frag < 1.1}
|
||||
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
|
||||
assert {$max_latency <= 30}
|
||||
} else {
|
||||
set _ ""
|
||||
}
|
||||
} {}
|
||||
# verify the data isn't corrupted or changed
|
||||
set newdigest [r debug digest]
|
||||
assert {$digest eq $newdigest}
|
||||
r save ;# saving an rdb iterates over all the data / pointers
|
||||
} {OK}
|
||||
|
||||
test "Active defrag big keys" {
|
||||
r flushdb
|
||||
r config resetstat
|
||||
r config set save "" ;# prevent bgsave from interfereing with save below
|
||||
r config set hz 100
|
||||
r config set activedefrag no
|
||||
r config set active-defrag-max-scan-fields 1000
|
||||
r config set active-defrag-threshold-lower 5
|
||||
@@ -142,7 +167,7 @@ start_server {tags {"defrag"}} {
|
||||
for {set j 0} {$j < 500000} {incr j} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
assert {[r dbsize] == 500010}
|
||||
assert_equal [r dbsize] 500010
|
||||
|
||||
# create some fragmentation
|
||||
for {set j 0} {$j < 500000} {incr j 2} {
|
||||
@@ -151,7 +176,7 @@ start_server {tags {"defrag"}} {
|
||||
for {set j 0} {$j < 500000} {incr j 2} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
assert {[r dbsize] == 250010}
|
||||
assert_equal [r dbsize] 250010
|
||||
|
||||
# start defrag
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
@@ -200,14 +225,106 @@ start_server {tags {"defrag"}} {
|
||||
puts [r latency history active-defrag-cycle]
|
||||
}
|
||||
assert {$frag < 1.1}
|
||||
# due to high fragmentation, 10hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 75ms
|
||||
assert {$max_latency <= 120}
|
||||
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
|
||||
assert {$max_latency <= 30}
|
||||
}
|
||||
# verify the data isn't corrupted or changed
|
||||
set newdigest [r debug digest]
|
||||
assert {$digest eq $newdigest}
|
||||
r save ;# saving an rdb iterates over all the data / pointers
|
||||
} {OK}
|
||||
|
||||
test "Active defrag big list" {
|
||||
r flushdb
|
||||
r config resetstat
|
||||
r config set save "" ;# prevent bgsave from interfereing with save below
|
||||
r config set hz 100
|
||||
r config set activedefrag no
|
||||
r config set active-defrag-max-scan-fields 1000
|
||||
r config set active-defrag-threshold-lower 5
|
||||
r config set active-defrag-cycle-min 65
|
||||
r config set active-defrag-cycle-max 75
|
||||
r config set active-defrag-ignore-bytes 2mb
|
||||
r config set maxmemory 0
|
||||
r config set list-max-ziplist-size 5 ;# list of 500k items will have 100k quicklist nodes
|
||||
|
||||
# create big keys with 10k items
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
set expected_frag 1.7
|
||||
# add a mass of list nodes to two lists (allocations are interlaced)
|
||||
set val [string repeat A 100] ;# 5 items of 100 bytes puts us in the 640 bytes bin, which has 32 regs, so high potential for fragmentation
|
||||
for {set j 0} {$j < 500000} {incr j} {
|
||||
$rd lpush biglist1 $val
|
||||
$rd lpush biglist2 $val
|
||||
}
|
||||
for {set j 0} {$j < 500000} {incr j} {
|
||||
$rd read ; # Discard replies
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
|
||||
# create some fragmentation
|
||||
r del biglist2
|
||||
|
||||
# start defrag
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
set frag [s allocator_frag_ratio]
|
||||
if {$::verbose} {
|
||||
puts "frag $frag"
|
||||
}
|
||||
|
||||
assert {$frag >= $expected_frag}
|
||||
r config set latency-monitor-threshold 5
|
||||
r latency reset
|
||||
|
||||
set digest [r debug digest]
|
||||
catch {r config set activedefrag yes} e
|
||||
if {![string match {DISABLED*} $e]} {
|
||||
# wait for the active defrag to start working (decision once a second)
|
||||
wait_for_condition 50 100 {
|
||||
[s active_defrag_running] ne 0
|
||||
} else {
|
||||
fail "defrag not started."
|
||||
}
|
||||
|
||||
# wait for the active defrag to stop working
|
||||
wait_for_condition 500 100 {
|
||||
[s active_defrag_running] eq 0
|
||||
} else {
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
puts [r info memory]
|
||||
puts [r info stats]
|
||||
puts [r memory malloc-stats]
|
||||
fail "defrag didn't stop."
|
||||
}
|
||||
|
||||
# test the the fragmentation is lower
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
set frag [s allocator_frag_ratio]
|
||||
set max_latency 0
|
||||
foreach event [r latency latest] {
|
||||
lassign $event eventname time latency max
|
||||
if {$eventname == "active-defrag-cycle"} {
|
||||
set max_latency $max
|
||||
}
|
||||
}
|
||||
if {$::verbose} {
|
||||
puts "frag $frag"
|
||||
puts "max latency $max_latency"
|
||||
puts [r latency latest]
|
||||
puts [r latency history active-defrag-cycle]
|
||||
}
|
||||
assert {$frag < 1.1}
|
||||
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
|
||||
assert {$max_latency <= 30}
|
||||
}
|
||||
# verify the data isn't corrupted or changed
|
||||
set newdigest [r debug digest]
|
||||
assert {$digest eq $newdigest}
|
||||
r save ;# saving an rdb iterates over all the data / pointers
|
||||
r del biglist1 ;# coverage for quicklistBookmarksClear
|
||||
} {1}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,8 @@ start_server {tags {"modules"}} {
|
||||
|
||||
test {Module fork kill} {
|
||||
r fork.create 3
|
||||
after 20
|
||||
after 250
|
||||
r fork.kill
|
||||
after 100
|
||||
|
||||
assert {[count_log_message "fork child started"] eq "2"}
|
||||
assert {[count_log_message "Received SIGUSR1 in child"] eq "1"}
|
||||
|
||||
@@ -320,4 +320,76 @@ start_server {tags {"multi"}} {
|
||||
$rd close
|
||||
r ping
|
||||
} {PONG}
|
||||
|
||||
test {MULTI and script timeout} {
|
||||
# check that if MULTI arrives during timeout, it is either refused, or
|
||||
# allowed to pass, and we don't end up executing half of the transaction
|
||||
set rd1 [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
r config set lua-time-limit 10
|
||||
r set xx 1
|
||||
$rd1 eval {while true do end} 0
|
||||
after 200
|
||||
catch { $rd2 multi; $rd2 read } e
|
||||
catch { $rd2 incr xx; $rd2 read } e
|
||||
r script kill
|
||||
after 200 ; # Give some time to Lua to call the hook again...
|
||||
catch { $rd2 incr xx; $rd2 read } e
|
||||
catch { $rd2 exec; $rd2 read } e
|
||||
set xx [r get xx]
|
||||
# make sure that either the whole transcation passed or none of it (we actually expect none)
|
||||
assert { $xx == 1 || $xx == 3}
|
||||
# check that the connection is no longer in multi state
|
||||
$rd2 ping asdf
|
||||
set pong [$rd2 read]
|
||||
assert_equal $pong "asdf"
|
||||
}
|
||||
|
||||
test {EXEC and script timeout} {
|
||||
# check that if EXEC arrives during timeout, we don't end up executing
|
||||
# half of the transaction, and also that we exit the multi state
|
||||
set rd1 [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
r config set lua-time-limit 10
|
||||
r set xx 1
|
||||
catch { $rd2 multi; $rd2 read } e
|
||||
catch { $rd2 incr xx; $rd2 read } e
|
||||
$rd1 eval {while true do end} 0
|
||||
after 200
|
||||
catch { $rd2 incr xx; $rd2 read } e
|
||||
catch { $rd2 exec; $rd2 read } e
|
||||
r script kill
|
||||
after 200 ; # Give some time to Lua to call the hook again...
|
||||
set xx [r get xx]
|
||||
# make sure that either the whole transcation passed or none of it (we actually expect none)
|
||||
assert { $xx == 1 || $xx == 3}
|
||||
# check that the connection is no longer in multi state
|
||||
$rd2 ping asdf
|
||||
set pong [$rd2 read]
|
||||
assert_equal $pong "asdf"
|
||||
}
|
||||
|
||||
test {MULTI-EXEC body and script timeout} {
|
||||
# check that we don't run an imcomplete transaction due to some commands
|
||||
# arriving during busy script
|
||||
set rd1 [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
r config set lua-time-limit 10
|
||||
r set xx 1
|
||||
catch { $rd2 multi; $rd2 read } e
|
||||
catch { $rd2 incr xx; $rd2 read } e
|
||||
$rd1 eval {while true do end} 0
|
||||
after 200
|
||||
catch { $rd2 incr xx; $rd2 read } e
|
||||
r script kill
|
||||
after 200 ; # Give some time to Lua to call the hook again...
|
||||
catch { $rd2 exec; $rd2 read } e
|
||||
set xx [r get xx]
|
||||
# make sure that either the whole transcation passed or none of it (we actually expect none)
|
||||
assert { $xx == 1 || $xx == 3}
|
||||
# check that the connection is no longer in multi state
|
||||
$rd2 ping asdf
|
||||
set pong [$rd2 read]
|
||||
assert_equal $pong "asdf"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +161,15 @@ start_server {
|
||||
assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {mystream {}}
|
||||
}
|
||||
|
||||
test {XGROUP DESTROY should unblock XREADGROUP with -NOGROUP} {
|
||||
r del mystream
|
||||
r XGROUP CREATE mystream mygroup $ MKSTREAM
|
||||
set rd [redis_deferring_client]
|
||||
$rd XREADGROUP GROUP mygroup Alice BLOCK 100 STREAMS mystream ">"
|
||||
r XGROUP DESTROY mystream mygroup
|
||||
assert_error "*NOGROUP*" {$rd read}
|
||||
}
|
||||
|
||||
test {XCLAIM can claim PEL items from another consumer} {
|
||||
# Add 3 items into the stream, and create a consumer group
|
||||
r del mystream
|
||||
@@ -302,4 +311,17 @@ start_server {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"stream"} overrides {appendonly yes aof-use-rdb-preamble no}} {
|
||||
test {Empty stream with no lastid can be rewrite into AOF correctly} {
|
||||
r XGROUP CREATE mystream group-name $ MKSTREAM
|
||||
assert {[dict get [r xinfo stream mystream] length] == 0}
|
||||
set grpinfo [r xinfo groups mystream]
|
||||
r bgrewriteaof
|
||||
waitForBgrewriteaof r
|
||||
r debug loadaof
|
||||
assert {[dict get [r xinfo stream mystream] length] == 0}
|
||||
assert {[r xinfo groups mystream] == $grpinfo}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user