Compare commits
295
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d21f4769b8 | ||
|
|
57bd8feb8d | ||
|
|
cf9a3f7048 | ||
|
|
30db805548 | ||
|
|
2564963dc8 | ||
|
|
a1944c3e4d | ||
|
|
34d5804d4c | ||
|
|
ffcf7d5ab1 | ||
|
|
b2e295971f | ||
|
|
de474186bd | ||
|
|
2bf8c2c130 | ||
|
|
54ad626cce | ||
|
|
7393fd814e | ||
|
|
b246635d6d | ||
|
|
616c546b01 | ||
|
|
474adba9fa | ||
|
|
fa2c0582cf | ||
|
|
269760edbb | ||
|
|
bb3b5ddd19 | ||
|
|
b541ccef25 | ||
|
|
c1c99e9f4e | ||
|
|
a4152119c6 | ||
|
|
b75ae0bbea | ||
|
|
2d13bf4c59 | ||
|
|
9b4cb4addc | ||
|
|
8885921dfb | ||
|
|
b122cadc66 | ||
|
|
202c2ebec4 | ||
|
|
a8c08b9b76 | ||
|
|
34a79c353f | ||
|
|
bc64df9a66 | ||
|
|
81fe7a4733 | ||
|
|
dc997755bf | ||
|
|
6b64cc47a0 | ||
|
|
964224b77f | ||
|
|
ae40e5f362 | ||
|
|
25c231c4c1 | ||
|
|
d9565379da | ||
|
|
7c3c472b5e | ||
|
|
ffbbe5a720 | ||
|
|
314043552b | ||
|
|
5bfdfbe174 | ||
|
|
a3778f3b0f | ||
|
|
b1c2e1a19c | ||
|
|
9e7a8c0207 | ||
|
|
2cdf4cc656 | ||
|
|
c32c690de6 | ||
|
|
7560d347da | ||
|
|
b80e467023 | ||
|
|
bd1782fa0a | ||
|
|
8eefc9323d | ||
|
|
f03947a676 | ||
|
|
43aaf96163 | ||
|
|
e74f0aa6d1 | ||
|
|
e1b8b4b6da | ||
|
|
5bd46d33db | ||
|
|
e203a46cf3 | ||
|
|
14c32c3569 | ||
|
|
54e4bbeabd | ||
|
|
58104d8327 | ||
|
|
11182a1a58 | ||
|
|
dfb68cd235 | ||
|
|
fc7ecd8d35 | ||
|
|
6b0670daad | ||
|
|
38dd30af42 | ||
|
|
2d5aa00959 | ||
|
|
87f771bff1 | ||
|
|
2b36950e9b | ||
|
|
51ffd062d3 | ||
|
|
10db81af71 | ||
|
|
ea31a4eae3 | ||
|
|
f9fac7f777 | ||
|
|
413c2bc180 | ||
|
|
09dd7b5ff0 | ||
|
|
eddd8d34c4 | ||
|
|
86e564e9ff | ||
|
|
b2cd9fcab6 | ||
|
|
26e638a8e9 | ||
|
|
7bad78bd2f | ||
|
|
f8547e53f0 | ||
|
|
01a4b9892d | ||
|
|
634c64dd18 | ||
|
|
365dd037dc | ||
|
|
c3998728a2 | ||
|
|
c9097393bf | ||
|
|
936ade80b2 | ||
|
|
f85f36f50d | ||
|
|
d303bca587 | ||
|
|
8b768e8ea4 | ||
|
|
6476f1a979 | ||
|
|
86e9f48a0c | ||
|
|
ef446bf16d | ||
|
|
1d857a99d5 | ||
|
|
0a03187ac4 | ||
|
|
a2146c7592 | ||
|
|
2a84927f35 | ||
|
|
ee5044eef0 | ||
|
|
530fcf8687 | ||
|
|
53cb27b1d7 | ||
|
|
ab9d398835 | ||
|
|
4740424049 | ||
|
|
3fc4bf07cc | ||
|
|
d3b32ca48d | ||
|
|
33035cad04 | ||
|
|
5877c02c51 | ||
|
|
2d1d57eb47 | ||
|
|
591dba8055 | ||
|
|
a3e53cf9bc | ||
|
|
e91b81c612 | ||
|
|
e498d9ee3e | ||
|
|
78211aaaaf | ||
|
|
58f79e2ff4 | ||
|
|
1f598fc2bb | ||
|
|
de786186a5 | ||
|
|
6eb51bf1ec | ||
|
|
9390c384b8 | ||
|
|
ece658713b | ||
|
|
2a51bac44e | ||
|
|
52bc74f221 | ||
|
|
7d9326b1f3 | ||
|
|
9b01b64430 | ||
|
|
e67fb915eb | ||
|
|
79226cb9fa | ||
|
|
6798736909 | ||
|
|
1ed2ff5570 | ||
|
|
7127f15ebe | ||
|
|
3fcf959e60 | ||
|
|
ba4a5a3255 | ||
|
|
275905b328 | ||
|
|
9c500b89fb | ||
|
|
59b06b14c9 | ||
|
|
a4c7f34d3a | ||
|
|
c180bc7d98 | ||
|
|
469d6e2b37 | ||
|
|
c861e1e1ee | ||
|
|
a17390853d | ||
|
|
0fb5c4ebd8 | ||
|
|
e3b8492e83 | ||
|
|
238cebdd5e | ||
|
|
3773c06d28 | ||
|
|
7d9dd80db3 | ||
|
|
189a12afb4 | ||
|
|
22be435efe | ||
|
|
27fe8e9fb2 | ||
|
|
02d02a3754 | ||
|
|
8b7b4d6734 | ||
|
|
da2f9cd186 | ||
|
|
332a05dc33 | ||
|
|
5f88bd320e | ||
|
|
c33493277a | ||
|
|
0a942f1751 | ||
|
|
c4ad4765b0 | ||
|
|
23ec36909e | ||
|
|
271733f4f8 | ||
|
|
3f068b92b9 | ||
|
|
02777bb252 | ||
|
|
8c829d9e43 | ||
|
|
6878a3fedd | ||
|
|
8f7bf2841a | ||
|
|
c5d6f577f0 | ||
|
|
b8286d1fc9 | ||
|
|
104584b95e | ||
|
|
1210af3804 | ||
|
|
4a850be4dc | ||
|
|
f47607af02 | ||
|
|
74720ea993 | ||
|
|
aa5b4be02e | ||
|
|
69ce5c5d10 | ||
|
|
531647bb1b | ||
|
|
01b6966afc | ||
|
|
ffefc9f92d | ||
|
|
91999fce40 | ||
|
|
98db5739cc | ||
|
|
3f9e2322ec | ||
|
|
1409c545da | ||
|
|
94751543b0 | ||
|
|
4acb4da1d1 | ||
|
|
a62f786344 | ||
|
|
59bdd08214 | ||
|
|
443f279a3a | ||
|
|
4b2229e4b8 | ||
|
|
9cc83d2ad9 | ||
|
|
ed7e331051 | ||
|
|
b3391fd853 | ||
|
|
d7826823c0 | ||
|
|
95883313b5 | ||
|
|
2b36706a48 | ||
|
|
7c8ddab4f8 | ||
|
|
06263485d4 | ||
|
|
e084b5a39f | ||
|
|
0285c2714b | ||
|
|
cd90389b30 | ||
|
|
84fa8230e5 | ||
|
|
05ea8c6122 | ||
|
|
adeed29a99 | ||
|
|
9b05aafb50 | ||
|
|
76d87f47c7 | ||
|
|
7329cc3981 | ||
|
|
4e9cf4cc7e | ||
|
|
72d6d64771 | ||
|
|
1e272a6b52 | ||
|
|
de07deb4d2 | ||
|
|
1f72ec7dad | ||
|
|
f917e0da4c | ||
|
|
0dbfb1d154 | ||
|
|
c495d095ae | ||
|
|
3a7410a8a6 | ||
|
|
27e29f4fe6 | ||
|
|
713fe0b7d7 | ||
|
|
3c9b817217 | ||
|
|
2e4f53a711 | ||
|
|
3ec1a001fb | ||
|
|
41d16f7a4a | ||
|
|
432699845c | ||
|
|
17ac46ea78 | ||
|
|
53b8bf2c89 | ||
|
|
2cd1ae736f | ||
|
|
d21aabcedc | ||
|
|
636c693f44 | ||
|
|
3f79b2f883 | ||
|
|
baa9898821 | ||
|
|
634b096610 | ||
|
|
6ad34a4b78 | ||
|
|
86192f3038 | ||
|
|
4186879675 | ||
|
|
e91f0ea1b3 | ||
|
|
173d692bc2 | ||
|
|
afaaa91885 | ||
|
|
5ab6a54cc6 | ||
|
|
7aa9e6d2ae | ||
|
|
b2da5ea773 | ||
|
|
6712bce92c | ||
|
|
0b7691201e | ||
|
|
43bccbc3f4 | ||
|
|
a764c87b67 | ||
|
|
619317da6f | ||
|
|
af0284926c | ||
|
|
0f72257049 | ||
|
|
913070a9e8 | ||
|
|
7c64e88963 | ||
|
|
8444b46d20 | ||
|
|
074383f850 | ||
|
|
06bfeb482d | ||
|
|
90a6f7fc98 | ||
|
|
2988889db1 | ||
|
|
ce9e36eb01 | ||
|
|
6cf1a325d6 | ||
|
|
8e390a62ad | ||
|
|
ca4ca5073e | ||
|
|
151af73118 | ||
|
|
87538cb7fe | ||
|
|
0224be8811 | ||
|
|
47dea01c85 | ||
|
|
c55e3fbae5 | ||
|
|
5ad2a94a16 | ||
|
|
d634c36253 | ||
|
|
ac61f90625 | ||
|
|
7f9b9512b8 | ||
|
|
b53e73e159 | ||
|
|
c9f0456d81 | ||
|
|
dee11ebab8 | ||
|
|
b6f871cf42 | ||
|
|
d1adc85aa6 | ||
|
|
5b9ba26403 | ||
|
|
04542cff92 | ||
|
|
39f5c0713e | ||
|
|
b6ab4d04b6 | ||
|
|
2a32f0371e | ||
|
|
978a790b85 | ||
|
|
2d91fce970 | ||
|
|
94fe98666c | ||
|
|
16cce320c4 | ||
|
|
37b6e16ae1 | ||
|
|
3c85a88888 | ||
|
|
b1fc06f7eb | ||
|
|
001138aec3 | ||
|
|
e5db99ad4a | ||
|
|
5dc4fe1529 | ||
|
|
6eb0c52d4c | ||
|
|
ff1d548738 | ||
|
|
5f5b4f1508 | ||
|
|
3c4fe59e09 | ||
|
|
8521cde570 | ||
|
|
8abf9729f0 | ||
|
|
e3a61950a2 | ||
|
|
0b57a19c1b | ||
|
|
dbb6cb442a | ||
|
|
a1a07225b3 | ||
|
|
c76b9b644c | ||
|
|
27d08fb8f0 | ||
|
|
0f36e5e5d8 | ||
|
|
413d8239df | ||
|
|
04607b5350 | ||
|
|
087b19237c | ||
|
|
367035a983 |
+11
-4794
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
This README is just a fast *quick start* document. You can find more detailed documentation at [redis.io](https://redis.io).
|
||||
This README is just a fast *quick start* document. You can find more detailed documentation at http://redis.io.
|
||||
|
||||
What is Redis?
|
||||
--------------
|
||||
|
||||
Vendored
+1
-1
@@ -13,7 +13,7 @@
|
||||
#if defined(__sun__)
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#else
|
||||
#if !(defined(__APPLE__) && defined(__MACH__)) && !(defined(__FreeBSD__))
|
||||
#if !(defined(__APPLE__) && defined(__MACH__))
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Vendored
-10
@@ -385,7 +385,6 @@ void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
|
||||
#endif
|
||||
|
||||
mp_encode_array(L,buf,len);
|
||||
luaL_checkstack(L, 1, "in function mp_encode_lua_table_as_array");
|
||||
for (j = 1; j <= len; j++) {
|
||||
lua_pushnumber(L,j);
|
||||
lua_gettable(L,-2);
|
||||
@@ -401,7 +400,6 @@ void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) {
|
||||
* Lua API, we need to iterate a first time. Note that an alternative
|
||||
* would be to do a single run, and then hack the buffer to insert the
|
||||
* map opcodes for message pack. Too hackish for this lib. */
|
||||
luaL_checkstack(L, 3, "in function mp_encode_lua_table_as_map");
|
||||
lua_pushnil(L);
|
||||
while(lua_next(L,-2)) {
|
||||
lua_pop(L,1); /* remove value, keep key for next iteration. */
|
||||
@@ -517,14 +515,10 @@ int mp_pack(lua_State *L) {
|
||||
if (nargs == 0)
|
||||
return luaL_argerror(L, 0, "MessagePack pack needs input.");
|
||||
|
||||
if (!lua_checkstack(L, nargs))
|
||||
return luaL_argerror(L, 0, "Too many arguments for MessagePack pack.");
|
||||
|
||||
buf = mp_buf_new(L);
|
||||
for(i = 1; i <= nargs; i++) {
|
||||
/* Copy argument i to top of stack for _encode processing;
|
||||
* the encode function pops it from the stack when complete. */
|
||||
luaL_checkstack(L, 1, "in function mp_check");
|
||||
lua_pushvalue(L, i);
|
||||
|
||||
mp_encode_lua_type(L,buf,0);
|
||||
@@ -553,7 +547,6 @@ void mp_decode_to_lua_array(lua_State *L, mp_cur *c, size_t len) {
|
||||
int index = 1;
|
||||
|
||||
lua_newtable(L);
|
||||
luaL_checkstack(L, 1, "in function mp_decode_to_lua_array");
|
||||
while(len--) {
|
||||
lua_pushnumber(L,index++);
|
||||
mp_decode_to_lua_type(L,c);
|
||||
@@ -828,9 +821,6 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
* subtract the entire buffer size from the unprocessed size
|
||||
* to get our next start offset */
|
||||
int offset = len - c.left;
|
||||
|
||||
luaL_checkstack(L, 1, "in function mp_unpack_full");
|
||||
|
||||
/* Return offset -1 when we have have processed the entire buffer. */
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : offset);
|
||||
/* Results are returned with the arg elements still
|
||||
|
||||
Vendored
+25
-29
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
** {======================================================
|
||||
** Library for packing/unpacking structures.
|
||||
** $Id: struct.c,v 1.7 2018/05/11 22:04:31 roberto Exp $
|
||||
** $Id: struct.c,v 1.4 2012/07/04 18:54:29 roberto Exp $
|
||||
** See Copyright Notice at the end of this file
|
||||
** =======================================================
|
||||
*/
|
||||
@@ -15,8 +15,8 @@
|
||||
** h/H - signed/unsigned short
|
||||
** l/L - signed/unsigned long
|
||||
** T - size_t
|
||||
** i/In - signed/unsigned integer with size 'n' (default is size of int)
|
||||
** cn - sequence of 'n' chars (from/to a string); when packing, n==0 means
|
||||
** i/In - signed/unsigned integer with size `n' (default is size of int)
|
||||
** cn - sequence of `n' chars (from/to a string); when packing, n==0 means
|
||||
the whole string; when unpacking, n==0 means use the previous
|
||||
read number as the string length
|
||||
** s - zero-terminated string
|
||||
@@ -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;
|
||||
@@ -293,26 +295,21 @@ static int b_unpack (lua_State *L) {
|
||||
const char *fmt = luaL_checkstring(L, 1);
|
||||
size_t ld;
|
||||
const char *data = luaL_checklstring(L, 2, &ld);
|
||||
size_t pos = luaL_optinteger(L, 3, 1);
|
||||
luaL_argcheck(L, pos > 0, 3, "offset must be 1 or greater");
|
||||
pos--; /* Lua indexes are 1-based, but here we want 0-based for C
|
||||
* pointer math. */
|
||||
int n = 0; /* number of results */
|
||||
size_t pos = luaL_optinteger(L, 3, 1) - 1;
|
||||
defaultoptions(&h);
|
||||
lua_settop(L, 2);
|
||||
while (*fmt) {
|
||||
int opt = *fmt++;
|
||||
size_t size = optsize(L, opt, &fmt);
|
||||
pos += gettoalign(pos, &h, opt, size);
|
||||
luaL_argcheck(L, size <= ld && pos <= ld - size,
|
||||
2, "data string too short");
|
||||
/* stack space for item + next position */
|
||||
luaL_checkstack(L, 2, "too many results");
|
||||
luaL_argcheck(L, pos+size <= ld, 2, "data string too short");
|
||||
luaL_checkstack(L, 1, "too many results");
|
||||
switch (opt) {
|
||||
case 'b': case 'B': case 'h': case 'H':
|
||||
case 'l': case 'L': case 'T': case 'i': case 'I': { /* integer types */
|
||||
int issigned = islower(opt);
|
||||
lua_Number res = getinteger(data+pos, h.endian, issigned, size);
|
||||
lua_pushnumber(L, res); n++;
|
||||
lua_pushnumber(L, res);
|
||||
break;
|
||||
}
|
||||
case 'x': {
|
||||
@@ -322,26 +319,25 @@ static int b_unpack (lua_State *L) {
|
||||
float f;
|
||||
memcpy(&f, data+pos, size);
|
||||
correctbytes((char *)&f, sizeof(f), h.endian);
|
||||
lua_pushnumber(L, f); n++;
|
||||
lua_pushnumber(L, f);
|
||||
break;
|
||||
}
|
||||
case 'd': {
|
||||
double d;
|
||||
memcpy(&d, data+pos, size);
|
||||
correctbytes((char *)&d, sizeof(d), h.endian);
|
||||
lua_pushnumber(L, d); n++;
|
||||
lua_pushnumber(L, d);
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
if (size == 0) {
|
||||
if (n == 0 || !lua_isnumber(L, -1))
|
||||
luaL_error(L, "format 'c0' needs a previous size");
|
||||
if (!lua_isnumber(L, -1))
|
||||
luaL_error(L, "format `c0' needs a previous size");
|
||||
size = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1); n--;
|
||||
luaL_argcheck(L, size <= ld && pos <= ld - size,
|
||||
2, "data string too short");
|
||||
lua_pop(L, 1);
|
||||
luaL_argcheck(L, pos+size <= ld, 2, "data string too short");
|
||||
}
|
||||
lua_pushlstring(L, data+pos, size); n++;
|
||||
lua_pushlstring(L, data+pos, size);
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
@@ -349,15 +345,15 @@ static int b_unpack (lua_State *L) {
|
||||
if (e == NULL)
|
||||
luaL_error(L, "unfinished string in data");
|
||||
size = (e - (data+pos)) + 1;
|
||||
lua_pushlstring(L, data+pos, size - 1); n++;
|
||||
lua_pushlstring(L, data+pos, size - 1);
|
||||
break;
|
||||
}
|
||||
default: controloptions(L, opt, &fmt, &h);
|
||||
}
|
||||
pos += size;
|
||||
}
|
||||
lua_pushinteger(L, pos + 1); /* next position */
|
||||
return n + 1;
|
||||
lua_pushinteger(L, pos + 1);
|
||||
return lua_gettop(L) - 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -403,7 +399,7 @@ LUALIB_API int luaopen_struct (lua_State *L) {
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (C) 2010-2018 Lua.org, PUC-Rio. All rights reserved.
|
||||
* Copyright (C) 2010-2012 Lua.org, PUC-Rio. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
|
||||
+3
-27
@@ -606,7 +606,7 @@ slave-priority 100
|
||||
# deletion of the object. It means that the server stops processing new commands
|
||||
# in order to reclaim all the memory associated with an object in a synchronous
|
||||
# way. If the key deleted is associated with a small object, the time needed
|
||||
# in order to execute the DEL command is very small and comparable to most other
|
||||
# in order to execute th DEL command is very small and comparable to most other
|
||||
# O(1) or O(log_N) commands in Redis. However if the key is associated with an
|
||||
# aggregated value containing millions of elements, the server can block for
|
||||
# a long time (even seconds) in order to complete the operation.
|
||||
@@ -621,7 +621,7 @@ slave-priority 100
|
||||
# It's up to the design of the application to understand when it is a good
|
||||
# idea to use one or the other. However the Redis server sometimes has to
|
||||
# delete keys or flush the whole database as a side effect of other operations.
|
||||
# Specifically Redis deletes objects independently of a user call in the
|
||||
# Specifically Redis deletes objects independently of an user call in the
|
||||
# following scenarios:
|
||||
#
|
||||
# 1) On eviction, because of the maxmemory and maxmemory policy configurations,
|
||||
@@ -904,16 +904,6 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-require-full-coverage yes
|
||||
|
||||
# This option, when set to yes, prevents slaves from trying to failover its
|
||||
# master during master failures. However the master can still perform a
|
||||
# manual failover, if forced to do so.
|
||||
#
|
||||
# This is useful in different scenarios, especially in the case of multiple
|
||||
# data center operations, where we want one side to never be promoted if not
|
||||
# in the case of a total DC failure.
|
||||
#
|
||||
# cluster-slave-no-failover no
|
||||
|
||||
# In order to setup your cluster make sure to read the documentation
|
||||
# available at http://redis.io web site.
|
||||
|
||||
@@ -924,7 +914,7 @@ lua-time-limit 5000
|
||||
# Docker and other containers).
|
||||
#
|
||||
# In order to make Redis Cluster working in such environments, a static
|
||||
# configuration where each node knows its public address is needed. The
|
||||
# configuration where each node known its public address is needed. The
|
||||
# following two options are used for this scope, and are:
|
||||
#
|
||||
# * cluster-announce-ip
|
||||
@@ -1164,20 +1154,6 @@ client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
|
||||
# Client query buffers accumulate new commands. They are limited to a fixed
|
||||
# amount by default in order to avoid that a protocol desynchronization (for
|
||||
# instance due to a bug in the client) will lead to unbound memory usage in
|
||||
# the query buffer. However you can configure it here if you have very special
|
||||
# needs, such us huge multi/exec requests or alike.
|
||||
#
|
||||
# client-query-buffer-limit 1gb
|
||||
|
||||
# In the Redis protocol, bulk requests, that are, elements representing single
|
||||
# strings, are normally limited ot 512 mb. However you can change this limit
|
||||
# here.
|
||||
#
|
||||
# proto-max-bulk-len 512mb
|
||||
|
||||
# Redis calls an internal function to perform many background tasks, like
|
||||
# closing connections of clients in timeout, purging expired keys that are
|
||||
# never requested, and so forth.
|
||||
|
||||
@@ -194,12 +194,3 @@ sentinel failover-timeout mymaster 180000
|
||||
#
|
||||
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
|
||||
|
||||
# SECURITY
|
||||
#
|
||||
# By default SENTINEL SET will not be able to change the notification-script
|
||||
# and client-reconfig-script at runtime. This avoids a trivial security issue
|
||||
# where clients can set the script to anything and trigger a failover in order
|
||||
# to get the program executed.
|
||||
|
||||
sentinel deny-scripts-reconfig yes
|
||||
|
||||
|
||||
+1
-1
@@ -353,7 +353,7 @@ void listJoin(list *l, list *o) {
|
||||
else
|
||||
l->head = o->head;
|
||||
|
||||
if (o->tail) l->tail = o->tail;
|
||||
l->tail = o->tail;
|
||||
l->len += o->len;
|
||||
|
||||
/* Setup other as an empty list. */
|
||||
|
||||
@@ -159,10 +159,6 @@ void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
|
||||
aeFileEvent *fe = &eventLoop->events[fd];
|
||||
if (fe->mask == AE_NONE) return;
|
||||
|
||||
/* We want to always remove AE_BARRIER if set when AE_WRITABLE
|
||||
* is removed. */
|
||||
if (mask & AE_WRITABLE) mask |= AE_BARRIER;
|
||||
|
||||
aeApiDelEvent(eventLoop, fd, mask);
|
||||
fe->mask = fe->mask & (~mask);
|
||||
if (fd == eventLoop->maxfd && fe->mask == AE_NONE) {
|
||||
@@ -219,10 +215,7 @@ long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
|
||||
te->timeProc = proc;
|
||||
te->finalizerProc = finalizerProc;
|
||||
te->clientData = clientData;
|
||||
te->prev = NULL;
|
||||
te->next = eventLoop->timeEventHead;
|
||||
if (te->next)
|
||||
te->next->prev = te;
|
||||
eventLoop->timeEventHead = te;
|
||||
return id;
|
||||
}
|
||||
@@ -269,7 +262,7 @@ static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
|
||||
/* Process time events */
|
||||
static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
int processed = 0;
|
||||
aeTimeEvent *te;
|
||||
aeTimeEvent *te, *prev;
|
||||
long long maxId;
|
||||
time_t now = time(NULL);
|
||||
|
||||
@@ -290,6 +283,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
}
|
||||
eventLoop->lastTime = now;
|
||||
|
||||
prev = NULL;
|
||||
te = eventLoop->timeEventHead;
|
||||
maxId = eventLoop->timeEventNextId-1;
|
||||
while(te) {
|
||||
@@ -299,12 +293,10 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
/* Remove events scheduled for deletion. */
|
||||
if (te->id == AE_DELETED_EVENT_ID) {
|
||||
aeTimeEvent *next = te->next;
|
||||
if (te->prev)
|
||||
te->prev->next = te->next;
|
||||
else
|
||||
if (prev == NULL)
|
||||
eventLoop->timeEventHead = te->next;
|
||||
if (te->next)
|
||||
te->next->prev = te->prev;
|
||||
else
|
||||
prev->next = te->next;
|
||||
if (te->finalizerProc)
|
||||
te->finalizerProc(eventLoop, te->clientData);
|
||||
zfree(te);
|
||||
@@ -336,6 +328,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
te->id = AE_DELETED_EVENT_ID;
|
||||
}
|
||||
}
|
||||
prev = te;
|
||||
te = te->next;
|
||||
}
|
||||
return processed;
|
||||
@@ -418,49 +411,19 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
|
||||
int mask = eventLoop->fired[j].mask;
|
||||
int fd = eventLoop->fired[j].fd;
|
||||
int fired = 0; /* Number of events fired for current fd. */
|
||||
int rfired = 0;
|
||||
|
||||
/* Normally we execute the readable event first, and the writable
|
||||
* event laster. This is useful as sometimes we may be able
|
||||
* to serve the reply of a query immediately after processing the
|
||||
* query.
|
||||
*
|
||||
* However if AE_BARRIER is set in the mask, our application is
|
||||
* asking us to do the reverse: never fire the writable event
|
||||
* after the readable. In such a case, we invert the calls.
|
||||
* This is useful when, for instance, we want to do things
|
||||
* in the beforeSleep() hook, like fsynching a file to disk,
|
||||
* before replying to a client. */
|
||||
int invert = fe->mask & AE_BARRIER;
|
||||
|
||||
/* Note the "fe->mask & mask & ..." code: maybe an already
|
||||
* processed event removed an element that fired and we still
|
||||
* didn't processed, so we check if the event is still valid.
|
||||
*
|
||||
* Fire the readable event if the call sequence is not
|
||||
* inverted. */
|
||||
if (!invert && fe->mask & mask & AE_READABLE) {
|
||||
/* note the fe->mask & mask & ... code: maybe an already processed
|
||||
* event removed an element that fired and we still didn't
|
||||
* processed, so we check if the event is still valid. */
|
||||
if (fe->mask & mask & AE_READABLE) {
|
||||
rfired = 1;
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
}
|
||||
|
||||
/* Fire the writable event. */
|
||||
if (fe->mask & mask & AE_WRITABLE) {
|
||||
if (!fired || fe->wfileProc != fe->rfileProc) {
|
||||
if (!rfired || fe->wfileProc != fe->rfileProc)
|
||||
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
}
|
||||
}
|
||||
|
||||
processed++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,14 +38,9 @@
|
||||
#define AE_OK 0
|
||||
#define AE_ERR -1
|
||||
|
||||
#define AE_NONE 0 /* No events registered. */
|
||||
#define AE_READABLE 1 /* Fire when descriptor is readable. */
|
||||
#define AE_WRITABLE 2 /* Fire when descriptor is writable. */
|
||||
#define AE_BARRIER 4 /* With WRITABLE, never fire the event if the
|
||||
READABLE event already fired in the same event
|
||||
loop iteration. Useful when you want to persist
|
||||
things to disk before sending replies, and want
|
||||
to do that in a group fashion. */
|
||||
#define AE_NONE 0
|
||||
#define AE_READABLE 1
|
||||
#define AE_WRITABLE 2
|
||||
|
||||
#define AE_FILE_EVENTS 1
|
||||
#define AE_TIME_EVENTS 2
|
||||
@@ -69,7 +64,7 @@ typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop);
|
||||
|
||||
/* File event structure */
|
||||
typedef struct aeFileEvent {
|
||||
int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */
|
||||
int mask; /* one of AE_(READABLE|WRITABLE) */
|
||||
aeFileProc *rfileProc;
|
||||
aeFileProc *wfileProc;
|
||||
void *clientData;
|
||||
@@ -83,7 +78,6 @@ typedef struct aeTimeEvent {
|
||||
aeTimeProc *timeProc;
|
||||
aeEventFinalizerProc *finalizerProc;
|
||||
void *clientData;
|
||||
struct aeTimeEvent *prev;
|
||||
struct aeTimeEvent *next;
|
||||
} aeTimeEvent;
|
||||
|
||||
|
||||
+1
-1
@@ -484,7 +484,7 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
|
||||
|
||||
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) goto error;
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
|
||||
@@ -203,26 +203,6 @@ void aof_background_fsync(int fd) {
|
||||
bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
|
||||
}
|
||||
|
||||
/* Kills an AOFRW child process if exists */
|
||||
static void killAppendOnlyChild(void) {
|
||||
int statloc;
|
||||
/* No AOFRW child? return. */
|
||||
if (server.aof_child_pid == -1) return;
|
||||
/* Kill AOFRW child, wait for child exit. */
|
||||
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
||||
(long) server.aof_child_pid);
|
||||
if (kill(server.aof_child_pid,SIGUSR1) != -1) {
|
||||
while(wait3(&statloc,0,NULL) != server.aof_child_pid);
|
||||
}
|
||||
/* Reset the buffer accumulating changes while the child saves. */
|
||||
aofRewriteBufferReset();
|
||||
aofRemoveTempFile(server.aof_child_pid);
|
||||
server.aof_child_pid = -1;
|
||||
server.aof_rewrite_time_start = -1;
|
||||
/* Close pipes used for IPC between the two processes. */
|
||||
aofClosePipes();
|
||||
}
|
||||
|
||||
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
||||
* at runtime using the CONFIG command. */
|
||||
void stopAppendOnly(void) {
|
||||
@@ -234,18 +214,34 @@ void stopAppendOnly(void) {
|
||||
server.aof_fd = -1;
|
||||
server.aof_selected_db = -1;
|
||||
server.aof_state = AOF_OFF;
|
||||
killAppendOnlyChild();
|
||||
/* rewrite operation in progress? kill it, wait child exit */
|
||||
if (server.aof_child_pid != -1) {
|
||||
int statloc;
|
||||
|
||||
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
||||
(long) server.aof_child_pid);
|
||||
if (kill(server.aof_child_pid,SIGUSR1) != -1) {
|
||||
while(wait3(&statloc,0,NULL) != server.aof_child_pid);
|
||||
}
|
||||
/* reset the buffer accumulating changes while the child saves */
|
||||
aofRewriteBufferReset();
|
||||
aofRemoveTempFile(server.aof_child_pid);
|
||||
server.aof_child_pid = -1;
|
||||
server.aof_rewrite_time_start = -1;
|
||||
/* close pipes used for IPC between the two processes. */
|
||||
aofClosePipes();
|
||||
}
|
||||
}
|
||||
|
||||
/* Called when the user switches from "appendonly no" to "appendonly yes"
|
||||
* at runtime using the CONFIG command. */
|
||||
int startAppendOnly(void) {
|
||||
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
|
||||
int newfd;
|
||||
|
||||
newfd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);
|
||||
serverAssert(server.aof_state == AOF_OFF);
|
||||
if (newfd == -1) {
|
||||
if (server.aof_fd == -1) {
|
||||
char *cwdp = getcwd(cwd,MAXPATHLEN);
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
@@ -259,56 +255,17 @@ int startAppendOnly(void) {
|
||||
if (server.rdb_child_pid != -1) {
|
||||
server.aof_rewrite_scheduled = 1;
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
|
||||
} else {
|
||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||
* start a new one: the old one cannot be reused becuase it is not
|
||||
* accumulating the AOF buffer. */
|
||||
if (server.aof_child_pid != -1) {
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
|
||||
killAppendOnlyChild();
|
||||
}
|
||||
if (rewriteAppendOnlyFileBackground() == C_ERR) {
|
||||
close(newfd);
|
||||
serverLog(LL_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.");
|
||||
return C_ERR;
|
||||
}
|
||||
} else if (rewriteAppendOnlyFileBackground() == C_ERR) {
|
||||
close(server.aof_fd);
|
||||
serverLog(LL_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.");
|
||||
return C_ERR;
|
||||
}
|
||||
/* We correctly switched on AOF, now wait for the rewrite to be complete
|
||||
* in order to append data on disk. */
|
||||
server.aof_state = AOF_WAIT_REWRITE;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
server.aof_fd = newfd;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* This is a wrapper to the write syscall in order to retry on short writes
|
||||
* or if the syscall gets interrupted. It could look strange that we retry
|
||||
* on short writes given that we are writing to a block device: normally if
|
||||
* the first call is short, there is a end-of-space condition, so the next
|
||||
* is likely to fail. However apparently in modern systems this is no longer
|
||||
* true, and in general it looks just more resilient to retry the write. If
|
||||
* there is an actual error condition we'll get it at the next try. */
|
||||
ssize_t aofWrite(int fd, const char *buf, size_t len) {
|
||||
ssize_t nwritten = 0, totwritten = 0;
|
||||
|
||||
while(len) {
|
||||
nwritten = write(fd, buf, len);
|
||||
|
||||
if (nwritten < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return totwritten ? totwritten : -1;
|
||||
}
|
||||
|
||||
len -= nwritten;
|
||||
buf += nwritten;
|
||||
totwritten += nwritten;
|
||||
}
|
||||
|
||||
return totwritten;
|
||||
}
|
||||
|
||||
/* Write the append only file buffer on disk.
|
||||
*
|
||||
* Since we are required to write the AOF before replying to the client,
|
||||
@@ -366,7 +323,7 @@ void flushAppendOnlyFile(int force) {
|
||||
* or alike */
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
||||
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
||||
latencyEndMonitor(latency);
|
||||
/* We want to capture different events for delayed writes:
|
||||
* when the delay happens with a pending fsync, or with a saving child
|
||||
@@ -385,7 +342,7 @@ void flushAppendOnlyFile(int force) {
|
||||
/* We performed the write so reset the postponed flush sentinel to zero. */
|
||||
server.aof_flush_postponed_start = 0;
|
||||
|
||||
if (nwritten != (ssize_t)sdslen(server.aof_buf)) {
|
||||
if (nwritten != (signed)sdslen(server.aof_buf)) {
|
||||
static time_t last_write_error_log = 0;
|
||||
int can_log = 0;
|
||||
|
||||
@@ -677,7 +634,6 @@ int loadAppendOnlyFile(char *filename) {
|
||||
int old_aof_state = server.aof_state;
|
||||
long loops = 0;
|
||||
off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */
|
||||
off_t valid_before_multi = 0; /* Offset before MULTI command loaded. */
|
||||
|
||||
if (fp == NULL) {
|
||||
serverLog(LL_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
|
||||
@@ -714,7 +670,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
|
||||
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
|
||||
rioInitWithFile(&rdb,fp);
|
||||
if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
|
||||
if (rdbLoadRio(&rdb,NULL) != C_OK) {
|
||||
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
|
||||
goto readerr;
|
||||
} else {
|
||||
@@ -778,28 +734,16 @@ int loadAppendOnlyFile(char *filename) {
|
||||
/* Command lookup */
|
||||
cmd = lookupCommand(argv[0]->ptr);
|
||||
if (!cmd) {
|
||||
serverLog(LL_WARNING,
|
||||
"Unknown command '%s' reading the append only file",
|
||||
(char*)argv[0]->ptr);
|
||||
serverLog(LL_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
|
||||
|
||||
/* Run the command in the context of a fake client */
|
||||
fakeClient->cmd = cmd;
|
||||
if (fakeClient->flags & CLIENT_MULTI &&
|
||||
fakeClient->cmd->proc != execCommand)
|
||||
{
|
||||
queueMultiCommand(fakeClient);
|
||||
} else {
|
||||
cmd->proc(fakeClient);
|
||||
}
|
||||
cmd->proc(fakeClient);
|
||||
|
||||
/* The fake client should not have a reply */
|
||||
serverAssert(fakeClient->bufpos == 0 &&
|
||||
listLength(fakeClient->reply) == 0);
|
||||
|
||||
serverAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
|
||||
/* The fake client should never get blocked */
|
||||
serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0);
|
||||
|
||||
@@ -811,15 +755,8 @@ int loadAppendOnlyFile(char *filename) {
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
* If the client is in the middle of a MULTI/EXEC, handle it as it was
|
||||
* a short read, even if technically the protocol is correct: we want
|
||||
* to remove the unprocessed tail and continue. */
|
||||
if (fakeClient->flags & CLIENT_MULTI) {
|
||||
serverLog(LL_WARNING,
|
||||
"Revert incomplete MULTI/EXEC transaction in AOF file");
|
||||
valid_up_to = valid_before_multi;
|
||||
goto uxeof;
|
||||
}
|
||||
* If the client is in the middle of a MULTI/EXEC, log error and quit. */
|
||||
if (fakeClient->flags & CLIENT_MULTI) goto uxeof;
|
||||
|
||||
loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
fclose(fp);
|
||||
@@ -1348,7 +1285,7 @@ int aofCreatePipes(void) {
|
||||
|
||||
if (pipe(fds) == -1) goto error; /* parent -> children data. */
|
||||
if (pipe(fds+2) == -1) goto error; /* children -> parent ack. */
|
||||
if (pipe(fds+4) == -1) goto error; /* parent -> children ack. */
|
||||
if (pipe(fds+4) == -1) goto error; /* children -> parent ack. */
|
||||
/* Parent -> children data is non blocking. */
|
||||
if (anetNonBlock(NULL,fds[0]) != ANET_OK) goto error;
|
||||
if (anetNonBlock(NULL,fds[1]) != ANET_OK) goto error;
|
||||
@@ -1562,10 +1499,10 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
if (server.aof_fd == -1) {
|
||||
/* AOF disabled */
|
||||
|
||||
/* Don't care if this fails: oldfd will be -1 and we handle that.
|
||||
* One notable case of -1 return is if the old file does
|
||||
* not exist. */
|
||||
oldfd = open(server.aof_filename,O_RDONLY|O_NONBLOCK);
|
||||
/* Don't care if this fails: oldfd will be -1 and we handle that.
|
||||
* One notable case of -1 return is if the old file does
|
||||
* not exist. */
|
||||
oldfd = open(server.aof_filename,O_RDONLY|O_NONBLOCK);
|
||||
} else {
|
||||
/* AOF enabled */
|
||||
oldfd = -1; /* We'll set this to the current AOF filedes later. */
|
||||
|
||||
+17
-87
@@ -201,8 +201,6 @@ int clusterLoadConfig(char *filename) {
|
||||
n->flags |= CLUSTER_NODE_HANDSHAKE;
|
||||
} else if (!strcasecmp(s,"noaddr")) {
|
||||
n->flags |= CLUSTER_NODE_NOADDR;
|
||||
} else if (!strcasecmp(s,"nofailover")) {
|
||||
n->flags |= CLUSTER_NODE_NOFAILOVER;
|
||||
} else if (!strcasecmp(s,"noflags")) {
|
||||
/* nothing to do */
|
||||
} else {
|
||||
@@ -409,22 +407,6 @@ int clusterLockConfig(char *filename) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Some flags (currently just the NOFAILOVER flag) may need to be updated
|
||||
* in the "myself" node based on the current configuration of the node,
|
||||
* that may change at runtime via CONFIG SET. This function changes the
|
||||
* set of flags in myself->flags accordingly. */
|
||||
void clusterUpdateMyselfFlags(void) {
|
||||
int oldflags = myself->flags;
|
||||
int nofailover = server.cluster_slave_no_failover ?
|
||||
CLUSTER_NODE_NOFAILOVER : 0;
|
||||
myself->flags &= ~CLUSTER_NODE_NOFAILOVER;
|
||||
myself->flags |= nofailover;
|
||||
if (myself->flags != oldflags) {
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
|
||||
CLUSTER_TODO_UPDATE_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
void clusterInit(void) {
|
||||
int saveconf = 0;
|
||||
|
||||
@@ -515,7 +497,6 @@ void clusterInit(void) {
|
||||
|
||||
server.cluster->mf_end = 0;
|
||||
resetManualFailover();
|
||||
clusterUpdateMyselfFlags();
|
||||
}
|
||||
|
||||
/* Reset a node performing a soft or hard reset:
|
||||
@@ -1827,18 +1808,6 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy the CLUSTER_NODE_NOFAILOVER flag from what the sender
|
||||
* announced. This is a dynamic flag that we receive from the
|
||||
* sender, and the latest status must be trusted. We need it to
|
||||
* be propagated because the slave ranking used to understand the
|
||||
* delay of each slave in the voting process, needs to know
|
||||
* what are the instances really competing. */
|
||||
if (sender) {
|
||||
int nofailover = flags & CLUSTER_NODE_NOFAILOVER;
|
||||
sender->flags &= ~CLUSTER_NODE_NOFAILOVER;
|
||||
sender->flags |= nofailover;
|
||||
}
|
||||
|
||||
/* Update the node address if it changed. */
|
||||
if (sender && type == CLUSTERMSG_TYPE_PING &&
|
||||
!nodeInHandshake(sender) &&
|
||||
@@ -2187,7 +2156,7 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* from event handlers that will do stuff with the same link later. */
|
||||
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
|
||||
if (sdslen(link->sndbuf) == 0 && msglen != 0)
|
||||
aeCreateFileEvent(server.el,link->fd,AE_WRITABLE|AE_BARRIER,
|
||||
aeCreateFileEvent(server.el,link->fd,AE_WRITABLE,
|
||||
clusterWriteHandler,link);
|
||||
|
||||
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
|
||||
@@ -2722,10 +2691,9 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
|
||||
}
|
||||
|
||||
/* We can vote for this slave. */
|
||||
clusterSendFailoverAuth(node);
|
||||
server.cluster->lastVoteEpoch = server.cluster->currentEpoch;
|
||||
node->slaveof->voted_time = mstime();
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
|
||||
clusterSendFailoverAuth(node);
|
||||
serverLog(LL_WARNING, "Failover auth granted to %.40s for epoch %llu",
|
||||
node->name, (unsigned long long) server.cluster->currentEpoch);
|
||||
}
|
||||
@@ -2754,7 +2722,6 @@ int clusterGetSlaveRank(void) {
|
||||
myoffset = replicationGetSlaveOffset();
|
||||
for (j = 0; j < master->numslaves; j++)
|
||||
if (master->slaves[j] != myself &&
|
||||
!nodeCantFailover(master->slaves[j]) &&
|
||||
master->slaves[j]->repl_offset > myoffset) rank++;
|
||||
return rank;
|
||||
}
|
||||
@@ -2892,13 +2859,10 @@ void clusterHandleSlaveFailover(void) {
|
||||
* of an automatic or manual failover:
|
||||
* 1) We are a slave.
|
||||
* 2) Our master is flagged as FAIL, or this is a manual failover.
|
||||
* 3) We don't have the no failover configuration set, and this is
|
||||
* not a manual failover.
|
||||
* 4) It is serving slots. */
|
||||
* 3) It is serving slots. */
|
||||
if (nodeIsMaster(myself) ||
|
||||
myself->slaveof == NULL ||
|
||||
(!nodeFailed(myself->slaveof) && !manual_failover) ||
|
||||
(server.cluster_slave_no_failover && !manual_failover) ||
|
||||
myself->slaveof->numslots == 0)
|
||||
{
|
||||
/* There are no reasons to failover, so we set the reason why we
|
||||
@@ -3274,9 +3238,6 @@ void clusterCron(void) {
|
||||
handshake_timeout = server.cluster_node_timeout;
|
||||
if (handshake_timeout < 1000) handshake_timeout = 1000;
|
||||
|
||||
/* Update myself flags. */
|
||||
clusterUpdateMyselfFlags();
|
||||
|
||||
/* Check if we have disconnected nodes and re-establish the connection.
|
||||
* Also update a few stats while we are here, that can be used to make
|
||||
* better decisions in other part of the code. */
|
||||
@@ -3875,8 +3836,7 @@ static struct redisNodeFlags redisNodeFlagsTable[] = {
|
||||
{CLUSTER_NODE_PFAIL, "fail?,"},
|
||||
{CLUSTER_NODE_FAIL, "fail,"},
|
||||
{CLUSTER_NODE_HANDSHAKE, "handshake,"},
|
||||
{CLUSTER_NODE_NOADDR, "noaddr,"},
|
||||
{CLUSTER_NODE_NOFAILOVER, "nofailover,"}
|
||||
{CLUSTER_NODE_NOADDR, "noaddr,"}
|
||||
};
|
||||
|
||||
/* Concatenate the comma separated list of node flags to the given SDS
|
||||
@@ -4907,16 +4867,14 @@ void migrateCloseTimedoutSockets(void) {
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password]
|
||||
/* MIGRATE host port key dbid timeout [COPY | REPLACE]
|
||||
*
|
||||
* On in the multiple keys form:
|
||||
*
|
||||
* MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password] KEYS key1
|
||||
* key2 ... keyN */
|
||||
* MIGRATE host port "" dbid timeout [COPY | REPLACE] KEYS key1 key2 ... keyN */
|
||||
void migrateCommand(client *c) {
|
||||
migrateCachedSocket *cs;
|
||||
int copy = 0, replace = 0, j;
|
||||
char *password = NULL;
|
||||
int copy, replace, j;
|
||||
long timeout;
|
||||
long dbid;
|
||||
robj **ov = NULL; /* Objects to migrate. */
|
||||
@@ -4931,20 +4889,16 @@ void migrateCommand(client *c) {
|
||||
int first_key = 3; /* Argument index of the first key. */
|
||||
int num_keys = 1; /* By default only migrate the 'key' argument. */
|
||||
|
||||
/* Initialization */
|
||||
copy = 0;
|
||||
replace = 0;
|
||||
|
||||
/* Parse additional options */
|
||||
for (j = 6; j < c->argc; j++) {
|
||||
int moreargs = j < c->argc-1;
|
||||
if (!strcasecmp(c->argv[j]->ptr,"copy")) {
|
||||
copy = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"replace")) {
|
||||
replace = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"auth")) {
|
||||
if (!moreargs) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
j++;
|
||||
password = c->argv[j]->ptr;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"keys")) {
|
||||
if (sdslen(c->argv[3]->ptr) != 0) {
|
||||
addReplyError(c,
|
||||
@@ -5003,14 +4957,6 @@ try_again:
|
||||
|
||||
rioInitWithBuffer(&cmd,sdsempty());
|
||||
|
||||
/* Authentication */
|
||||
if (password) {
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2));
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"AUTH",4));
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,password,
|
||||
sdslen(password)));
|
||||
}
|
||||
|
||||
/* Send the SELECT command if the current DB is not already selected. */
|
||||
int select = cs->last_dbid != dbid; /* Should we emit SELECT? */
|
||||
if (select) {
|
||||
@@ -5028,9 +4974,7 @@ try_again:
|
||||
ttl = expireat-mstime();
|
||||
if (ttl < 1) ttl = 1;
|
||||
}
|
||||
serverAssertWithInfo(c,NULL,
|
||||
rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
|
||||
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
|
||||
if (server.cluster_enabled)
|
||||
serverAssertWithInfo(c,NULL,
|
||||
rioWriteBulkString(&cmd,"RESTORE-ASKING",14));
|
||||
@@ -5073,14 +5017,9 @@ try_again:
|
||||
}
|
||||
}
|
||||
|
||||
char buf0[1024]; /* Auth reply. */
|
||||
char buf1[1024]; /* Select reply. */
|
||||
char buf2[1024]; /* Restore reply. */
|
||||
|
||||
/* Read the AUTH reply if needed. */
|
||||
if (password && syncReadLine(cs->fd, buf0, sizeof(buf0), timeout) <= 0)
|
||||
goto socket_err;
|
||||
|
||||
/* Read the SELECT reply if needed. */
|
||||
if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
|
||||
goto socket_err;
|
||||
@@ -5097,21 +5036,13 @@ try_again:
|
||||
socket_error = 1;
|
||||
break;
|
||||
}
|
||||
if ((password && buf0[0] == '-') ||
|
||||
(select && buf1[0] == '-') ||
|
||||
buf2[0] == '-')
|
||||
{
|
||||
if ((select && buf1[0] == '-') || buf2[0] == '-') {
|
||||
/* On error assume that last_dbid is no longer valid. */
|
||||
if (!error_from_target) {
|
||||
cs->last_dbid = -1;
|
||||
char *errbuf;
|
||||
if (password && buf0[0] == '-') errbuf = buf0;
|
||||
else if (select && buf1[0] == '-') errbuf = buf1;
|
||||
else errbuf = buf2;
|
||||
|
||||
error_from_target = 1;
|
||||
addReplyErrorFormat(c,"Target instance replied with error: %s",
|
||||
errbuf+1);
|
||||
(select && buf1[0] == '-') ? buf1+1 : buf2+1);
|
||||
error_from_target = 1;
|
||||
}
|
||||
} else {
|
||||
if (!copy) {
|
||||
@@ -5176,7 +5107,7 @@ try_again:
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
/* On error we already sent it in the for loop above, and set
|
||||
* the currently selected socket to -1 to force SELECT the next time. */
|
||||
* the curretly selected socket to -1 to force SELECT the next time. */
|
||||
}
|
||||
|
||||
sdsfree(cmd.io.buffer.ptr);
|
||||
@@ -5432,8 +5363,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
* node is a slave and the request is about an hash slot our master
|
||||
* is serving, we can reply without redirection. */
|
||||
if (c->flags & CLIENT_READONLY &&
|
||||
(cmd->flags & CMD_READONLY || cmd->proc == evalCommand ||
|
||||
cmd->proc == evalShaCommand) &&
|
||||
cmd->flags & CMD_READONLY &&
|
||||
nodeIsSlave(myself) &&
|
||||
myself->slaveof == n)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#define CLUSTER_DEFAULT_NODE_TIMEOUT 15000
|
||||
#define CLUSTER_DEFAULT_SLAVE_VALIDITY 10 /* Slave max data age factor. */
|
||||
#define CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE 1
|
||||
#define CLUSTER_DEFAULT_SLAVE_NO_FAILOVER 0 /* Failover by default. */
|
||||
#define CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
|
||||
#define CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
|
||||
#define CLUSTER_FAIL_UNDO_TIME_ADD 10 /* Some additional time. */
|
||||
@@ -56,7 +55,6 @@ typedef struct clusterLink {
|
||||
#define CLUSTER_NODE_NOADDR 64 /* We don't know the address of this node */
|
||||
#define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */
|
||||
#define CLUSTER_NODE_MIGRATE_TO 256 /* Master elegible for replica migration. */
|
||||
#define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failver. */
|
||||
#define CLUSTER_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
|
||||
#define nodeIsMaster(n) ((n)->flags & CLUSTER_NODE_MASTER)
|
||||
@@ -66,7 +64,6 @@ typedef struct clusterLink {
|
||||
#define nodeWithoutAddr(n) ((n)->flags & CLUSTER_NODE_NOADDR)
|
||||
#define nodeTimedOut(n) ((n)->flags & CLUSTER_NODE_PFAIL)
|
||||
#define nodeFailed(n) ((n)->flags & CLUSTER_NODE_FAIL)
|
||||
#define nodeCantFailover(n) ((n)->flags & CLUSTER_NODE_NOFAILOVER)
|
||||
|
||||
/* Reasons why a slave is not able to failover. */
|
||||
#define CLUSTER_CANT_FAILOVER_NONE 0
|
||||
|
||||
+2
-31
@@ -328,19 +328,15 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "maxmemory-samples must be 1 or greater";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if ((!strcasecmp(argv[0],"proto-max-bulk-len")) && argc == 2) {
|
||||
server.proto_max_bulk_len = memtoll(argv[1],NULL);
|
||||
} else if ((!strcasecmp(argv[0],"client-query-buffer-limit")) && argc == 2) {
|
||||
server.client_max_querybuf_len = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"lfu-log-factor") && argc == 2) {
|
||||
server.lfu_log_factor = atoi(argv[1]);
|
||||
if (server.lfu_log_factor < 0) {
|
||||
if (server.maxmemory_samples < 0) {
|
||||
err = "lfu-log-factor must be 0 or greater";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lfu-decay-time") && argc == 2) {
|
||||
server.lfu_decay_time = atoi(argv[1]);
|
||||
if (server.lfu_decay_time < 0) {
|
||||
if (server.maxmemory_samples < 1) {
|
||||
err = "lfu-decay-time must be 0 or greater";
|
||||
goto loaderr;
|
||||
}
|
||||
@@ -635,14 +631,6 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "cluster slave validity factor must be zero or positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-slave-no-failover") &&
|
||||
argc == 2)
|
||||
{
|
||||
server.cluster_slave_no_failover = yesnotoi(argv[1]);
|
||||
if (server.cluster_slave_no_failover == -1) {
|
||||
err = "argument must be 'yes' or 'no'";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
|
||||
server.lua_time_limit = strtoll(argv[1],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
|
||||
@@ -1005,8 +993,6 @@ void configSetCommand(client *c) {
|
||||
"repl-diskless-sync",server.repl_diskless_sync) {
|
||||
} config_set_bool_field(
|
||||
"cluster-require-full-coverage",server.cluster_require_full_coverage) {
|
||||
} config_set_bool_field(
|
||||
"cluster-slave-no-failover",server.cluster_slave_no_failover) {
|
||||
} config_set_bool_field(
|
||||
"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) {
|
||||
} config_set_bool_field(
|
||||
@@ -1146,10 +1132,6 @@ void configSetCommand(client *c) {
|
||||
}
|
||||
freeMemoryIfNeeded();
|
||||
}
|
||||
} config_set_memory_field(
|
||||
"proto-max-bulk-len",server.proto_max_bulk_len) {
|
||||
} config_set_memory_field(
|
||||
"client-query-buffer-limit",server.client_max_querybuf_len) {
|
||||
} config_set_memory_field("repl-backlog-size",ll) {
|
||||
resizeReplicationBacklog(ll);
|
||||
} config_set_memory_field("auto-aof-rewrite-min-size",ll) {
|
||||
@@ -1238,11 +1220,7 @@ void configGetCommand(client *c) {
|
||||
|
||||
/* Numerical values */
|
||||
config_get_numerical_field("maxmemory",server.maxmemory);
|
||||
config_get_numerical_field("proto-max-bulk-len",server.proto_max_bulk_len);
|
||||
config_get_numerical_field("client-query-buffer-limit",server.client_max_querybuf_len);
|
||||
config_get_numerical_field("maxmemory-samples",server.maxmemory_samples);
|
||||
config_get_numerical_field("lfu-log-factor",server.lfu_log_factor);
|
||||
config_get_numerical_field("lfu-decay-time",server.lfu_decay_time);
|
||||
config_get_numerical_field("timeout",server.maxidletime);
|
||||
config_get_numerical_field("active-defrag-threshold-lower",server.active_defrag_threshold_lower);
|
||||
config_get_numerical_field("active-defrag-threshold-upper",server.active_defrag_threshold_upper);
|
||||
@@ -1301,8 +1279,6 @@ void configGetCommand(client *c) {
|
||||
/* Bool (yes/no) values */
|
||||
config_get_bool_field("cluster-require-full-coverage",
|
||||
server.cluster_require_full_coverage);
|
||||
config_get_bool_field("cluster-slave-no-failover",
|
||||
server.cluster_slave_no_failover);
|
||||
config_get_bool_field("no-appendfsync-on-rewrite",
|
||||
server.aof_no_fsync_on_rewrite);
|
||||
config_get_bool_field("slave-serve-stale-data",
|
||||
@@ -2014,12 +1990,8 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
|
||||
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,CONFIG_DEFAULT_MAX_CLIENTS);
|
||||
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,CONFIG_DEFAULT_MAXMEMORY);
|
||||
rewriteConfigBytesOption(state,"proto-max-bulk-len",server.proto_max_bulk_len,CONFIG_DEFAULT_PROTO_MAX_BULK_LEN);
|
||||
rewriteConfigBytesOption(state,"client-query-buffer-limit",server.client_max_querybuf_len,PROTO_MAX_QUERYBUF_LEN);
|
||||
rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum,CONFIG_DEFAULT_MAXMEMORY_POLICY);
|
||||
rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,CONFIG_DEFAULT_MAXMEMORY_SAMPLES);
|
||||
rewriteConfigNumericalOption(state,"lfu-log-factor",server.lfu_log_factor,CONFIG_DEFAULT_LFU_LOG_FACTOR);
|
||||
rewriteConfigNumericalOption(state,"lfu-decay-time",server.lfu_decay_time,CONFIG_DEFAULT_LFU_DECAY_TIME);
|
||||
rewriteConfigNumericalOption(state,"active-defrag-threshold-lower",server.active_defrag_threshold_lower,CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER);
|
||||
rewriteConfigNumericalOption(state,"active-defrag-threshold-upper",server.active_defrag_threshold_upper,CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER);
|
||||
rewriteConfigBytesOption(state,"active-defrag-ignore-bytes",server.active_defrag_ignore_bytes,CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES);
|
||||
@@ -2035,7 +2007,6 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
|
||||
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
rewriteConfigYesNoOption(state,"cluster-require-full-coverage",server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE);
|
||||
rewriteConfigYesNoOption(state,"cluster-slave-no-failover",server.cluster_slave_no_failover,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER);
|
||||
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT);
|
||||
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER);
|
||||
rewriteConfigNumericalOption(state,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
|
||||
|
||||
@@ -38,15 +38,6 @@
|
||||
* C-level DB API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Update LFU when an object is accessed.
|
||||
* Firstly, decrement the counter if the decrement time is reached.
|
||||
* Then logarithmically increment the counter, and update the access time. */
|
||||
void updateLFU(robj *val) {
|
||||
unsigned long counter = LFUDecrAndReturn(val);
|
||||
counter = LFULogIncr(counter);
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | counter;
|
||||
}
|
||||
|
||||
/* Low level key lookup API, not actually called directly from commands
|
||||
* implementations that should instead rely on lookupKeyRead(),
|
||||
* lookupKeyWrite() and lookupKeyReadWithFlags(). */
|
||||
@@ -63,7 +54,9 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
!(flags & LOOKUP_NOTOUCH))
|
||||
{
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
updateLFU(val);
|
||||
unsigned long ldt = val->lru >> 8;
|
||||
unsigned long counter = LFULogIncr(val->lru & 255);
|
||||
val->lru = (ldt << 8) | counter;
|
||||
} else {
|
||||
val->lru = LRU_CLOCK();
|
||||
}
|
||||
@@ -187,9 +180,6 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
int saved_lru = old->lru;
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
val->lru = saved_lru;
|
||||
/* LFU should be not only copied but also updated
|
||||
* when a key is overwritten. */
|
||||
updateLFU(val);
|
||||
} else {
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
}
|
||||
@@ -224,8 +214,6 @@ int dbExists(redisDb *db, robj *key) {
|
||||
* The function makes sure to return keys not already expired. */
|
||||
robj *dbRandomKey(redisDb *db) {
|
||||
dictEntry *de;
|
||||
int maxtries = 100;
|
||||
int allvolatile = dictSize(db->dict) == dictSize(db->expires);
|
||||
|
||||
while(1) {
|
||||
sds key;
|
||||
@@ -237,17 +225,6 @@ robj *dbRandomKey(redisDb *db) {
|
||||
key = dictGetKey(de);
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
if (dictFind(db->expires,key)) {
|
||||
if (allvolatile && server.masterhost && --maxtries == 0) {
|
||||
/* If the DB is composed only of keys with an expire set,
|
||||
* it could happen that all the keys are already logically
|
||||
* expired in the slave, so the function cannot stop because
|
||||
* expireIfNeeded() is false, nor it can stop because
|
||||
* dictGetRandomKey() returns NULL (there are keys to return).
|
||||
* To prevent the infinite loop we do some tries, but if there
|
||||
* are the conditions for an infinite loop, eventually we
|
||||
* return a key name that may be already expired. */
|
||||
return keyobj;
|
||||
}
|
||||
if (expireIfNeeded(db,keyobj)) {
|
||||
decrRefCount(keyobj);
|
||||
continue; /* search for another key. This expired. */
|
||||
@@ -481,7 +458,8 @@ void existsCommand(client *c) {
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
if (lookupKeyRead(c->db,c->argv[j])) count++;
|
||||
expireIfNeeded(c->db,c->argv[j]);
|
||||
if (dbExists(c->db,c->argv[j])) count++;
|
||||
}
|
||||
addReplyLongLong(c,count);
|
||||
}
|
||||
@@ -1106,25 +1084,6 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
decrRefCount(argv[1]);
|
||||
}
|
||||
|
||||
/* This function is called when we are going to perform some operation
|
||||
* in a given key, but such key may be already logically expired even if
|
||||
* it still exists in the database. The main way this function is called
|
||||
* is via lookupKey*() family of functions.
|
||||
*
|
||||
* The behavior of the function depends on the replication role of the
|
||||
* instance, because slave instances do not expire keys, they wait
|
||||
* for DELs from the master for consistency matters. However even
|
||||
* slaves will try to have a coherent return value for the function,
|
||||
* so that read commands executed in the slave side will be able to
|
||||
* behave like if the key is expired even if still present (because the
|
||||
* master has yet to propagate the DEL).
|
||||
*
|
||||
* In masters as a side effect of finding a key which is expired, such
|
||||
* key will be evicted from the database. Also this may trigger the
|
||||
* propagation of a DEL/UNLINK command in AOF / replication stream.
|
||||
*
|
||||
* The return value of the function is 0 if the key is still valid,
|
||||
* otherwise the function returns 1 if the key is expired. */
|
||||
int expireIfNeeded(redisDb *db, robj *key) {
|
||||
mstime_t when = getExpire(db,key);
|
||||
mstime_t now;
|
||||
@@ -1134,7 +1093,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
/* Don't expire anything while loading. It will be done later. */
|
||||
if (server.loading) return 0;
|
||||
|
||||
/* If we are in the context of a Lua script, we pretend that time is
|
||||
/* If we are in the context of a Lua script, we claim that time is
|
||||
* blocked to when the Lua script started. This way a key can expire
|
||||
* only the first time it is accessed and not in the middle of the
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
@@ -1182,13 +1141,11 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
|
||||
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
|
||||
if (j >= argc) {
|
||||
/* Modules commands, and standard commands with a not fixed number
|
||||
* of arugments (negative arity parameter) do not have dispatch
|
||||
* time arity checks, so we need to handle the case where the user
|
||||
* passed an invalid number of arguments here. In this case we
|
||||
* return no keys and expect the command implementation to report
|
||||
* an arity or syntax error. */
|
||||
if (cmd->flags & CMD_MODULE || cmd->arity < 0) {
|
||||
/* Modules command do not have dispatch time arity checks, so
|
||||
* we need to handle the case where the user passed an invalid
|
||||
* number of arguments here. In this case we return no keys
|
||||
* and expect the module command to report an arity error. */
|
||||
if (cmd->flags & CMD_MODULE) {
|
||||
zfree(keys);
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
|
||||
+3
-10
@@ -308,8 +308,6 @@ void debugCommand(client *c) {
|
||||
"structsize -- Return the size of different Redis core C structures.");
|
||||
blen++; addReplyStatus(c,
|
||||
"htstats <dbid> -- Return hash table statistics of the specified Redis database.");
|
||||
blen++; addReplyStatus(c,
|
||||
"change-repl-id -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.");
|
||||
setDeferredMultiBulkLength(c,blenp,blen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
||||
*((char*)-1) = 'x';
|
||||
@@ -372,13 +370,13 @@ void debugCommand(client *c) {
|
||||
val = dictGetVal(de);
|
||||
strenc = strEncoding(val->encoding);
|
||||
|
||||
char extra[138] = {0};
|
||||
char extra[128] = {0};
|
||||
if (val->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
char *nextra = extra;
|
||||
int remaining = sizeof(extra);
|
||||
quicklist *ql = val->ptr;
|
||||
/* Add number of quicklist nodes */
|
||||
int used = snprintf(nextra, remaining, " ql_nodes:%lu", ql->len);
|
||||
int used = snprintf(nextra, remaining, " ql_nodes:%u", ql->len);
|
||||
nextra += used;
|
||||
remaining -= used;
|
||||
/* Add average quicklist fill factor */
|
||||
@@ -551,11 +549,6 @@ void debugCommand(client *c) {
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
addReplyBulkSds(c,stats);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
|
||||
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
|
||||
(char*)c->argv[1]->ptr);
|
||||
@@ -1030,7 +1023,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
"Redis %s crashed by signal: %d", REDIS_VERSION, sig);
|
||||
if (eip != NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Crashed running the instruction at: %p", eip);
|
||||
"Crashed running the instuction at: %p", eip);
|
||||
}
|
||||
if (sig == SIGSEGV || sig == SIGBUS) {
|
||||
serverLog(LL_WARNING,
|
||||
|
||||
+1
-1
@@ -289,7 +289,7 @@ int defragKey(redisDb *db, dictEntry *de) {
|
||||
/* Dirty code:
|
||||
* I can't search in db->expires for that key after i already released
|
||||
* the pointer it holds it won't be able to do the string compare */
|
||||
uint64_t hash = dictGetHash(db->dict, de->key);
|
||||
unsigned int hash = dictGetHash(db->dict, de->key);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged);
|
||||
}
|
||||
|
||||
|
||||
+22
-25
@@ -66,7 +66,7 @@ static unsigned int dict_force_resize_ratio = 5;
|
||||
|
||||
static int _dictExpandIfNeeded(dict *ht);
|
||||
static unsigned long _dictNextPower(unsigned long size);
|
||||
static long _dictKeyIndex(dict *ht, const void *key, uint64_t hash, dictEntry **existing);
|
||||
static int _dictKeyIndex(dict *ht, const void *key, unsigned int hash, dictEntry **existing);
|
||||
static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
|
||||
|
||||
/* -------------------------- hash functions -------------------------------- */
|
||||
@@ -202,7 +202,7 @@ int dictRehash(dict *d, int n) {
|
||||
de = d->ht[0].table[d->rehashidx];
|
||||
/* Move all the keys in this bucket from the old to the new hash HT */
|
||||
while(de) {
|
||||
uint64_t h;
|
||||
unsigned int h;
|
||||
|
||||
nextde = de->next;
|
||||
/* Get the index in the new hash table */
|
||||
@@ -291,7 +291,7 @@ int dictAdd(dict *d, void *key, void *val)
|
||||
*/
|
||||
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
|
||||
{
|
||||
long index;
|
||||
int index;
|
||||
dictEntry *entry;
|
||||
dictht *ht;
|
||||
|
||||
@@ -362,7 +362,7 @@ dictEntry *dictAddOrFind(dict *d, void *key) {
|
||||
* dictDelete() and dictUnlink(), please check the top comment
|
||||
* of those functions. */
|
||||
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
|
||||
uint64_t h, idx;
|
||||
unsigned int h, idx;
|
||||
dictEntry *he, *prevHe;
|
||||
int table;
|
||||
|
||||
@@ -476,7 +476,7 @@ void dictRelease(dict *d)
|
||||
dictEntry *dictFind(dict *d, const void *key)
|
||||
{
|
||||
dictEntry *he;
|
||||
uint64_t h, idx, table;
|
||||
unsigned int h, idx, table;
|
||||
|
||||
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
|
||||
if (dictIsRehashing(d)) _dictRehashStep(d);
|
||||
@@ -610,7 +610,7 @@ void dictReleaseIterator(dictIterator *iter)
|
||||
dictEntry *dictGetRandomKey(dict *d)
|
||||
{
|
||||
dictEntry *he, *orighe;
|
||||
unsigned long h;
|
||||
unsigned int h;
|
||||
int listlen, listele;
|
||||
|
||||
if (dictSize(d) == 0) return NULL;
|
||||
@@ -858,15 +858,6 @@ unsigned long dictScan(dict *d,
|
||||
de = next;
|
||||
}
|
||||
|
||||
/* Set unmasked bits so incrementing the reversed cursor
|
||||
* operates on the masked bits */
|
||||
v |= ~m0;
|
||||
|
||||
/* Increment the reverse cursor */
|
||||
v = rev(v);
|
||||
v++;
|
||||
v = rev(v);
|
||||
|
||||
} else {
|
||||
t0 = &d->ht[0];
|
||||
t1 = &d->ht[1];
|
||||
@@ -901,16 +892,22 @@ unsigned long dictScan(dict *d,
|
||||
de = next;
|
||||
}
|
||||
|
||||
/* Increment the reverse cursor not covered by the smaller mask.*/
|
||||
v |= ~m1;
|
||||
v = rev(v);
|
||||
v++;
|
||||
v = rev(v);
|
||||
/* Increment bits not covered by the smaller mask */
|
||||
v = (((v | m0) + 1) & ~m0) | (v & m0);
|
||||
|
||||
/* Continue while bits covered by mask difference is non-zero */
|
||||
} while (v & (m0 ^ m1));
|
||||
}
|
||||
|
||||
/* Set unmasked bits so incrementing the reversed cursor
|
||||
* operates on the masked bits of the smaller table */
|
||||
v |= ~m0;
|
||||
|
||||
/* Increment the reverse cursor */
|
||||
v = rev(v);
|
||||
v++;
|
||||
v = rev(v);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -958,9 +955,9 @@ static unsigned long _dictNextPower(unsigned long size)
|
||||
*
|
||||
* Note that if we are in the process of rehashing the hash table, the
|
||||
* index is always returned in the context of the second (new) hash table. */
|
||||
static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
|
||||
static int _dictKeyIndex(dict *d, const void *key, unsigned int hash, dictEntry **existing)
|
||||
{
|
||||
unsigned long idx, table;
|
||||
unsigned int idx, table;
|
||||
dictEntry *he;
|
||||
if (existing) *existing = NULL;
|
||||
|
||||
@@ -998,7 +995,7 @@ void dictDisableResize(void) {
|
||||
dict_can_resize = 0;
|
||||
}
|
||||
|
||||
uint64_t dictGetHash(dict *d, const void *key) {
|
||||
unsigned int dictGetHash(dict *d, const void *key) {
|
||||
return dictHashKey(d, key);
|
||||
}
|
||||
|
||||
@@ -1007,9 +1004,9 @@ uint64_t dictGetHash(dict *d, const void *key) {
|
||||
* the hash value should be provided using dictGetHash.
|
||||
* no string / key comparison is performed.
|
||||
* return value is the reference to the dictEntry if found, or NULL if not found. */
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash) {
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, unsigned int hash) {
|
||||
dictEntry *he, **heref;
|
||||
unsigned long idx, table;
|
||||
unsigned int idx, table;
|
||||
|
||||
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
|
||||
for (table = 0; table <= 1; table++) {
|
||||
|
||||
+2
-2
@@ -178,8 +178,8 @@ int dictRehashMilliseconds(dict *d, int ms);
|
||||
void dictSetHashFunctionSeed(uint8_t *seed);
|
||||
uint8_t *dictGetHashFunctionSeed(void);
|
||||
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata);
|
||||
uint64_t dictGetHash(dict *d, const void *key);
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash);
|
||||
unsigned int dictGetHash(dict *d, const void *key);
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, unsigned int hash);
|
||||
|
||||
/* Hash table types */
|
||||
extern dictType dictTypeHeapStringCopyKey;
|
||||
|
||||
+17
-12
@@ -60,6 +60,8 @@ struct evictionPoolEntry {
|
||||
|
||||
static struct evictionPoolEntry *EvictionPoolLRU;
|
||||
|
||||
unsigned long LFUDecrAndReturn(robj *o);
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Implementation of eviction, aging and LRU
|
||||
* --------------------------------------------------------------------------*/
|
||||
@@ -300,8 +302,8 @@ unsigned long LFUGetTimeInMinutes(void) {
|
||||
return (server.unixtime/60) & 65535;
|
||||
}
|
||||
|
||||
/* Given an object last access time, compute the minimum number of minutes
|
||||
* that elapsed since the last access. Handle overflow (ldt greater than
|
||||
/* Given an object last decrement time, compute the minimum number of minutes
|
||||
* that elapsed since the last decrement. Handle overflow (ldt greater than
|
||||
* the current 16 bits minutes time) considering the time as wrapping
|
||||
* exactly once. */
|
||||
unsigned long LFUTimeElapsed(unsigned long ldt) {
|
||||
@@ -322,22 +324,25 @@ uint8_t LFULogIncr(uint8_t counter) {
|
||||
return counter;
|
||||
}
|
||||
|
||||
/* If the object decrement time is reached decrement the LFU counter but
|
||||
* do not update LFU fields of the object, we update the access time
|
||||
* and counter in an explicit way when the object is really accessed.
|
||||
* And we will times halve the counter according to the times of
|
||||
* elapsed time than server.lfu_decay_time.
|
||||
* Return the object frequency counter.
|
||||
/* If the object decrement time is reached, decrement the LFU counter and
|
||||
* update the decrement time field. Return the object frequency counter.
|
||||
*
|
||||
* This function is used in order to scan the dataset for the best object
|
||||
* to fit: as we check for the candidate, we incrementally decrement the
|
||||
* counter of the scanned objects if needed. */
|
||||
#define LFU_DECR_INTERVAL 1
|
||||
unsigned long LFUDecrAndReturn(robj *o) {
|
||||
unsigned long ldt = o->lru >> 8;
|
||||
unsigned long counter = o->lru & 255;
|
||||
unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0;
|
||||
if (num_periods)
|
||||
counter = (num_periods > counter) ? 0 : counter - num_periods;
|
||||
if (LFUTimeElapsed(ldt) >= server.lfu_decay_time && counter) {
|
||||
if (counter > LFU_INIT_VAL*2) {
|
||||
counter /= 2;
|
||||
if (counter < LFU_INIT_VAL*2) counter = LFU_INIT_VAL*2;
|
||||
} else {
|
||||
counter--;
|
||||
}
|
||||
o->lru = (LFUGetTimeInMinutes()<<8) | counter;
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
@@ -404,7 +409,7 @@ int freeMemoryIfNeeded(void) {
|
||||
latencyStartMonitor(latency);
|
||||
while (mem_freed < mem_tofree) {
|
||||
int j, k, i, keys_freed = 0;
|
||||
static unsigned int next_db = 0;
|
||||
static int next_db = 0;
|
||||
sds bestkey = NULL;
|
||||
int bestdbid;
|
||||
redisDb *db;
|
||||
|
||||
+9
-31
@@ -103,7 +103,7 @@ void activeExpireCycle(int type) {
|
||||
|
||||
int j, iteration = 0;
|
||||
int dbs_per_call = CRON_DBS_PER_CALL;
|
||||
long long start = ustime(), timelimit, elapsed;
|
||||
long long start = ustime(), timelimit;
|
||||
|
||||
/* When clients are paused the dataset should be static not just from the
|
||||
* POV of clients not being able to write, but also from the POV of
|
||||
@@ -111,7 +111,7 @@ void activeExpireCycle(int type) {
|
||||
if (clientsArePaused()) return;
|
||||
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
||||
/* Don't start a fast cycle if the previous cycle did not exit
|
||||
/* Don't start a fast cycle if the previous cycle did not exited
|
||||
* for time limt. Also don't repeat a fast cycle for the same period
|
||||
* as the fast cycle total duration itself. */
|
||||
if (!timelimit_exit) return;
|
||||
@@ -140,13 +140,7 @@ void activeExpireCycle(int type) {
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST)
|
||||
timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */
|
||||
|
||||
/* Accumulate some global stats as we expire keys, to have some idea
|
||||
* about the number of keys that are already logically expired, but still
|
||||
* existing inside the database. */
|
||||
long total_sampled = 0;
|
||||
long total_expired = 0;
|
||||
|
||||
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
|
||||
for (j = 0; j < dbs_per_call; j++) {
|
||||
int expired;
|
||||
redisDb *db = server.db+(current_db % server.dbnum);
|
||||
|
||||
@@ -161,7 +155,6 @@ void activeExpireCycle(int type) {
|
||||
unsigned long num, slots;
|
||||
long long now, ttl_sum;
|
||||
int ttl_samples;
|
||||
iteration++;
|
||||
|
||||
/* If there is nothing to expire try next DB ASAP. */
|
||||
if ((num = dictSize(db->expires)) == 0) {
|
||||
@@ -198,9 +191,7 @@ void activeExpireCycle(int type) {
|
||||
ttl_sum += ttl;
|
||||
ttl_samples++;
|
||||
}
|
||||
total_sampled++;
|
||||
}
|
||||
total_expired += expired;
|
||||
|
||||
/* Update the average TTL stats for this database. */
|
||||
if (ttl_samples) {
|
||||
@@ -216,31 +207,18 @@ void activeExpireCycle(int type) {
|
||||
/* We can't block forever here even if there are many keys to
|
||||
* expire. So after a given amount of milliseconds return to the
|
||||
* caller waiting for the other active expire cycle. */
|
||||
iteration++;
|
||||
if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
|
||||
elapsed = ustime()-start;
|
||||
if (elapsed > timelimit) {
|
||||
timelimit_exit = 1;
|
||||
server.stat_expired_time_cap_reached_count++;
|
||||
break;
|
||||
}
|
||||
long long elapsed = ustime()-start;
|
||||
|
||||
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
|
||||
if (elapsed > timelimit) timelimit_exit = 1;
|
||||
}
|
||||
if (timelimit_exit) return;
|
||||
/* We don't repeat the cycle if there are less than 25% of keys
|
||||
* found expired in the current DB. */
|
||||
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
|
||||
}
|
||||
|
||||
elapsed = ustime()-start;
|
||||
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
|
||||
|
||||
/* Update our estimate of keys existing but yet to be expired.
|
||||
* Running average with this sample accounting for 5%. */
|
||||
double current_perc;
|
||||
if (total_sampled) {
|
||||
current_perc = (double)total_expired/total_sampled;
|
||||
} else
|
||||
current_perc = 0;
|
||||
server.stat_expired_stale_perc = (current_perc*0.05)+
|
||||
(server.stat_expired_stale_perc*0.95);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
|
||||
@@ -659,7 +659,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
setKey(c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"georadiusstore",storekey,
|
||||
c->db->id);
|
||||
server.dirty += returned_items;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
|
||||
+25
-58
@@ -475,8 +475,9 @@ int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
|
||||
|
||||
/* ================== Dense representation implementation ================== */
|
||||
|
||||
/* Low level function to set the dense HLL register at 'index' to the
|
||||
* specified value if the current value is smaller than 'count'.
|
||||
/* "Add" the element in the dense hyperloglog data structure.
|
||||
* Actually nothing is added, but the max 0 pattern counter of the subset
|
||||
* the element belongs to is incremented if needed.
|
||||
*
|
||||
* 'registers' is expected to have room for HLL_REGISTERS plus an
|
||||
* additional byte on the right. This requirement is met by sds strings
|
||||
@@ -485,9 +486,12 @@ int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
|
||||
* The function always succeed, however if as a result of the operation
|
||||
* the approximated cardinality changed, 1 is returned. Otherwise 0
|
||||
* is returned. */
|
||||
int hllDenseSet(uint8_t *registers, long index, uint8_t count) {
|
||||
uint8_t oldcount;
|
||||
int hllDenseAdd(uint8_t *registers, unsigned char *ele, size_t elesize) {
|
||||
uint8_t oldcount, count;
|
||||
long index;
|
||||
|
||||
/* Update the register if this element produced a longer run of zeroes. */
|
||||
count = hllPatLen(ele,elesize,&index);
|
||||
HLL_DENSE_GET_REGISTER(oldcount,registers,index);
|
||||
if (count > oldcount) {
|
||||
HLL_DENSE_SET_REGISTER(registers,index,count);
|
||||
@@ -497,19 +501,6 @@ int hllDenseSet(uint8_t *registers, long index, uint8_t count) {
|
||||
}
|
||||
}
|
||||
|
||||
/* "Add" the element in the dense hyperloglog data structure.
|
||||
* Actually nothing is added, but the max 0 pattern counter of the subset
|
||||
* the element belongs to is incremented if needed.
|
||||
*
|
||||
* This is just a wrapper to hllDenseSet(), performing the hashing of the
|
||||
* element in order to retrieve the index and zero-run count. */
|
||||
int hllDenseAdd(uint8_t *registers, unsigned char *ele, size_t elesize) {
|
||||
long index;
|
||||
uint8_t count = hllPatLen(ele,elesize,&index);
|
||||
/* Update the register if this element produced a longer run of zeroes. */
|
||||
return hllDenseSet(registers,index,count);
|
||||
}
|
||||
|
||||
/* Compute SUM(2^-reg) in the dense representation.
|
||||
* PE is an array with a pre-computer table of values 2^-reg indexed by reg.
|
||||
* As a side effect the integer pointed by 'ezp' is set to the number
|
||||
@@ -611,7 +602,6 @@ int hllSparseToDense(robj *o) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval);
|
||||
idx++;
|
||||
@@ -633,8 +623,9 @@ int hllSparseToDense(robj *o) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Low level function to set the sparse HLL register at 'index' to the
|
||||
* specified value if the current value is smaller than 'count'.
|
||||
/* "Add" the element in the sparse hyperloglog data structure.
|
||||
* Actually nothing is added, but the max 0 pattern counter of the subset
|
||||
* the element belongs to is incremented if needed.
|
||||
*
|
||||
* The object 'o' is the String object holding the HLL. The function requires
|
||||
* a reference to the object in order to be able to enlarge the string if
|
||||
@@ -648,12 +639,15 @@ int hllSparseToDense(robj *o) {
|
||||
* sparse to dense: this happens when a register requires to be set to a value
|
||||
* not representable with the sparse representation, or when the resulting
|
||||
* size would be greater than server.hll_sparse_max_bytes. */
|
||||
int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
int hllSparseAdd(robj *o, unsigned char *ele, size_t elesize) {
|
||||
struct hllhdr *hdr;
|
||||
uint8_t oldcount, *sparse, *end, *p, *prev, *next;
|
||||
long first, span;
|
||||
uint8_t oldcount, count, *sparse, *end, *p, *prev, *next;
|
||||
long index, first, span;
|
||||
long is_zero = 0, is_xzero = 0, is_val = 0, runlen = 0;
|
||||
|
||||
/* Update the register if this element produced a longer run of zeroes. */
|
||||
count = hllPatLen(ele,elesize,&index);
|
||||
|
||||
/* If the count is too big to be representable by the sparse representation
|
||||
* switch to dense representation. */
|
||||
if (count > HLL_SPARSE_VAL_MAX_VALUE) goto promote;
|
||||
@@ -697,7 +691,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
p += oplen;
|
||||
first += span;
|
||||
}
|
||||
if (span == 0 || p >= end) return -1; /* Invalid format. */
|
||||
if (span == 0) return -1; /* Invalid format. */
|
||||
|
||||
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
|
||||
if (next >= end) next = NULL;
|
||||
@@ -886,24 +880,11 @@ promote: /* Promote to dense representation. */
|
||||
* Note that this in turn means that PFADD will make sure the command
|
||||
* is propagated to slaves / AOF, so if there is a sparse -> dense
|
||||
* convertion, it will be performed in all the slaves as well. */
|
||||
int dense_retval = hllDenseSet(hdr->registers,index,count);
|
||||
int dense_retval = hllDenseAdd(hdr->registers, ele, elesize);
|
||||
serverAssert(dense_retval == 1);
|
||||
return dense_retval;
|
||||
}
|
||||
|
||||
/* "Add" the element in the sparse hyperloglog data structure.
|
||||
* Actually nothing is added, but the max 0 pattern counter of the subset
|
||||
* the element belongs to is incremented if needed.
|
||||
*
|
||||
* This function is actually a wrapper for hllSparseSet(), it only performs
|
||||
* the hashshing of the elmenet to obtain the index and zeros run length. */
|
||||
int hllSparseAdd(robj *o, unsigned char *ele, size_t elesize) {
|
||||
long index;
|
||||
uint8_t count = hllPatLen(ele,elesize,&index);
|
||||
/* Update the register if this element produced a longer run of zeroes. */
|
||||
return hllSparseSet(o,index,count);
|
||||
}
|
||||
|
||||
/* Compute SUM(2^-reg) in the sparse representation.
|
||||
* PE is an array with a pre-computer table of values 2^-reg indexed by reg.
|
||||
* As a side effect the integer pointed by 'ezp' is set to the number
|
||||
@@ -1082,7 +1063,6 @@ int hllMerge(uint8_t *max, robj *hll) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
if (regval > max[i]) max[i] = regval;
|
||||
i++;
|
||||
@@ -1300,10 +1280,9 @@ void pfmergeCommand(client *c) {
|
||||
uint8_t max[HLL_REGISTERS];
|
||||
struct hllhdr *hdr;
|
||||
int j;
|
||||
int use_dense = 0; /* Use dense representation as target? */
|
||||
|
||||
/* Compute an HLL with M[i] = MAX(M[i]_j).
|
||||
* We store the maximum into the max array of registers. We'll write
|
||||
* We we the maximum into the max array of registers. We'll write
|
||||
* it to the target variable later. */
|
||||
memset(max,0,sizeof(max));
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
@@ -1312,11 +1291,6 @@ void pfmergeCommand(client *c) {
|
||||
if (o == NULL) continue; /* Assume empty HLL for non existing var. */
|
||||
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
||||
|
||||
/* If at least one involved HLL is dense, use the dense representation
|
||||
* as target ASAP to save time and avoid the conversion step. */
|
||||
hdr = o->ptr;
|
||||
if (hdr->encoding == HLL_DENSE) use_dense = 1;
|
||||
|
||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
||||
* to MAX(max[i],hll[i]). */
|
||||
if (hllMerge(max,o) == C_ERR) {
|
||||
@@ -1340,29 +1314,22 @@ void pfmergeCommand(client *c) {
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
}
|
||||
|
||||
/* Convert the destination object to dense representation if at least
|
||||
* one of the inputs was dense. */
|
||||
if (use_dense && hllSparseToDense(o) == C_ERR) {
|
||||
/* Only support dense objects as destination. */
|
||||
if (hllSparseToDense(o) == C_ERR) {
|
||||
addReplySds(c,sdsnew(invalid_hll_err));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Write the resulting HLL to the destination HLL registers and
|
||||
* invalidate the cached value. */
|
||||
hdr = o->ptr;
|
||||
for (j = 0; j < HLL_REGISTERS; j++) {
|
||||
if (max[j] == 0) continue;
|
||||
hdr = o->ptr;
|
||||
switch(hdr->encoding) {
|
||||
case HLL_DENSE: hllDenseSet(hdr->registers,j,max[j]); break;
|
||||
case HLL_SPARSE: hllSparseSet(o,j,max[j]); break;
|
||||
}
|
||||
HLL_DENSE_SET_REGISTER(hdr->registers,j,max[j]);
|
||||
}
|
||||
hdr = o->ptr; /* o->ptr may be different now, as a side effect of
|
||||
last hllSparseSet() call. */
|
||||
HLL_INVALIDATE_CACHE(hdr);
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
/* We generate a PFADD event for PFMERGE for semantical simplicity
|
||||
/* We generate an PFADD event for PFMERGE for semantical simplicity
|
||||
* since in theory this is a mass-add of elements. */
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
+1
-2
@@ -109,8 +109,6 @@ void latencyAddSample(char *event, mstime_t latency) {
|
||||
dictAdd(server.latency_events,zstrdup(event),ts);
|
||||
}
|
||||
|
||||
if (latency > ts->max) ts->max = latency;
|
||||
|
||||
/* If the previous sample is in the same second, we update our old sample
|
||||
* if this latency is > of the old one, or just return. */
|
||||
prev = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
|
||||
@@ -122,6 +120,7 @@ void latencyAddSample(char *event, mstime_t latency) {
|
||||
|
||||
ts->samples[ts->idx].time = time(NULL);
|
||||
ts->samples[ts->idx].latency = latency;
|
||||
if (latency > ts->max) ts->max = latency;
|
||||
|
||||
ts->idx++;
|
||||
if (ts->idx == LATENCY_TS_LEN) ts->idx = 0;
|
||||
|
||||
+3
-9
@@ -64,15 +64,9 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
robj *val = dictGetVal(de);
|
||||
size_t free_effort = lazyfreeGetFreeEffort(val);
|
||||
|
||||
/* If releasing the object is too much work, do it in the background
|
||||
* by adding the object to the lazy free list.
|
||||
* Note that if the object is shared, to reclaim it now it is not
|
||||
* possible. This rarely happens, however sometimes the implementation
|
||||
* of parts of the Redis core may call incrRefCount() to protect
|
||||
* objects, and then call dbDelete(). In this case we'll fall
|
||||
* through and reach the dictFreeUnlinkedEntry() call, that will be
|
||||
* equivalent to just calling decrRefCount(). */
|
||||
if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
|
||||
/* If releasing the object is too much work, let's put it into the
|
||||
* lazy free list. */
|
||||
if (free_effort > LAZYFREE_THRESHOLD) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
|
||||
dictSetVal(db->dict,de,NULL);
|
||||
|
||||
+1
-5
@@ -79,11 +79,7 @@
|
||||
* Unconditionally aligning does not cost very much, so do it if unsure
|
||||
*/
|
||||
#ifndef STRICT_ALIGN
|
||||
# if !(defined(__i386) || defined (__amd64))
|
||||
# define STRICT_ALIGN 1
|
||||
# else
|
||||
# define STRICT_ALIGN 0
|
||||
# endif
|
||||
# define STRICT_ALIGN !(defined(__i386) || defined (__amd64))
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
+26
-209
@@ -216,31 +216,6 @@ static list *moduleUnblockedClients;
|
||||
* allow thread safe contexts to execute commands at a safe moment. */
|
||||
static pthread_mutex_t moduleGIL = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
|
||||
/* Function pointer type for keyspace event notification subscriptions from modules. */
|
||||
typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||
|
||||
/* Keyspace notification subscriber information.
|
||||
* See RM_SubscribeToKeyspaceEvents() for more information. */
|
||||
typedef struct RedisModuleKeyspaceSubscriber {
|
||||
/* The module subscribed to the event */
|
||||
RedisModule *module;
|
||||
/* Notification callback in the module*/
|
||||
RedisModuleNotificationFunc notify_callback;
|
||||
/* A bit mask of the events the module is interested in */
|
||||
int event_mask;
|
||||
/* Active flag set on entry, to avoid reentrant subscribers
|
||||
* calling themselves */
|
||||
int active;
|
||||
} RedisModuleKeyspaceSubscriber;
|
||||
|
||||
/* The module keyspace notification subscribers list */
|
||||
static list *moduleKeyspaceSubscribers;
|
||||
|
||||
/* Static client recycled for all notification clients, to avoid allocating
|
||||
* per round. */
|
||||
static client *moduleKeyspaceSubscribersClient;
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -638,7 +613,7 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
|
||||
sds cmdname = sdsnew(name);
|
||||
|
||||
/* Check if the command name is busy. */
|
||||
if (lookupCommand(cmdname) != NULL) {
|
||||
if (lookupCommand((char*)name) != NULL) {
|
||||
sdsfree(cmdname);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
@@ -673,7 +648,7 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
|
||||
*
|
||||
* This is an internal function, Redis modules developers don't need
|
||||
* to use it. */
|
||||
void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
|
||||
void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int apiver){
|
||||
RedisModule *module;
|
||||
|
||||
if (ctx->module != NULL) return;
|
||||
@@ -685,15 +660,6 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
|
||||
ctx->module = module;
|
||||
}
|
||||
|
||||
/* Return non-zero if the module name is busy.
|
||||
* Otherwise zero is returned. */
|
||||
int RM_IsModuleNameBusy(const char *name) {
|
||||
sds modulename = sdsnew(name);
|
||||
dictEntry *de = dictFind(modules,modulename);
|
||||
sdsfree(modulename);
|
||||
return de != NULL;
|
||||
}
|
||||
|
||||
/* Return the current UNIX time in milliseconds. */
|
||||
long long RM_Milliseconds(void) {
|
||||
return mstime();
|
||||
@@ -1196,9 +1162,7 @@ int RM_ReplyWithDouble(RedisModuleCtx *ctx, double d) {
|
||||
* in the context of a command execution. EXEC will be handled by the
|
||||
* RedisModuleCommandDispatcher() function. */
|
||||
void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
/* Skip this if client explicitly wrap the command with MULTI, or if
|
||||
* the module command was called by a script. */
|
||||
if (ctx->client->flags & (CLIENT_MULTI|CLIENT_LUA)) return;
|
||||
if (ctx->client->flags & CLIENT_LUA) return;
|
||||
/* If we already emitted MULTI return ASAP. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
|
||||
/* If this is a thread safe context, we do not want to wrap commands
|
||||
@@ -1481,20 +1445,6 @@ int RM_DeleteKey(RedisModuleKey *key) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* If the key is open for writing, unlink it (that is delete it in a
|
||||
* non-blocking way, not reclaiming memory immediately) and setup the key to
|
||||
* accept new writes as an empty key (that will be created on demand).
|
||||
* On success REDISMODULE_OK is returned. If the key is not open for
|
||||
* writing REDISMODULE_ERR is returned. */
|
||||
int RM_UnlinkKey(RedisModuleKey *key) {
|
||||
if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
|
||||
if (key->value) {
|
||||
dbAsyncDelete(key->db,key->key);
|
||||
key->value = NULL;
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Return the key expire value, as milliseconds of remaining TTL.
|
||||
* If no TTL is associated with the key or if the key is empty,
|
||||
* REDISMODULE_NO_EXPIRE is returned. */
|
||||
@@ -3063,7 +3013,7 @@ int64_t RM_LoadSigned(RedisModuleIO *io) {
|
||||
void RM_SaveString(RedisModuleIO *io, RedisModuleString *s) {
|
||||
if (io->error) return;
|
||||
/* Save opcode. */
|
||||
ssize_t retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
/* Save value. */
|
||||
@@ -3081,7 +3031,7 @@ saveerr:
|
||||
void RM_SaveStringBuffer(RedisModuleIO *io, const char *str, size_t len) {
|
||||
if (io->error) return;
|
||||
/* Save opcode. */
|
||||
ssize_t retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
/* Save value. */
|
||||
@@ -3694,120 +3644,6 @@ void moduleReleaseGIL(void) {
|
||||
pthread_mutex_unlock(&moduleGIL);
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Module Keyspace Notifications API
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Subscribe to keyspace notifications. This is a low-level version of the
|
||||
* keyspace-notifications API. A module cand register callbacks to be notified
|
||||
* when keyspce events occur.
|
||||
*
|
||||
* Notification events are filtered by their type (string events, set events,
|
||||
* etc), and the subsriber callback receives only events that match a specific
|
||||
* mask of event types.
|
||||
*
|
||||
* When subscribing to notifications with RedisModule_SubscribeToKeyspaceEvents
|
||||
* the module must provide an event type-mask, denoting the events the subscriber
|
||||
* is interested in. This can be an ORed mask of any of the following flags:
|
||||
*
|
||||
* - REDISMODULE_NOTIFY_GENERIC: Generic commands like DEL, EXPIRE, RENAME
|
||||
* - REDISMODULE_NOTIFY_STRING: String events
|
||||
* - REDISMODULE_NOTIFY_LIST: List events
|
||||
* - REDISMODULE_NOTIFY_SET: Set events
|
||||
* - REDISMODULE_NOTIFY_HASH: Hash events
|
||||
* - REDISMODULE_NOTIFY_ZSET: Sorted Set events
|
||||
* - REDISMODULE_NOTIFY_EXPIRED: Expiration events
|
||||
* - REDISMODULE_NOTIFY_EVICTED: Eviction events
|
||||
* - REDISMODULE_NOTIFY_STREAM: Stream events
|
||||
* - REDISMODULE_NOTIFY_ALL: All events
|
||||
*
|
||||
* We do not distinguish between key events and keyspace events, and it is up
|
||||
* to the module to filter the actions taken based on the key.
|
||||
*
|
||||
* The subscriber signature is:
|
||||
*
|
||||
* int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type,
|
||||
* const char *event,
|
||||
* RedisModuleString *key);
|
||||
*
|
||||
* `type` is the event type bit, that must match the mask given at registration
|
||||
* time. The event string is the actual command being executed, and key is the
|
||||
* relevant Redis key.
|
||||
*
|
||||
* Notification callback gets executed with a redis context that can not be
|
||||
* used to send anything to the client, and has the db number where the event
|
||||
* occured as its selected db number.
|
||||
*
|
||||
* Notice that it is not necessary to enable norifications in redis.conf for
|
||||
* module notifications to work.
|
||||
*
|
||||
* Warning: the notification callbacks are performed in a synchronous manner,
|
||||
* so notification callbacks must to be fast, or they would slow Redis down.
|
||||
* If you need to take long actions, use threads to offload them.
|
||||
*
|
||||
* See https://redis.io/topics/notifications for more information.
|
||||
*/
|
||||
int RM_SubscribeToKeyspaceEvents(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc callback) {
|
||||
RedisModuleKeyspaceSubscriber *sub = zmalloc(sizeof(*sub));
|
||||
sub->module = ctx->module;
|
||||
sub->event_mask = types;
|
||||
sub->notify_callback = callback;
|
||||
sub->active = 0;
|
||||
|
||||
listAddNodeTail(moduleKeyspaceSubscribers, sub);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Dispatcher for keyspace notifications to module subscriber functions.
|
||||
* This gets called only if at least one module requested to be notified on
|
||||
* keyspace notifications */
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) {
|
||||
/* Don't do anything if there aren't any subscribers */
|
||||
if (listLength(moduleKeyspaceSubscribers) == 0) return;
|
||||
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(moduleKeyspaceSubscribers,&li);
|
||||
|
||||
/* Remove irrelevant flags from the type mask */
|
||||
type &= ~(NOTIFY_KEYEVENT | NOTIFY_KEYSPACE);
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModuleKeyspaceSubscriber *sub = ln->value;
|
||||
/* Only notify subscribers on events matching they registration,
|
||||
* and avoid subscribers triggering themselves */
|
||||
if ((sub->event_mask & type) && sub->active == 0) {
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
ctx.module = sub->module;
|
||||
ctx.client = moduleKeyspaceSubscribersClient;
|
||||
selectDb(ctx.client, dbid);
|
||||
|
||||
/* mark the handler as activer to avoid reentrant loops.
|
||||
* If the subscriber performs an action triggering itself,
|
||||
* it will not be notified about it. */
|
||||
sub->active = 1;
|
||||
sub->notify_callback(&ctx, type, event, key);
|
||||
sub->active = 0;
|
||||
moduleFreeContext(&ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Unsubscribe any notification subscirbers this module has upon unloading */
|
||||
void moduleUnsubscribeNotifications(RedisModule *module) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(moduleKeyspaceSubscribers,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModuleKeyspaceSubscriber *sub = ln->value;
|
||||
if (sub->module == module) {
|
||||
listDelNode(moduleKeyspaceSubscribers, ln);
|
||||
zfree(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules API internals
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -3845,14 +3681,9 @@ void moduleRegisterCoreAPI(void);
|
||||
|
||||
void moduleInitModulesSystem(void) {
|
||||
moduleUnblockedClients = listCreate();
|
||||
|
||||
server.loadmodule_queue = listCreate();
|
||||
modules = dictCreate(&modulesDictType,NULL);
|
||||
|
||||
/* Set up the keyspace notification susbscriber list and static client */
|
||||
moduleKeyspaceSubscribers = listCreate();
|
||||
moduleKeyspaceSubscribersClient = createClient(-1);
|
||||
moduleKeyspaceSubscribersClient->flags |= CLIENT_MODULE;
|
||||
|
||||
moduleRegisterCoreAPI();
|
||||
if (pipe(server.module_blocked_pipe) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
@@ -3903,28 +3734,6 @@ void moduleFreeModuleStructure(struct RedisModule *module) {
|
||||
zfree(module);
|
||||
}
|
||||
|
||||
void moduleUnregisterCommands(struct RedisModule *module) {
|
||||
/* Unregister all the commands registered by this module. */
|
||||
dictIterator *di = dictGetSafeIterator(server.commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = dictGetVal(de);
|
||||
if (cmd->proc == RedisModuleCommandDispatcher) {
|
||||
RedisModuleCommandProxy *cp =
|
||||
(void*)(unsigned long)cmd->getkeys_proc;
|
||||
sds cmdname = cp->rediscmd->name;
|
||||
if (cp->module == module) {
|
||||
dictDelete(server.commands,cmdname);
|
||||
dictDelete(server.orig_commands,cmdname);
|
||||
sdsfree(cmdname);
|
||||
zfree(cp->rediscmd);
|
||||
zfree(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* Load a module and initialize it. On success C_OK is returned, otherwise
|
||||
* C_ERR is returned. */
|
||||
int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
@@ -3945,10 +3754,7 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
return C_ERR;
|
||||
}
|
||||
if (onload((void*)&ctx,module_argv,module_argc) == REDISMODULE_ERR) {
|
||||
if (ctx.module) {
|
||||
moduleUnregisterCommands(ctx.module);
|
||||
moduleFreeModuleStructure(ctx.module);
|
||||
}
|
||||
if (ctx.module) moduleFreeModuleStructure(ctx.module);
|
||||
dlclose(handle);
|
||||
serverLog(LL_WARNING,
|
||||
"Module %s initialization failed. Module not loaded",path);
|
||||
@@ -3963,7 +3769,6 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Unload the module registered with the specified name. On success
|
||||
* C_OK is returned, otherwise C_ERR is returned and errno is set
|
||||
* to the following values depending on the type of error:
|
||||
@@ -3983,10 +3788,25 @@ int moduleUnload(sds name) {
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
moduleUnregisterCommands(module);
|
||||
|
||||
/* Remvoe any noification subscribers this module might have */
|
||||
moduleUnsubscribeNotifications(module);
|
||||
/* Unregister all the commands registered by this module. */
|
||||
dictIterator *di = dictGetSafeIterator(server.commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = dictGetVal(de);
|
||||
if (cmd->proc == RedisModuleCommandDispatcher) {
|
||||
RedisModuleCommandProxy *cp =
|
||||
(void*)(unsigned long)cmd->getkeys_proc;
|
||||
sds cmdname = cp->rediscmd->name;
|
||||
if (cp->module == module) {
|
||||
dictDelete(server.commands,cmdname);
|
||||
dictDelete(server.orig_commands,cmdname);
|
||||
sdsfree(cmdname);
|
||||
zfree(cp->rediscmd);
|
||||
zfree(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
/* Unregister all the hooks. TODO: Yet no hooks support here. */
|
||||
|
||||
@@ -4081,7 +3901,6 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(Strdup);
|
||||
REGISTER_API(CreateCommand);
|
||||
REGISTER_API(SetModuleAttribs);
|
||||
REGISTER_API(IsModuleNameBusy);
|
||||
REGISTER_API(WrongArity);
|
||||
REGISTER_API(ReplyWithLongLong);
|
||||
REGISTER_API(ReplyWithError);
|
||||
@@ -4122,7 +3941,6 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(Replicate);
|
||||
REGISTER_API(ReplicateVerbatim);
|
||||
REGISTER_API(DeleteKey);
|
||||
REGISTER_API(UnlinkKey);
|
||||
REGISTER_API(StringSet);
|
||||
REGISTER_API(StringDMA);
|
||||
REGISTER_API(StringTruncate);
|
||||
@@ -4185,5 +4003,4 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(DigestAddStringBuffer);
|
||||
REGISTER_API(DigestAddLongLong);
|
||||
REGISTER_API(DigestEndSequence);
|
||||
REGISTER_API(SubscribeToKeyspaceEvents);
|
||||
}
|
||||
|
||||
+35
-161
@@ -30,7 +30,6 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "../redismodule.h"
|
||||
#include <string.h>
|
||||
|
||||
@@ -121,187 +120,81 @@ int TestStringPrintf(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int failTest(RedisModuleCtx *ctx, const char *msg) {
|
||||
RedisModule_ReplyWithError(ctx, msg);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_AutoMemory(ctx);
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "unlinked"), REDISMODULE_WRITE | REDISMODULE_READ);
|
||||
if (!k) return failTest(ctx, "Could not create key");
|
||||
|
||||
if (REDISMODULE_ERR == RedisModule_StringSet(k, RedisModule_CreateStringPrintf(ctx, "Foobar"))) {
|
||||
return failTest(ctx, "Could not set string value");
|
||||
}
|
||||
|
||||
RedisModuleCallReply *rep = RedisModule_Call(ctx, "EXISTS", "c", "unlinked");
|
||||
if (!rep || RedisModule_CallReplyInteger(rep) != 1) {
|
||||
return failTest(ctx, "Key does not exist before unlink");
|
||||
}
|
||||
|
||||
if (REDISMODULE_ERR == RedisModule_UnlinkKey(k)) {
|
||||
return failTest(ctx, "Could not unlink key");
|
||||
}
|
||||
|
||||
rep = RedisModule_Call(ctx, "EXISTS", "c", "unlinked");
|
||||
if (!rep || RedisModule_CallReplyInteger(rep) != 0) {
|
||||
return failTest(ctx, "Could not verify key to be unlinked");
|
||||
}
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
|
||||
}
|
||||
|
||||
int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event,
|
||||
RedisModuleString *key) {
|
||||
/* Increment a counter on the notifications: for each key notified we
|
||||
* increment a counter */
|
||||
RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s", type,
|
||||
event, RedisModule_StringPtrLen(key, NULL));
|
||||
|
||||
RedisModule_Call(ctx, "HINCRBY", "csc", "notifications", key, "1");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* TEST.NOTIFICATIONS -- Test Keyspace Notifications. */
|
||||
int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
#define FAIL(msg, ...) \
|
||||
{ \
|
||||
RedisModule_Log(ctx, "warning", "Failed NOTIFY Test. Reason: " #msg, ##__VA_ARGS__); \
|
||||
goto err; \
|
||||
}
|
||||
RedisModule_Call(ctx, "FLUSHDB", "");
|
||||
|
||||
RedisModule_Call(ctx, "SET", "cc", "foo", "bar");
|
||||
RedisModule_Call(ctx, "SET", "cc", "foo", "baz");
|
||||
RedisModule_Call(ctx, "SADD", "cc", "bar", "x");
|
||||
RedisModule_Call(ctx, "SADD", "cc", "bar", "y");
|
||||
|
||||
RedisModule_Call(ctx, "HSET", "ccc", "baz", "x", "y");
|
||||
/* LPUSH should be ignored and not increment any counters */
|
||||
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
|
||||
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
|
||||
|
||||
size_t sz;
|
||||
const char *rep;
|
||||
RedisModuleCallReply *r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo");
|
||||
if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
|
||||
FAIL("Wrong or no reply for foo");
|
||||
} else {
|
||||
rep = RedisModule_CallReplyStringPtr(r, &sz);
|
||||
if (sz != 1 || *rep != '2') {
|
||||
FAIL("Got reply '%s'. expected '2'", RedisModule_CallReplyStringPtr(r, NULL));
|
||||
}
|
||||
}
|
||||
|
||||
r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "bar");
|
||||
if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
|
||||
FAIL("Wrong or no reply for bar");
|
||||
} else {
|
||||
rep = RedisModule_CallReplyStringPtr(r, &sz);
|
||||
if (sz != 1 || *rep != '2') {
|
||||
FAIL("Got reply '%s'. expected '2'", rep);
|
||||
}
|
||||
}
|
||||
|
||||
r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "baz");
|
||||
if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
|
||||
FAIL("Wrong or no reply for baz");
|
||||
} else {
|
||||
rep = RedisModule_CallReplyStringPtr(r, &sz);
|
||||
if (sz != 1 || *rep != '1') {
|
||||
FAIL("Got reply '%.*s'. expected '1'", sz, rep);
|
||||
}
|
||||
}
|
||||
/* For l we expect nothing since we didn't subscribe to list events */
|
||||
r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "l");
|
||||
if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_NULL) {
|
||||
FAIL("Wrong reply for l");
|
||||
}
|
||||
|
||||
RedisModule_Call(ctx, "FLUSHDB", "");
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
err:
|
||||
RedisModule_Call(ctx, "FLUSHDB", "");
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "ERR");
|
||||
}
|
||||
|
||||
/* TEST.CTXFLAGS -- Test GetContextFlags. */
|
||||
int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
|
||||
|
||||
RedisModule_AutoMemory(ctx);
|
||||
|
||||
|
||||
int ok = 1;
|
||||
const char *errString = NULL;
|
||||
#undef FAIL
|
||||
#define FAIL(msg) \
|
||||
{ \
|
||||
ok = 0; \
|
||||
errString = msg; \
|
||||
goto end; \
|
||||
|
||||
#define FAIL(msg) \
|
||||
{ \
|
||||
ok = 0; \
|
||||
errString = msg; \
|
||||
goto end; \
|
||||
}
|
||||
|
||||
|
||||
int flags = RedisModule_GetContextFlags(ctx);
|
||||
if (flags == 0) {
|
||||
FAIL("Got no flags");
|
||||
FAIL("Got no flags");
|
||||
}
|
||||
|
||||
|
||||
if (flags & REDISMODULE_CTX_FLAGS_LUA) FAIL("Lua flag was set");
|
||||
if (flags & REDISMODULE_CTX_FLAGS_MULTI) FAIL("Multi flag was set");
|
||||
|
||||
|
||||
if (flags & REDISMODULE_CTX_FLAGS_AOF) FAIL("AOF Flag was set")
|
||||
/* Enable AOF to test AOF flags */
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "yes");
|
||||
flags = RedisModule_GetContextFlags(ctx);
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_AOF)) FAIL("AOF Flag not set after config set");
|
||||
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_AOF))
|
||||
FAIL("AOF Flag not set after config set");
|
||||
|
||||
if (flags & REDISMODULE_CTX_FLAGS_RDB) FAIL("RDB Flag was set");
|
||||
/* Enable RDB to test RDB flags */
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "save", "900 1");
|
||||
flags = RedisModule_GetContextFlags(ctx);
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_RDB)) FAIL("RDB Flag was not set after config set");
|
||||
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_RDB))
|
||||
FAIL("RDB Flag was not set after config set");
|
||||
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_MASTER)) FAIL("Master flag was not set");
|
||||
if (flags & REDISMODULE_CTX_FLAGS_SLAVE) FAIL("Slave flag was set");
|
||||
if (flags & REDISMODULE_CTX_FLAGS_READONLY) FAIL("Read-only flag was set");
|
||||
if (flags & REDISMODULE_CTX_FLAGS_CLUSTER) FAIL("Cluster flag was set");
|
||||
|
||||
|
||||
if (flags & REDISMODULE_CTX_FLAGS_MAXMEMORY) FAIL("Maxmemory flag was set");
|
||||
|
||||
;
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "100000000");
|
||||
flags = RedisModule_GetContextFlags(ctx);
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_MAXMEMORY))
|
||||
FAIL("Maxmemory flag was not set after config set");
|
||||
|
||||
FAIL("Maxmemory flag was not set after config set");
|
||||
|
||||
if (flags & REDISMODULE_CTX_FLAGS_EVICT) FAIL("Eviction flag was set");
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", "allkeys-lru");
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy",
|
||||
"allkeys-lru");
|
||||
flags = RedisModule_GetContextFlags(ctx);
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_EVICT)) FAIL("Eviction flag was not set after config set");
|
||||
|
||||
end:
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_EVICT))
|
||||
FAIL("Eviction flag was not set after config set");
|
||||
|
||||
end:
|
||||
/* Revert config changes */
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "no");
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "save", "");
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "0");
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", "noeviction");
|
||||
|
||||
|
||||
if (!ok) {
|
||||
RedisModule_Log(ctx, "warning", "Failed CTXFLAGS Test. Reason: %s", errString);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "ERR");
|
||||
RedisModule_Log(ctx, "warning", "Failed CTXFLAGS Test. Reason: %s",
|
||||
errString);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "ERR");
|
||||
}
|
||||
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------- Test framework ----------------------------- */
|
||||
|
||||
@@ -376,18 +269,12 @@ int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
T("test.string.append","");
|
||||
if (!TestAssertStringReply(ctx,reply,"foobar",6)) goto fail;
|
||||
|
||||
T("test.unlink","");
|
||||
if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
|
||||
|
||||
T("test.string.append.am","");
|
||||
if (!TestAssertStringReply(ctx,reply,"foobar",6)) goto fail;
|
||||
|
||||
T("test.string.printf", "cc", "foo", "bar");
|
||||
if (!TestAssertStringReply(ctx,reply,"Got 3 args. argv[1]: foo, argv[2]: bar",38)) goto fail;
|
||||
|
||||
T("test.notify", "");
|
||||
if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
|
||||
|
||||
RedisModule_ReplyWithSimpleString(ctx,"ALL TESTS PASSED");
|
||||
return REDISMODULE_OK;
|
||||
|
||||
@@ -423,23 +310,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_CreateCommand(ctx,"test.ctxflags",
|
||||
TestCtxFlags,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"test.unlink",
|
||||
TestUnlink,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"test.it",
|
||||
TestIt,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_SubscribeToKeyspaceEvents(ctx,
|
||||
REDISMODULE_NOTIFY_HASH |
|
||||
REDISMODULE_NOTIFY_SET |
|
||||
REDISMODULE_NOTIFY_STRING,
|
||||
NotifyCallback);
|
||||
if (RedisModule_CreateCommand(ctx,"test.notify",
|
||||
TestNotifications,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ void execCommand(client *c) {
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL);
|
||||
call(c,CMD_CALL_FULL);
|
||||
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
c->mstate.commands[j].argc = c->argc;
|
||||
|
||||
+22
-62
@@ -33,7 +33,7 @@
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static void setProtocolError(const char *errstr, client *c, long pos);
|
||||
static void setProtocolError(const char *errstr, client *c, int pos);
|
||||
|
||||
/* Return the size consumed from the allocator, for the specified SDS string,
|
||||
* including internal fragmentation. This function is used in order to compute
|
||||
@@ -67,16 +67,6 @@ int listMatchObjects(void *a, void *b) {
|
||||
return equalStringObjects(a,b);
|
||||
}
|
||||
|
||||
/* This function links the client to the global linked list of clients.
|
||||
* unlinkClient() does the opposite, among other things. */
|
||||
void linkClient(client *c) {
|
||||
listAddNodeTail(server.clients,c);
|
||||
/* Note that we remember the linked list node where the client is stored,
|
||||
* this way removing the client in unlinkClient() will not require
|
||||
* a linear scan, but just a constant time operation. */
|
||||
c->client_list_node = listLast(server.clients);
|
||||
}
|
||||
|
||||
client *createClient(int fd) {
|
||||
client *c = zmalloc(sizeof(client));
|
||||
|
||||
@@ -143,10 +133,9 @@ client *createClient(int fd) {
|
||||
c->pubsub_channels = dictCreate(&objectKeyPointerValueDictType,NULL);
|
||||
c->pubsub_patterns = listCreate();
|
||||
c->peerid = NULL;
|
||||
c->client_list_node = NULL;
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (fd != -1) linkClient(c);
|
||||
if (fd != -1) listAddNodeTail(server.clients,c);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
}
|
||||
@@ -387,14 +376,6 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
addReplyString(c,"-ERR ",5);
|
||||
addReplyString(c,s,len);
|
||||
addReplyString(c,"\r\n",2);
|
||||
if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE)) {
|
||||
char* to = c->flags & CLIENT_MASTER? "master": "slave";
|
||||
char* from = c->flags & CLIENT_MASTER? "slave": "master";
|
||||
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
|
||||
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
|
||||
"to its %s: '%s' after processing the command "
|
||||
"'%s'", from, to, s, cmdname);
|
||||
}
|
||||
}
|
||||
|
||||
void addReplyError(client *c, const char *err) {
|
||||
@@ -608,7 +589,6 @@ void addReplyBulkLongLong(client *c, long long ll) {
|
||||
* destination client. */
|
||||
void copyClientOutputBuffer(client *dst, client *src) {
|
||||
listRelease(dst->reply);
|
||||
dst->sentlen = 0;
|
||||
dst->reply = listDup(src->reply);
|
||||
memcpy(dst->buf,src->buf,src->bufpos);
|
||||
dst->bufpos = src->bufpos;
|
||||
@@ -763,10 +743,9 @@ void unlinkClient(client *c) {
|
||||
* fd is already set to -1. */
|
||||
if (c->fd != -1) {
|
||||
/* Remove from the list of active clients. */
|
||||
if (c->client_list_node) {
|
||||
listDelNode(server.clients,c->client_list_node);
|
||||
c->client_list_node = NULL;
|
||||
}
|
||||
ln = listSearchKey(server.clients,c);
|
||||
serverAssert(ln != NULL);
|
||||
listDelNode(server.clients,ln);
|
||||
|
||||
/* Unregister async I/O handlers and close the socket. */
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
||||
@@ -960,15 +939,10 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
* scenario think about 'KEYS *' against the loopback interface).
|
||||
*
|
||||
* However if we are over the maxmemory limit we ignore that and
|
||||
* just deliver as much data as it is possible to deliver.
|
||||
*
|
||||
* Moreover, we also send as much as possible if the client is
|
||||
* a slave (otherwise, on high-speed traffic, the replication
|
||||
* buffer will grow indefinitely) */
|
||||
* just deliver as much data as it is possible to deliver. */
|
||||
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
|
||||
(server.maxmemory == 0 ||
|
||||
zmalloc_used_memory() < server.maxmemory) &&
|
||||
!(c->flags & CLIENT_SLAVE)) break;
|
||||
zmalloc_used_memory() < server.maxmemory)) break;
|
||||
}
|
||||
server.stat_net_output_bytes += totwritten;
|
||||
if (nwritten == -1) {
|
||||
@@ -1026,25 +1000,13 @@ int handleClientsWithPendingWrites(void) {
|
||||
/* Try to write buffers to the client socket. */
|
||||
if (writeToClient(c->fd,c,0) == C_ERR) continue;
|
||||
|
||||
/* If after the synchronous writes above we still have data to
|
||||
* output to the client, we need to install the writable handler. */
|
||||
if (clientHasPendingReplies(c)) {
|
||||
int ae_flags = AE_WRITABLE;
|
||||
/* For the fsync=always policy, we want that a given FD is never
|
||||
* served for reading and writing in the same event loop iteration,
|
||||
* so that in the middle of receiving the query, and serving it
|
||||
* to the client, we'll call beforeSleep() that will do the
|
||||
* actual fsync of AOF to disk. AE_BARRIER ensures that. */
|
||||
if (server.aof_state == AOF_ON &&
|
||||
server.aof_fsync == AOF_FSYNC_ALWAYS)
|
||||
{
|
||||
ae_flags |= AE_BARRIER;
|
||||
}
|
||||
if (aeCreateFileEvent(server.el, c->fd, ae_flags,
|
||||
/* If there is nothing left, do nothing. Otherwise install
|
||||
* the write handler. */
|
||||
if (clientHasPendingReplies(c) &&
|
||||
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
|
||||
sendReplyToClient, c) == AE_ERR)
|
||||
{
|
||||
freeClientAsync(c);
|
||||
}
|
||||
{
|
||||
freeClientAsync(c);
|
||||
}
|
||||
}
|
||||
return processed;
|
||||
@@ -1083,7 +1045,7 @@ void resetClient(client *c) {
|
||||
* with the error and close the connection. */
|
||||
int processInlineBuffer(client *c) {
|
||||
char *newline;
|
||||
int argc, j, linefeed_chars = 1;
|
||||
int argc, j;
|
||||
sds *argv, aux;
|
||||
size_t querylen;
|
||||
|
||||
@@ -1101,7 +1063,7 @@ int processInlineBuffer(client *c) {
|
||||
|
||||
/* Handle the \r\n case. */
|
||||
if (newline && newline != c->querybuf && *(newline-1) == '\r')
|
||||
newline--, linefeed_chars++;
|
||||
newline--;
|
||||
|
||||
/* Split the input buffer up to the \r\n */
|
||||
querylen = newline-(c->querybuf);
|
||||
@@ -1121,7 +1083,7 @@ int processInlineBuffer(client *c) {
|
||||
c->repl_ack_time = server.unixtime;
|
||||
|
||||
/* Leave data after the first line of the query in the buffer */
|
||||
sdsrange(c->querybuf,querylen+linefeed_chars,-1);
|
||||
sdsrange(c->querybuf,querylen+2,-1);
|
||||
|
||||
/* Setup argv array on client structure */
|
||||
if (argc) {
|
||||
@@ -1145,7 +1107,7 @@ int processInlineBuffer(client *c) {
|
||||
/* Helper function. Trims query buffer to make the function that processes
|
||||
* multi bulk requests idempotent. */
|
||||
#define PROTO_DUMP_LEN 128
|
||||
static void setProtocolError(const char *errstr, client *c, long pos) {
|
||||
static void setProtocolError(const char *errstr, client *c, int pos) {
|
||||
if (server.verbosity <= LL_VERBOSE) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
|
||||
@@ -1186,8 +1148,7 @@ static void setProtocolError(const char *errstr, client *c, long pos) {
|
||||
* to be '*'. Otherwise for inline commands processInlineBuffer() is called. */
|
||||
int processMultibulkBuffer(client *c) {
|
||||
char *newline = NULL;
|
||||
long pos = 0;
|
||||
int ok;
|
||||
int pos = 0, ok;
|
||||
long long ll;
|
||||
|
||||
if (c->multibulklen == 0) {
|
||||
@@ -1259,7 +1220,7 @@ int processMultibulkBuffer(client *c) {
|
||||
}
|
||||
|
||||
ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll);
|
||||
if (!ok || ll < 0 || ll > server.proto_max_bulk_len) {
|
||||
if (!ok || ll < 0 || ll > 512*1024*1024) {
|
||||
addReplyError(c,"Protocol error: invalid bulk length");
|
||||
setProtocolError("invalid bulk length",c,pos);
|
||||
return C_ERR;
|
||||
@@ -1285,7 +1246,7 @@ int processMultibulkBuffer(client *c) {
|
||||
}
|
||||
|
||||
/* Read bulk argument */
|
||||
if (sdslen(c->querybuf)-pos < (size_t)(c->bulklen+2)) {
|
||||
if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) {
|
||||
/* Not enough data (+2 == trailing \r\n) */
|
||||
break;
|
||||
} else {
|
||||
@@ -1294,7 +1255,7 @@ int processMultibulkBuffer(client *c) {
|
||||
* just use the current sds string. */
|
||||
if (pos == 0 &&
|
||||
c->bulklen >= PROTO_MBULK_BIG_ARG &&
|
||||
sdslen(c->querybuf) == (size_t)(c->bulklen+2))
|
||||
(signed) sdslen(c->querybuf) == c->bulklen+2)
|
||||
{
|
||||
c->argv[c->argc++] = createObject(OBJ_STRING,c->querybuf);
|
||||
sdsIncrLen(c->querybuf,-2); /* remove CRLF */
|
||||
@@ -1405,7 +1366,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (c->reqtype == PROTO_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1
|
||||
&& c->bulklen >= PROTO_MBULK_BIG_ARG)
|
||||
{
|
||||
ssize_t remaining = (size_t)(c->bulklen+2)-sdslen(c->querybuf);
|
||||
int remaining = (unsigned)(c->bulklen+2)-sdslen(c->querybuf);
|
||||
|
||||
if (remaining < readlen) readlen = remaining;
|
||||
}
|
||||
@@ -1944,7 +1905,6 @@ int checkClientOutputBufferLimits(client *c) {
|
||||
* called from contexts where the client can't be freed safely, i.e. from the
|
||||
* lower level functions pushing data inside the client output buffers. */
|
||||
void asyncCloseClientOnOutputBufferLimitReached(client *c) {
|
||||
if (c->fd == -1) return; /* It is unsafe to free fake clients. */
|
||||
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
|
||||
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return;
|
||||
if (checkClientOutputBufferLimits(c)) {
|
||||
|
||||
+1
-7
@@ -98,12 +98,6 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
int len = -1;
|
||||
char buf[24];
|
||||
|
||||
/* If any modules are interested in events, notify the module system now.
|
||||
* This bypasses the notifications configuration, but the module engine
|
||||
* will only call event subscribers if the event type matches the types
|
||||
* they are interested in. */
|
||||
moduleNotifyKeyspaceEvent(type, event, key, dbid);
|
||||
|
||||
/* If notifications for this class of events are off, return ASAP. */
|
||||
if (!(server.notify_keyspace_events & type)) return;
|
||||
|
||||
@@ -121,7 +115,7 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
decrRefCount(chanobj);
|
||||
}
|
||||
|
||||
/* __keyevent@<db>__:<event> <key> notifications. */
|
||||
/* __keyevente@<db>__:<event> <key> notifications. */
|
||||
if (server.notify_keyspace_events & NOTIFY_KEYEVENT) {
|
||||
chan = sdsnewlen("__keyevent@",11);
|
||||
if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
|
||||
|
||||
+10
-29
@@ -145,7 +145,7 @@ robj *createStringObjectFromLongLong(long long value) {
|
||||
*
|
||||
* The 'humanfriendly' option is used for INCRBYFLOAT and HINCRBYFLOAT. */
|
||||
robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
char buf[256];
|
||||
int len = ld2string(buf,sizeof(buf),value,humanfriendly);
|
||||
return createStringObject(buf,len);
|
||||
}
|
||||
@@ -560,7 +560,7 @@ int getDoubleFromObject(const robj *o, double *target) {
|
||||
value = strtod(o->ptr, &eptr);
|
||||
if (sdslen(o->ptr) == 0 ||
|
||||
isspace(((const char*)o->ptr)[0]) ||
|
||||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
|
||||
eptr[0] != '\0' ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
@@ -602,7 +602,7 @@ int getLongDoubleFromObject(robj *o, long double *target) {
|
||||
value = strtold(o->ptr, &eptr);
|
||||
if (sdslen(o->ptr) == 0 ||
|
||||
isspace(((const char*)o->ptr)[0]) ||
|
||||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
|
||||
eptr[0] != '\0' ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
@@ -727,7 +727,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
elesize += sizeof(quicklistNode)+ziplistBlobLen(node->zl);
|
||||
samples++;
|
||||
} while ((node = node->next) && samples < sample_size);
|
||||
asize += (double)elesize/samples*ql->len;
|
||||
asize += (double)elesize/samples*listTypeLength(o);
|
||||
} else if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
asize = sizeof(*o)+ziplistBlobLen(o->ptr);
|
||||
} else {
|
||||
@@ -1012,25 +1012,11 @@ robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
}
|
||||
|
||||
/* Object command allows to inspect the internals of an Redis Object.
|
||||
* Usage: OBJECT <refcount|encoding|idletime|freq> <key> */
|
||||
* Usage: OBJECT <refcount|encoding|idletime> <key> */
|
||||
void objectCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
||||
void *blenp = addDeferredMultiBulkLength(c);
|
||||
int blen = 0;
|
||||
blen++; addReplyStatus(c,
|
||||
"OBJECT <subcommand> key. Subcommands:");
|
||||
blen++; addReplyStatus(c,
|
||||
"refcount -- Return the number of references of the value associated with the specified key.");
|
||||
blen++; addReplyStatus(c,
|
||||
"encoding -- Return the kind of internal representation used in order to store the value associated with a key.");
|
||||
blen++; addReplyStatus(c,
|
||||
"idletime -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.");
|
||||
blen++; addReplyStatus(c,
|
||||
"freq -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.");
|
||||
setDeferredMultiBulkLength(c,blenp,blen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,o->refcount);
|
||||
@@ -1049,18 +1035,13 @@ void objectCommand(client *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
|
||||
addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
|
||||
addReplyError(c,"An LRU maxmemory policy is selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
return;
|
||||
}
|
||||
/* LFUDecrAndReturn should be called
|
||||
* in case of the key has not been accessed for a long time,
|
||||
* because we update the access time only
|
||||
* when the key is read or overwritten. */
|
||||
addReplyLongLong(c,LFUDecrAndReturn(o));
|
||||
addReplyLongLong(c,o->lru&255);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try OBJECT help",
|
||||
(char *)c->argv[1]->ptr);
|
||||
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime|freq)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ REDIS_STATIC quicklistNode *quicklistCreateNode(void) {
|
||||
}
|
||||
|
||||
/* Return cached quicklist count */
|
||||
unsigned long quicklistCount(const quicklist *ql) { return ql->count; }
|
||||
unsigned int quicklistCount(const quicklist *ql) { return ql->count; }
|
||||
|
||||
/* Free entire quicklist. */
|
||||
void quicklistRelease(quicklist *quicklist) {
|
||||
|
||||
+3
-3
@@ -64,7 +64,7 @@ typedef struct quicklistLZF {
|
||||
char compressed[];
|
||||
} quicklistLZF;
|
||||
|
||||
/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
|
||||
/* quicklist is a 32 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
|
||||
@@ -74,7 +74,7 @@ typedef struct quicklist {
|
||||
quicklistNode *head;
|
||||
quicklistNode *tail;
|
||||
unsigned long count; /* total count of all entries in all ziplists */
|
||||
unsigned long len; /* number of quicklistNodes */
|
||||
unsigned int 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 */
|
||||
} quicklist;
|
||||
@@ -154,7 +154,7 @@ int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
|
||||
void *(*saver)(unsigned char *data, unsigned int sz));
|
||||
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
|
||||
unsigned int *sz, long long *slong);
|
||||
unsigned long quicklistCount(const quicklist *ql);
|
||||
unsigned int quicklistCount(const quicklist *ql);
|
||||
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
|
||||
size_t quicklistGetLzf(const quicklistNode *node, void **data);
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ static inline void raxStackFree(raxStack *ts) {
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Radix tree implementation
|
||||
* Radis tree implementation
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* Allocate a new non compressed node with the specified number of children.
|
||||
@@ -873,8 +873,7 @@ raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
|
||||
memmove(((char*)cp)-1,cp,(parent->size-taillen-1)*sizeof(raxNode**));
|
||||
|
||||
/* Move the remaining "tail" pointer at the right position as well. */
|
||||
size_t valuelen = (parent->iskey && !parent->isnull) ? sizeof(void*) : 0;
|
||||
memmove(((char*)c)-1,c+1,taillen*sizeof(raxNode**)+valuelen);
|
||||
memmove(((char*)c)-1,c+1,taillen*sizeof(raxNode**)+parent->iskey*sizeof(void*));
|
||||
|
||||
/* 4. Update size. */
|
||||
parent->size--;
|
||||
@@ -1093,34 +1092,26 @@ int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) {
|
||||
|
||||
/* This is the core of raxFree(): performs a depth-first scan of the
|
||||
* tree and releases all the nodes found. */
|
||||
void raxRecursiveFree(rax *rax, raxNode *n, void (*free_callback)(void*)) {
|
||||
void raxRecursiveFree(rax *rax, raxNode *n) {
|
||||
debugnode("free traversing",n);
|
||||
int numchildren = n->iscompr ? 1 : n->size;
|
||||
raxNode **cp = raxNodeLastChildPtr(n);
|
||||
while(numchildren--) {
|
||||
raxNode *child;
|
||||
memcpy(&child,cp,sizeof(child));
|
||||
raxRecursiveFree(rax,child,free_callback);
|
||||
raxRecursiveFree(rax,child);
|
||||
cp--;
|
||||
}
|
||||
debugnode("free depth-first",n);
|
||||
if (free_callback && n->iskey && !n->isnull)
|
||||
free_callback(raxGetData(n));
|
||||
rax_free(n);
|
||||
rax->numnodes--;
|
||||
}
|
||||
|
||||
/* Free a whole radix tree, calling the specified callback in order to
|
||||
* free the auxiliary data. */
|
||||
void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)) {
|
||||
raxRecursiveFree(rax,rax->head,free_callback);
|
||||
assert(rax->numnodes == 0);
|
||||
rax_free(rax);
|
||||
}
|
||||
|
||||
/* Free a whole radix tree. */
|
||||
void raxFree(rax *rax) {
|
||||
raxFreeWithCallback(rax,NULL);
|
||||
raxRecursiveFree(rax,rax->head);
|
||||
assert(rax->numnodes == 0);
|
||||
rax_free(rax);
|
||||
}
|
||||
|
||||
/* ------------------------------- Iterator --------------------------------- */
|
||||
@@ -1184,7 +1175,7 @@ void raxIteratorDelChars(raxIterator *it, size_t count) {
|
||||
* The function returns 1 on success or 0 on out of memory. */
|
||||
int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
if (it->flags & RAX_ITER_EOF) {
|
||||
return 1;
|
||||
return 0;
|
||||
} else if (it->flags & RAX_ITER_JUST_SEEKED) {
|
||||
it->flags &= ~RAX_ITER_JUST_SEEKED;
|
||||
return 1;
|
||||
@@ -1196,6 +1187,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
size_t orig_stack_items = it->stack.items;
|
||||
raxNode *orig_node = it->node;
|
||||
|
||||
/* Clear the EOF flag: it will be set again if the EOF condition
|
||||
* is still valid. */
|
||||
it->flags &= ~RAX_ITER_EOF;
|
||||
|
||||
while(1) {
|
||||
int children = it->node->iscompr ? 1 : it->node->size;
|
||||
if (!noup && children) {
|
||||
@@ -1296,7 +1291,7 @@ int raxSeekGreatest(raxIterator *it) {
|
||||
* effect to the one of raxIteratorPrevSte(). */
|
||||
int raxIteratorPrevStep(raxIterator *it, int noup) {
|
||||
if (it->flags & RAX_ITER_EOF) {
|
||||
return 1;
|
||||
return 0;
|
||||
} else if (it->flags & RAX_ITER_JUST_SEEKED) {
|
||||
it->flags &= ~RAX_ITER_JUST_SEEKED;
|
||||
return 1;
|
||||
@@ -1417,7 +1412,6 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
|
||||
it->node = it->rt->head;
|
||||
if (!raxSeekGreatest(it)) return 0;
|
||||
assert(it->node->iskey);
|
||||
it->data = raxGetData(it->node);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1436,7 +1430,6 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
|
||||
/* We found our node, since the key matches and we have an
|
||||
* "equal" condition. */
|
||||
if (!raxIteratorAddChars(it,ele,len)) return 0; /* OOM. */
|
||||
it->data = raxGetData(it->node);
|
||||
} else if (lt || gt) {
|
||||
/* Exact key not found or eq flag not set. We have to set as current
|
||||
* key the one represented by the node we stopped at, and perform
|
||||
@@ -1509,7 +1502,6 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
|
||||
* the previous sub-tree. */
|
||||
if (nodechar < keychar) {
|
||||
if (!raxSeekGreatest(it)) return 0;
|
||||
it->data = raxGetData(it->node);
|
||||
} else {
|
||||
if (!raxIteratorAddChars(it,it->node->data,it->node->size))
|
||||
return 0;
|
||||
@@ -1626,8 +1618,8 @@ int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key
|
||||
int eq = 0, lt = 0, gt = 0;
|
||||
|
||||
if (op[0] == '=' || op[1] == '=') eq = 1;
|
||||
if (op[0] == '>') gt = 1;
|
||||
else if (op[0] == '<') lt = 1;
|
||||
if (op[1] == '>') gt = 1;
|
||||
else if (op[1] == '<') lt = 1;
|
||||
else if (op[1] != '=') return 0; /* Syntax error. */
|
||||
|
||||
size_t minlen = key_len < iter->key_len ? key_len : iter->key_len;
|
||||
@@ -1655,19 +1647,6 @@ void raxStop(raxIterator *it) {
|
||||
raxStackFree(&it->stack);
|
||||
}
|
||||
|
||||
/* Return if the iterator is in an EOF state. This happens when raxSeek()
|
||||
* failed to seek an appropriate element, so that raxNext() or raxPrev()
|
||||
* will return zero, or when an EOF condition was reached while iterating
|
||||
* with raxNext() and raxPrev(). */
|
||||
int raxEOF(raxIterator *it) {
|
||||
return it->flags & RAX_ITER_EOF;
|
||||
}
|
||||
|
||||
/* Return the number of elements inside the radix tree. */
|
||||
uint64_t raxSize(rax *rax) {
|
||||
return rax->numele;
|
||||
}
|
||||
|
||||
/* ----------------------------- Introspection ------------------------------ */
|
||||
|
||||
/* This function is mostly used for debugging and learning purposes.
|
||||
|
||||
@@ -148,7 +148,6 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
|
||||
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old);
|
||||
void *raxFind(rax *rax, unsigned char *s, size_t len);
|
||||
void raxFree(rax *rax);
|
||||
void raxFreeWithCallback(rax *rax, void (*free_callback)(void*));
|
||||
void raxStart(raxIterator *it, rax *rt);
|
||||
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len);
|
||||
int raxNext(raxIterator *it);
|
||||
@@ -156,8 +155,6 @@ int raxPrev(raxIterator *it);
|
||||
int raxRandomWalk(raxIterator *it, size_t steps);
|
||||
int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key_len);
|
||||
void raxStop(raxIterator *it);
|
||||
int raxEOF(raxIterator *it);
|
||||
void raxShow(rax *rax);
|
||||
uint64_t raxSize(rax *rax);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -424,7 +424,7 @@ ssize_t rdbSaveLongLongAsStringObject(rio *rdb, long long value) {
|
||||
}
|
||||
|
||||
/* Like rdbSaveRawString() gets a Redis object instead. */
|
||||
ssize_t rdbSaveStringObject(rio *rdb, robj *obj) {
|
||||
int rdbSaveStringObject(rio *rdb, robj *obj) {
|
||||
/* Avoid to decode the object, then encode it again, if the
|
||||
* object is already integer encoded. */
|
||||
if (obj->encoding == OBJ_ENCODING_INT) {
|
||||
@@ -807,10 +807,13 @@ size_t rdbSavedObjectLen(robj *o) {
|
||||
* On error -1 is returned.
|
||||
* On success if the key was actually saved 1 is returned, otherwise 0
|
||||
* is returned (the key was already expired). */
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime)
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
|
||||
long long expiretime, long long now)
|
||||
{
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime < now) return 0;
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
|
||||
if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
|
||||
}
|
||||
@@ -823,25 +826,21 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime)
|
||||
}
|
||||
|
||||
/* Save an AUX field. */
|
||||
ssize_t rdbSaveAuxField(rio *rdb, void *key, size_t keylen, void *val, size_t vallen) {
|
||||
ssize_t ret, len = 0;
|
||||
if ((ret = rdbSaveType(rdb,RDB_OPCODE_AUX)) == -1) return -1;
|
||||
len += ret;
|
||||
if ((ret = rdbSaveRawString(rdb,key,keylen)) == -1) return -1;
|
||||
len += ret;
|
||||
if ((ret = rdbSaveRawString(rdb,val,vallen)) == -1) return -1;
|
||||
len += ret;
|
||||
return len;
|
||||
int rdbSaveAuxField(rio *rdb, void *key, size_t keylen, void *val, size_t vallen) {
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_AUX) == -1) return -1;
|
||||
if (rdbSaveRawString(rdb,key,keylen) == -1) return -1;
|
||||
if (rdbSaveRawString(rdb,val,vallen) == -1) return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Wrapper for rdbSaveAuxField() used when key/val length can be obtained
|
||||
* with strlen(). */
|
||||
ssize_t rdbSaveAuxFieldStrStr(rio *rdb, char *key, char *val) {
|
||||
int rdbSaveAuxFieldStrStr(rio *rdb, char *key, char *val) {
|
||||
return rdbSaveAuxField(rdb,key,strlen(key),val,strlen(val));
|
||||
}
|
||||
|
||||
/* Wrapper for strlen(key) + integer type (up to long long range). */
|
||||
ssize_t rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {
|
||||
int rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {
|
||||
char buf[LONG_STR_SIZE];
|
||||
int vlen = ll2string(buf,sizeof(buf),val);
|
||||
return rdbSaveAuxField(rdb,key,strlen(key),buf,vlen);
|
||||
@@ -884,6 +883,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
dictEntry *de;
|
||||
char magic[10];
|
||||
int j;
|
||||
long long now = mstime();
|
||||
uint64_t cksum;
|
||||
size_t processed = 0;
|
||||
|
||||
@@ -927,7 +927,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
|
||||
initStaticStringObject(key,keystr);
|
||||
expire = getExpire(db,&key);
|
||||
if (rdbSaveKeyValuePair(rdb,&key,o,expire) == -1) goto werr;
|
||||
if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr;
|
||||
|
||||
/* When this RDB is produced as part of an AOF rewrite, move
|
||||
* accumulated diff from parent to child while rewriting in
|
||||
@@ -943,20 +943,6 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
}
|
||||
di = NULL; /* So that we don't release it again on error. */
|
||||
|
||||
/* If we are storing the replication information on disk, persist
|
||||
* the script cache as well: on successful PSYNC after a restart, we need
|
||||
* to be able to process any EVALSHA inside the replication backlog the
|
||||
* master will send us. */
|
||||
if (rsi && dictSize(server.lua_scripts)) {
|
||||
di = dictGetIterator(server.lua_scripts);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *body = dictGetVal(de);
|
||||
if (rdbSaveAuxField(rdb,"lua",3,body->ptr,sdslen(body->ptr)) == -1)
|
||||
goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* EOF opcode */
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
|
||||
|
||||
@@ -1503,7 +1489,7 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
|
||||
/* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
|
||||
* otherwise C_ERR is returned and 'errno' is set accordingly. */
|
||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
|
||||
uint64_t dbid;
|
||||
int type, rdbver;
|
||||
redisDb *db = server.db+0;
|
||||
@@ -1603,13 +1589,6 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
}
|
||||
} else if (!strcasecmp(auxkey->ptr,"repl-offset")) {
|
||||
if (rsi) rsi->repl_offset = strtoll(auxval->ptr,NULL,10);
|
||||
} else if (!strcasecmp(auxkey->ptr,"lua")) {
|
||||
/* Load the script back in memory. */
|
||||
if (luaCreateFunction(NULL,server.lua,auxval) == NULL) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Can't load Lua script from RDB file! "
|
||||
"BODY: %s", auxval->ptr);
|
||||
}
|
||||
} else {
|
||||
/* We ignore fields we don't understand, as by AUX field
|
||||
* contract. */
|
||||
@@ -1631,7 +1610,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
* 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 && !loading_aof && expiretime != -1 && expiretime < now) {
|
||||
if (server.masterhost == NULL && expiretime != -1 && expiretime < now) {
|
||||
decrRefCount(key);
|
||||
decrRefCount(val);
|
||||
continue;
|
||||
@@ -1645,18 +1624,16 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
decrRefCount(key);
|
||||
}
|
||||
/* Verify the checksum if RDB version is >= 5 */
|
||||
if (rdbver >= 5) {
|
||||
if (rdbver >= 5 && server.rdb_checksum) {
|
||||
uint64_t cksum, expected = rdb->cksum;
|
||||
|
||||
if (rioRead(rdb,&cksum,8) == 0) goto eoferr;
|
||||
if (server.rdb_checksum) {
|
||||
memrev64ifbe(&cksum);
|
||||
if (cksum == 0) {
|
||||
serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
|
||||
} else if (cksum != expected) {
|
||||
serverLog(LL_WARNING,"Wrong RDB checksum. Aborting now.");
|
||||
rdbExitReportCorruptRDB("RDB CRC error");
|
||||
}
|
||||
memrev64ifbe(&cksum);
|
||||
if (cksum == 0) {
|
||||
serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
|
||||
} else if (cksum != expected) {
|
||||
serverLog(LL_WARNING,"Wrong RDB checksum. Aborting now.");
|
||||
rdbExitReportCorruptRDB("RDB CRC error");
|
||||
}
|
||||
}
|
||||
return C_OK;
|
||||
@@ -1682,7 +1659,7 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi) {
|
||||
if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
|
||||
startLoading(fp);
|
||||
rioInitWithFile(&rdb,fp);
|
||||
retval = rdbLoadRio(&rdb,rsi,0);
|
||||
retval = rdbLoadRio(&rdb,rsi);
|
||||
fclose(fp);
|
||||
stopLoading();
|
||||
return retval;
|
||||
@@ -2023,9 +2000,6 @@ void bgsaveCommand(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
|
||||
if (server.rdb_child_pid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
} else if (server.aof_child_pid != -1) {
|
||||
@@ -2038,7 +2012,7 @@ void bgsaveCommand(client *c) {
|
||||
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
||||
"possible.");
|
||||
}
|
||||
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
|
||||
} else if (rdbSaveBackground(server.rdb_filename,NULL) == C_OK) {
|
||||
addReplyStatus(c,"Background saving started");
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
@@ -2059,37 +2033,22 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
|
||||
*rsi = rsi_init;
|
||||
|
||||
/* If the instance is a master, we can populate the replication info
|
||||
* only when repl_backlog is not NULL. If the repl_backlog is NULL,
|
||||
* it means that the instance isn't in any replication chains. In this
|
||||
* scenario the replication info is useless, because when a slave
|
||||
* connects to us, the NULL repl_backlog will trigger a full
|
||||
* synchronization, at the same time we will use a new replid and clear
|
||||
* replid2. */
|
||||
if (!server.masterhost && server.repl_backlog) {
|
||||
/* Note that when server.slaveseldb is -1, it means that this master
|
||||
* didn't apply any write commands after a full synchronization.
|
||||
* So we can let repl_stream_db be 0, this allows a restarted slave
|
||||
* to reload replication ID/offset, it's safe because the next write
|
||||
* command must generate a SELECT statement. */
|
||||
rsi->repl_stream_db = server.slaveseldb == -1 ? 0 : server.slaveseldb;
|
||||
* in all the cases, even if sometimes in incomplete (but safe) form. */
|
||||
if (!server.masterhost) {
|
||||
if (server.repl_backlog) rsi->repl_stream_db = server.slaveseldb;
|
||||
/* Note that if repl_backlog is NULL, it means that histories
|
||||
* following from this point will trigger a full synchronization
|
||||
* generating a SELECT statement, so we can leave the currently
|
||||
* selected DB set to -1. This allows a restarted master to reload
|
||||
* its replication ID/offset when there are no connected slaves. */
|
||||
return rsi;
|
||||
}
|
||||
|
||||
/* If the instance is a slave we need a connected master
|
||||
* in order to fetch the currently selected DB. */
|
||||
/* If the instance is a slave we need a connected master in order to
|
||||
* fetch the currently selected DB. */
|
||||
if (server.master) {
|
||||
rsi->repl_stream_db = server.master->db->id;
|
||||
return rsi;
|
||||
}
|
||||
|
||||
/* If we have a cached master we can use it in order to populate the
|
||||
* replication selected DB info inside the RDB file: the slave can
|
||||
* increment the master_repl_offset only from data arriving from the
|
||||
* master, so if we are disconnected the offset in the cached master
|
||||
* is valid. */
|
||||
if (server.cached_master) {
|
||||
rsi->repl_stream_db = server.cached_master->db->id;
|
||||
return rsi;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -137,16 +137,16 @@ ssize_t rdbSaveObject(rio *rdb, robj *o);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime, long long now);
|
||||
robj *rdbLoadStringObject(rio *rdb);
|
||||
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
|
||||
int rdbSaveStringObject(rio *rdb, robj *obj);
|
||||
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
|
||||
void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr);
|
||||
int rdbSaveBinaryDoubleValue(rio *rdb, double val);
|
||||
int rdbLoadBinaryDoubleValue(rio *rdb, double *val);
|
||||
int rdbSaveBinaryFloatValue(rio *rdb, float val);
|
||||
int rdbLoadBinaryFloatValue(rio *rdb, float *val);
|
||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof);
|
||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi);
|
||||
rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -614,7 +614,7 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
|
||||
UNUSED(id);
|
||||
UNUSED(clientData);
|
||||
|
||||
if (config.liveclients == 0 && config.requests_finished != config.requests) {
|
||||
if (config.liveclients == 0) {
|
||||
fprintf(stderr,"All clients disconnected... aborting.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
|
||||
#define ERROR(...) { \
|
||||
char __buf[1024]; \
|
||||
snprintf(__buf, sizeof(__buf), __VA_ARGS__); \
|
||||
snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
|
||||
sprintf(__buf, __VA_ARGS__); \
|
||||
sprintf(error, "0x%16llx: %s", (long long)epos, __buf); \
|
||||
}
|
||||
|
||||
static char error[1024];
|
||||
|
||||
@@ -193,12 +193,12 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
buf[9] = '\0';
|
||||
if (memcmp(buf,"REDIS",5) != 0) {
|
||||
rdbCheckError("Wrong signature trying to load DB from file");
|
||||
goto err;
|
||||
return 1;
|
||||
}
|
||||
rdbver = atoi(buf+5);
|
||||
if (rdbver < 1 || rdbver > RDB_VERSION) {
|
||||
rdbCheckError("Can't handle RDB format version %d",rdbver);
|
||||
goto err;
|
||||
return 1;
|
||||
}
|
||||
|
||||
startLoading(fp);
|
||||
@@ -270,7 +270,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
} else {
|
||||
if (!rdbIsObjectType(type)) {
|
||||
rdbCheckError("Invalid object type: %d", type);
|
||||
goto err;
|
||||
return 1;
|
||||
}
|
||||
rdbstate.key_type = type;
|
||||
}
|
||||
@@ -307,7 +307,6 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbCheckInfo("RDB file was saved with checksum disabled: no check performed.");
|
||||
} else if (cksum != expected) {
|
||||
rdbCheckError("RDB CRC error");
|
||||
goto err;
|
||||
} else {
|
||||
rdbCheckInfo("Checksum OK");
|
||||
}
|
||||
@@ -322,8 +321,6 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
} else {
|
||||
rdbCheckError("Unexpected EOF reading RDB file");
|
||||
}
|
||||
err:
|
||||
if (closefile) fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
+33
-284
@@ -107,7 +107,6 @@ static struct config {
|
||||
char *pattern;
|
||||
char *rdb_filename;
|
||||
int bigkeys;
|
||||
int hotkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
char *auth;
|
||||
int output; /* output mode, see OUTPUT_* defines */
|
||||
@@ -152,25 +151,20 @@ static long long mstime(void) {
|
||||
}
|
||||
|
||||
static void cliRefreshPrompt(void) {
|
||||
int len;
|
||||
|
||||
if (config.eval_ldb) return;
|
||||
|
||||
sds prompt = sdsempty();
|
||||
if (config.hostsocket != NULL) {
|
||||
prompt = sdscatfmt(prompt,"redis %s",config.hostsocket);
|
||||
} else {
|
||||
char addr[256];
|
||||
anetFormatAddr(addr, sizeof(addr), config.hostip, config.hostport);
|
||||
prompt = sdscatlen(prompt,addr,strlen(addr));
|
||||
}
|
||||
|
||||
if (config.hostsocket != NULL)
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
|
||||
config.hostsocket);
|
||||
else
|
||||
len = anetFormatAddr(config.prompt, sizeof(config.prompt),
|
||||
config.hostip, config.hostport);
|
||||
/* Add [dbnum] if needed */
|
||||
if (config.dbnum != 0)
|
||||
prompt = sdscatfmt(prompt,"[%i]",config.dbnum);
|
||||
|
||||
/* Copy the prompt in the static buffer. */
|
||||
prompt = sdscatlen(prompt,"> ",2);
|
||||
snprintf(config.prompt,sizeof(config.prompt),"%s",prompt);
|
||||
sdsfree(prompt);
|
||||
len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]",
|
||||
config.dbnum);
|
||||
snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
|
||||
}
|
||||
|
||||
/* Return the name of the dotfile for the specified 'dotfilename'.
|
||||
@@ -204,92 +198,6 @@ static sds getDotfilePath(char *envoverride, char *dotfilename) {
|
||||
return dotPath;
|
||||
}
|
||||
|
||||
/* URL-style percent decoding. */
|
||||
#define isHexChar(c) (isdigit(c) || (c >= 'a' && c <= 'f'))
|
||||
#define decodeHexChar(c) (isdigit(c) ? c - '0' : c - 'a' + 10)
|
||||
#define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l))
|
||||
|
||||
static sds percentDecode(const char *pe, size_t len) {
|
||||
const char *end = pe + len;
|
||||
sds ret = sdsempty();
|
||||
const char *curr = pe;
|
||||
|
||||
while (curr < end) {
|
||||
if (*curr == '%') {
|
||||
if ((end - curr) < 2) {
|
||||
fprintf(stderr, "Incomplete URI encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char h = tolower(*(++curr));
|
||||
char l = tolower(*(++curr));
|
||||
if (!isHexChar(h) || !isHexChar(l)) {
|
||||
fprintf(stderr, "Illegal character in URI encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
char c = decodeHex(h, l);
|
||||
ret = sdscatlen(ret, &c, 1);
|
||||
curr++;
|
||||
} else {
|
||||
ret = sdscatlen(ret, curr++, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parse a URI and extract the server connection information.
|
||||
* URI scheme is based on the the provisional specification[1] excluding support
|
||||
* for query parameters. Valid URIs are:
|
||||
* scheme: "redis://"
|
||||
* authority: [<username> ":"] <password> "@"] [<hostname> [":" <port>]]
|
||||
* path: ["/" [<db>]]
|
||||
*
|
||||
* [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */
|
||||
static void parseRedisUri(const char *uri) {
|
||||
|
||||
const char *scheme = "redis://";
|
||||
const char *curr = uri;
|
||||
const char *end = uri + strlen(uri);
|
||||
const char *userinfo, *username, *port, *host, *path;
|
||||
|
||||
/* URI must start with a valid scheme. */
|
||||
if (strncasecmp(scheme, curr, strlen(scheme))) {
|
||||
fprintf(stderr,"Invalid URI scheme\n");
|
||||
exit(1);
|
||||
}
|
||||
curr += strlen(scheme);
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract user info. */
|
||||
if ((userinfo = strchr(curr,'@'))) {
|
||||
if ((username = strchr(curr, ':')) && username < userinfo) {
|
||||
/* If provided, username is ignored. */
|
||||
curr = username + 1;
|
||||
}
|
||||
|
||||
config.auth = percentDecode(curr, userinfo - curr);
|
||||
curr = userinfo + 1;
|
||||
}
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract host and port. */
|
||||
path = strchr(curr, '/');
|
||||
if (*curr != '/') {
|
||||
host = path ? path - 1 : end;
|
||||
if ((port = strchr(curr, ':'))) {
|
||||
config.hostport = atoi(port + 1);
|
||||
host = port - 1;
|
||||
}
|
||||
config.hostip = sdsnewlen(curr, host - curr + 1);
|
||||
}
|
||||
curr = path ? path + 1 : end;
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract database number. */
|
||||
config.dbnum = atoi(curr);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Help functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -716,7 +624,7 @@ int isColorTerm(void) {
|
||||
return t != NULL && strstr(t,"xterm") != NULL;
|
||||
}
|
||||
|
||||
/* Helper function for sdsCatColorizedLdbReply() appending colorize strings
|
||||
/* Helpe function for sdsCatColorizedLdbReply() appending colorize strings
|
||||
* to an SDS string. */
|
||||
sds sdscatcolor(sds o, char *s, size_t len, char *color) {
|
||||
if (!isColorTerm()) return sdscatlen(o,s,len);
|
||||
@@ -922,7 +830,7 @@ static int cliReadReply(int output_raw_strings) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
char *command = argv[0];
|
||||
size_t *argvlen;
|
||||
int j, output_raw;
|
||||
@@ -985,7 +893,7 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
for (j = 0; j < argc; j++)
|
||||
argvlen[j] = sdslen(argv[j]);
|
||||
|
||||
while(repeat-- > 0) {
|
||||
while(repeat--) {
|
||||
redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
|
||||
while (config.monitor_mode) {
|
||||
if (cliReadReply(output_raw) != REDIS_OK) exit(1);
|
||||
@@ -1093,10 +1001,7 @@ static int parseOptions(int argc, char **argv) {
|
||||
} else if (!strcmp(argv[i],"-n") && !lastarg) {
|
||||
config.dbnum = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"-a") && !lastarg) {
|
||||
fputs("Warning: Using a password with '-a' option on the command line interface may not be safe.\n", stderr);
|
||||
config.auth = argv[++i];
|
||||
} else if (!strcmp(argv[i],"-u") && !lastarg) {
|
||||
parseRedisUri(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--raw")) {
|
||||
config.output = OUTPUT_RAW;
|
||||
} else if (!strcmp(argv[i],"--no-raw")) {
|
||||
@@ -1136,8 +1041,6 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.pipe_timeout = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--bigkeys")) {
|
||||
config.bigkeys = 1;
|
||||
} else if (!strcmp(argv[i],"--hotkeys")) {
|
||||
config.hotkeys = 1;
|
||||
} else if (!strcmp(argv[i],"--eval") && !lastarg) {
|
||||
config.eval = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--ldb")) {
|
||||
@@ -1206,7 +1109,6 @@ static void usage(void) {
|
||||
" -p <port> Server port (default: 6379).\n"
|
||||
" -s <socket> Server socket (overrides hostname and port).\n"
|
||||
" -a <password> Password to use when connecting to the server.\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"
|
||||
" It is possible to specify sub-second times like -i 0.1.\n"
|
||||
@@ -1238,8 +1140,6 @@ static void usage(void) {
|
||||
" no reply is received within <n> seconds.\n"
|
||||
" Default timeout: %d. Use 0 to wait forever.\n"
|
||||
" --bigkeys Sample Redis keys looking for big keys.\n"
|
||||
" --hotkeys Sample Redis keys looking for hot keys.\n"
|
||||
" only works when maxmemory-policy is *lfu.\n"
|
||||
" --scan List all keys using the SCAN command.\n"
|
||||
" --pattern <pat> Useful with --scan to specify a SCAN pattern.\n"
|
||||
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
|
||||
@@ -1392,9 +1292,8 @@ static void repl(void) {
|
||||
/* Only use history and load the rc file when stdin is a tty. */
|
||||
if (isatty(fileno(stdin))) {
|
||||
historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV,REDIS_CLI_HISTFILE_DEFAULT);
|
||||
//keep in-memory history always regardless if history file can be determined
|
||||
history = 1;
|
||||
if (historyfile != NULL) {
|
||||
history = 1;
|
||||
linenoiseHistoryLoad(historyfile);
|
||||
}
|
||||
cliLoadPreferences();
|
||||
@@ -1403,35 +1302,9 @@ static void repl(void) {
|
||||
cliRefreshPrompt();
|
||||
while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
long repeat = 1;
|
||||
int skipargs = 0;
|
||||
char *endptr = NULL;
|
||||
|
||||
argv = cliSplitArgs(line,&argc);
|
||||
|
||||
/* check if we have a repeat command option and
|
||||
* need to skip the first arg */
|
||||
if (argv && argc > 0) {
|
||||
errno = 0;
|
||||
repeat = strtol(argv[0], &endptr, 10);
|
||||
if (argc > 1 && *endptr == '\0') {
|
||||
if (errno == ERANGE || errno == EINVAL || repeat <= 0) {
|
||||
fputs("Invalid redis-cli repeat command option value.\n", stdout);
|
||||
sdsfreesplitres(argv, argc);
|
||||
linenoiseFree(line);
|
||||
continue;
|
||||
}
|
||||
skipargs = 1;
|
||||
} else {
|
||||
repeat = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Won't save auth command in history file */
|
||||
if (!(argv && argc > 0 && !strcasecmp(argv[0+skipargs], "auth"))) {
|
||||
if (history) linenoiseHistoryAdd(line);
|
||||
if (historyfile) linenoiseHistorySave(historyfile);
|
||||
}
|
||||
if (history) linenoiseHistoryAdd(line);
|
||||
if (historyfile) linenoiseHistorySave(historyfile);
|
||||
|
||||
if (argv == NULL) {
|
||||
printf("Invalid argument(s)\n");
|
||||
@@ -1444,8 +1317,6 @@ static void repl(void) {
|
||||
exit(0);
|
||||
} else if (argv[0][0] == ':') {
|
||||
cliSetPreferences(argv,argc,1);
|
||||
sdsfreesplitres(argv,argc);
|
||||
linenoiseFree(line);
|
||||
continue;
|
||||
} else if (strcasecmp(argv[0],"restart") == 0) {
|
||||
if (config.eval) {
|
||||
@@ -1465,6 +1336,15 @@ static void repl(void) {
|
||||
linenoiseClearScreen();
|
||||
} else {
|
||||
long long start_time = mstime(), elapsed;
|
||||
int repeat, skipargs = 0;
|
||||
char *endptr;
|
||||
|
||||
repeat = strtol(argv[0], &endptr, 10);
|
||||
if (argc > 1 && *endptr == '\0' && repeat) {
|
||||
skipargs = 1;
|
||||
} else {
|
||||
repeat = 1;
|
||||
}
|
||||
|
||||
issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
|
||||
|
||||
@@ -1926,6 +1806,7 @@ static void getRDB(void) {
|
||||
}
|
||||
close(s); /* Close the file descriptor ASAP as fsync() may take time. */
|
||||
fsync(fd);
|
||||
close(fd);
|
||||
fprintf(stderr,"Transfer finished with success.\n");
|
||||
exit(0);
|
||||
}
|
||||
@@ -2099,9 +1980,7 @@ static void pipeMode(void) {
|
||||
#define TYPE_SET 2
|
||||
#define TYPE_HASH 3
|
||||
#define TYPE_ZSET 4
|
||||
#define TYPE_STREAM 5
|
||||
#define TYPE_NONE 6
|
||||
#define TYPE_COUNT 7
|
||||
#define TYPE_NONE 5
|
||||
|
||||
static redisReply *sendScan(unsigned long long *it) {
|
||||
redisReply *reply = redisCommand(context, "SCAN %llu", *it);
|
||||
@@ -2245,11 +2124,11 @@ static void getKeySizes(redisReply *keys, int *types,
|
||||
}
|
||||
|
||||
static void findBigKeys(void) {
|
||||
unsigned long long biggest[TYPE_COUNT] = {0}, counts[TYPE_COUNT] = {0}, totalsize[TYPE_COUNT] = {0};
|
||||
unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0};
|
||||
unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
|
||||
sds maxkeys[TYPE_COUNT] = {0};
|
||||
char *typename[] = {"string","list","set","hash","zset","stream","none"};
|
||||
char *typeunit[] = {"bytes","items","members","fields","members","entries",""};
|
||||
sds maxkeys[5] = {0};
|
||||
char *typename[] = {"string","list","set","hash","zset"};
|
||||
char *typeunit[] = {"bytes","items","members","fields","members"};
|
||||
redisReply *reply, *keys;
|
||||
unsigned int arrsize=0, i;
|
||||
int type, *types=NULL;
|
||||
@@ -2375,129 +2254,6 @@ static void findBigKeys(void) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void getKeyFreqs(redisReply *keys, unsigned long long *freqs) {
|
||||
redisReply *reply;
|
||||
unsigned int i;
|
||||
|
||||
/* Pipeline OBJECT freq commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
redisAppendCommand(context, "OBJECT freq %s", keys->element[i]->str);
|
||||
}
|
||||
|
||||
/* Retrieve freqs */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
|
||||
fprintf(stderr, "Error getting freq for key '%s' (%d: %s)\n",
|
||||
keys->element[i]->str, context->err, context->errstr);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_INTEGER) {
|
||||
if(reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "Error: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else {
|
||||
fprintf(stderr, "Warning: OBJECT freq on '%s' failed (may have been deleted)\n", keys->element[i]->str);
|
||||
freqs[i] = 0;
|
||||
}
|
||||
} else {
|
||||
freqs[i] = reply->integer;
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
#define HOTKEYS_SAMPLE 16
|
||||
static void findHotKeys(void) {
|
||||
redisReply *keys, *reply;
|
||||
unsigned long long counters[HOTKEYS_SAMPLE] = {0};
|
||||
sds hotkeys[HOTKEYS_SAMPLE] = {NULL};
|
||||
unsigned long long sampled = 0, total_keys, *freqs = NULL, it = 0;
|
||||
unsigned int arrsize = 0, i, k;
|
||||
double pct;
|
||||
|
||||
/* Total keys pre scanning */
|
||||
total_keys = getDbSize();
|
||||
|
||||
/* Status message */
|
||||
printf("\n# Scanning the entire keyspace to find hot keys as well as\n");
|
||||
printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
|
||||
printf("# per 100 SCAN commands (not usually needed).\n\n");
|
||||
|
||||
/* SCAN loop */
|
||||
do {
|
||||
/* Calculate approximate percentage completion */
|
||||
pct = 100 * (double)sampled/total_keys;
|
||||
|
||||
/* Grab some keys and point to the keys array */
|
||||
reply = sendScan(&it);
|
||||
keys = reply->element[1];
|
||||
|
||||
/* Reallocate our freqs array if we need to */
|
||||
if(keys->elements > arrsize) {
|
||||
freqs = zrealloc(freqs, sizeof(unsigned long long)*keys->elements);
|
||||
|
||||
if(!freqs) {
|
||||
fprintf(stderr, "Failed to allocate storage for keys!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
arrsize = keys->elements;
|
||||
}
|
||||
|
||||
getKeyFreqs(keys, freqs);
|
||||
|
||||
/* Now update our stats */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
sampled++;
|
||||
/* Update overall progress */
|
||||
if(sampled % 1000000 == 0) {
|
||||
printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
|
||||
}
|
||||
|
||||
/* Use eviction pool here */
|
||||
k = 0;
|
||||
while (k < HOTKEYS_SAMPLE && freqs[i] > counters[k]) k++;
|
||||
if (k == 0) continue;
|
||||
k--;
|
||||
if (k == 0 || counters[k] == 0) {
|
||||
sdsfree(hotkeys[k]);
|
||||
} else {
|
||||
sdsfree(hotkeys[0]);
|
||||
memmove(counters,counters+1,sizeof(counters[0])*k);
|
||||
memmove(hotkeys,hotkeys+1,sizeof(hotkeys[0])*k);
|
||||
}
|
||||
counters[k] = freqs[i];
|
||||
hotkeys[k] = sdsnew(keys->element[i]->str);
|
||||
printf(
|
||||
"[%05.2f%%] Hot key '%s' found so far with counter %llu\n",
|
||||
pct, keys->element[i]->str, freqs[i]);
|
||||
}
|
||||
|
||||
/* Sleep if we've been directed to do so */
|
||||
if(sampled && (sampled %100) == 0 && config.interval) {
|
||||
usleep(config.interval);
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
} while(it != 0);
|
||||
|
||||
if (freqs) zfree(freqs);
|
||||
|
||||
/* We're done */
|
||||
printf("\n-------- summary -------\n\n");
|
||||
|
||||
printf("Sampled %llu keys in the keyspace!\n", sampled);
|
||||
|
||||
for (i=1; i<= HOTKEYS_SAMPLE; i++) {
|
||||
k = HOTKEYS_SAMPLE - i;
|
||||
if(counters[k]>0) {
|
||||
printf("hot key found with counter: %llu\tkeyname: %s\n", counters[k], hotkeys[k]);
|
||||
sdsfree(hotkeys[k]);
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Stats mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -2608,7 +2364,7 @@ static void statMode(void) {
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf("%-8s",buf);
|
||||
|
||||
/* Requests */
|
||||
/* Requets */
|
||||
aux = getLongInfoField(reply->str,"total_commands_processed");
|
||||
sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
|
||||
printf("%-19s",buf);
|
||||
@@ -2875,7 +2631,6 @@ int main(int argc, char **argv) {
|
||||
config.pipe_mode = 0;
|
||||
config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
|
||||
config.bigkeys = 0;
|
||||
config.hotkeys = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.eval = NULL;
|
||||
@@ -2936,12 +2691,6 @@ int main(int argc, char **argv) {
|
||||
findBigKeys();
|
||||
}
|
||||
|
||||
/* Find hot keys */
|
||||
if (config.hotkeys) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
findHotKeys();
|
||||
}
|
||||
|
||||
/* Stat mode */
|
||||
if (config.stat_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
|
||||
+1
-131
@@ -701,12 +701,7 @@ class RedisTrib
|
||||
|
||||
masters.each{|m| puts m}
|
||||
|
||||
# Rotating the list sometimes helps to get better initial
|
||||
# anti-affinity before the optimizer runs.
|
||||
interleaved.push interleaved.shift
|
||||
|
||||
# Alloc slots on masters. After interleaving to get just the first N
|
||||
# should be optimal. With slaves is more complex, see later...
|
||||
# Alloc slots on masters
|
||||
slots_per_node = ClusterHashSlots.to_f / masters_count
|
||||
first = 0
|
||||
cursor = 0.0
|
||||
@@ -774,131 +769,6 @@ class RedisTrib
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
optimize_anti_affinity
|
||||
end
|
||||
|
||||
def optimize_anti_affinity
|
||||
score,aux = get_anti_affinity_score
|
||||
return if score == 0
|
||||
|
||||
xputs ">>> Trying to optimize slaves allocation for anti-affinity"
|
||||
|
||||
maxiter = 500*@nodes.length # Effort is proportional to cluster size...
|
||||
while maxiter > 0
|
||||
score,offenders = get_anti_affinity_score
|
||||
break if score == 0 # Optimal anti affinity reached
|
||||
|
||||
# We'll try to randomly swap a slave's assigned master causing
|
||||
# an affinity problem with another random slave, to see if we
|
||||
# can improve the affinity.
|
||||
first = offenders.shuffle.first
|
||||
nodes = @nodes.select{|n| n != first && n.info[:replicate]}
|
||||
break if nodes.length == 0
|
||||
second = nodes.shuffle.first
|
||||
|
||||
first_master = first.info[:replicate]
|
||||
second_master = second.info[:replicate]
|
||||
first.set_as_replica(second_master)
|
||||
second.set_as_replica(first_master)
|
||||
|
||||
new_score,aux = get_anti_affinity_score
|
||||
# If the change actually makes thing worse, revert. Otherwise
|
||||
# leave as it is becuase the best solution may need a few
|
||||
# combined swaps.
|
||||
if new_score > score
|
||||
first.set_as_replica(first_master)
|
||||
second.set_as_replica(second_master)
|
||||
end
|
||||
|
||||
maxiter -= 1
|
||||
end
|
||||
|
||||
score,aux = get_anti_affinity_score
|
||||
if score == 0
|
||||
xputs "[OK] Perfect anti-affinity obtained!"
|
||||
elsif score >= 10000
|
||||
puts "[WARNING] Some slaves are in the same host as their master"
|
||||
else
|
||||
puts "[WARNING] Some slaves of the same master are in the same host"
|
||||
end
|
||||
end
|
||||
|
||||
# Return the anti-affinity score, which is a measure of the amount of
|
||||
# violations of anti-affinity in the current cluster layout, that is, how
|
||||
# badly the masters and slaves are distributed in the different IP
|
||||
# addresses so that slaves of the same master are not in the master
|
||||
# host and are also in different hosts.
|
||||
#
|
||||
# The score is calculated as follows:
|
||||
#
|
||||
# SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
|
||||
# SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
|
||||
# of the same master.
|
||||
# FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
|
||||
#
|
||||
# So a greater score means a worse anti-affinity level, while zero
|
||||
# means perfect anti-affinity.
|
||||
#
|
||||
# The anti affinity optimizator will try to get a score as low as
|
||||
# possible. Since we do not want to sacrifice the fact that slaves should
|
||||
# not be in the same host as the master, we assign 10000 times the score
|
||||
# to this violation, so that we'll optimize for the second factor only
|
||||
# if it does not impact the first one.
|
||||
#
|
||||
# The function returns two things: the above score, and the list of
|
||||
# offending slaves, so that the optimizer can try changing the
|
||||
# configuration of the slaves violating the anti-affinity goals.
|
||||
def get_anti_affinity_score
|
||||
score = 0
|
||||
offending = [] # List of offending slaves to return to the caller
|
||||
|
||||
# First, split nodes by host
|
||||
host_to_node = {}
|
||||
@nodes.each{|n|
|
||||
host = n.info[:host]
|
||||
host_to_node[host] = [] if host_to_node[host] == nil
|
||||
host_to_node[host] << n
|
||||
}
|
||||
|
||||
# Then, for each set of nodes in the same host, split by
|
||||
# related nodes (masters and slaves which are involved in
|
||||
# replication of each other)
|
||||
host_to_node.each{|host,nodes|
|
||||
related = {}
|
||||
nodes.each{|n|
|
||||
if !n.info[:replicate]
|
||||
name = n.info[:name]
|
||||
related[name] = [] if related[name] == nil
|
||||
related[name] << :m
|
||||
else
|
||||
name = n.info[:replicate]
|
||||
related[name] = [] if related[name] == nil
|
||||
related[name] << :s
|
||||
end
|
||||
}
|
||||
|
||||
# Now it's trivial to check, for each related group having the
|
||||
# same host, what is their local score.
|
||||
related.each{|id,types|
|
||||
next if types.length < 2
|
||||
types.sort! # Make sure :m if the first if any
|
||||
if types[0] == :m
|
||||
score += 10000 * (types.length-1)
|
||||
else
|
||||
score += 1 * types.length
|
||||
end
|
||||
|
||||
# Populate the list of offending nodes
|
||||
@nodes.each{|n|
|
||||
if n.info[:replicate] == id &&
|
||||
n.info[:host] == host
|
||||
offending << n
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
return score,offending
|
||||
end
|
||||
|
||||
def flush_nodes_config
|
||||
|
||||
+1
-22
@@ -82,17 +82,6 @@
|
||||
#define REDISMODULE_CTX_FLAGS_EVICT 0x0200
|
||||
|
||||
|
||||
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
|
||||
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
|
||||
#define REDISMODULE_NOTIFY_LIST (1<<4) /* l */
|
||||
#define REDISMODULE_NOTIFY_SET (1<<5) /* s */
|
||||
#define REDISMODULE_NOTIFY_HASH (1<<6) /* h */
|
||||
#define REDISMODULE_NOTIFY_ZSET (1<<7) /* z */
|
||||
#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED) /* A */
|
||||
|
||||
|
||||
/* A special pointer that we can use between the core and the module to signal
|
||||
* field deletion, and that is impossible to be a valid pointer. */
|
||||
#define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1)
|
||||
@@ -123,7 +112,6 @@ typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
|
||||
typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
||||
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
||||
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
||||
@@ -155,8 +143,7 @@ void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
|
||||
int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);
|
||||
@@ -197,7 +184,6 @@ int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnlinkKey)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
|
||||
@@ -263,8 +249,6 @@ RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModu
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb);
|
||||
|
||||
#endif
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
@@ -279,7 +263,6 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(Strdup);
|
||||
REDISMODULE_GET_API(CreateCommand);
|
||||
REDISMODULE_GET_API(SetModuleAttribs);
|
||||
REDISMODULE_GET_API(IsModuleNameBusy);
|
||||
REDISMODULE_GET_API(WrongArity);
|
||||
REDISMODULE_GET_API(ReplyWithLongLong);
|
||||
REDISMODULE_GET_API(ReplyWithError);
|
||||
@@ -321,7 +304,6 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(Replicate);
|
||||
REDISMODULE_GET_API(ReplicateVerbatim);
|
||||
REDISMODULE_GET_API(DeleteKey);
|
||||
REDISMODULE_GET_API(UnlinkKey);
|
||||
REDISMODULE_GET_API(StringSet);
|
||||
REDISMODULE_GET_API(StringDMA);
|
||||
REDISMODULE_GET_API(StringTruncate);
|
||||
@@ -386,11 +368,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(IsBlockedTimeoutRequest);
|
||||
REDISMODULE_GET_API(GetBlockedClientPrivateData);
|
||||
REDISMODULE_GET_API(AbortBlock);
|
||||
REDISMODULE_GET_API(SubscribeToKeyspaceEvents);
|
||||
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
+4
-51
@@ -1244,18 +1244,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (eof_reached) {
|
||||
int aof_is_enabled = server.aof_state != AOF_OFF;
|
||||
|
||||
/* Ensure background save doesn't overwrite synced data */
|
||||
if (server.rdb_child_pid != -1) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Replica is about to load the RDB file received from the "
|
||||
"master, but there is a pending RDB child running. "
|
||||
"Killing process %ld and removing its temp file to avoid "
|
||||
"any race",
|
||||
(long) server.rdb_child_pid);
|
||||
kill(server.rdb_child_pid,SIGUSR1);
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
}
|
||||
|
||||
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
|
||||
serverLog(LL_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> SLAVE synchronization: %s", strerror(errno));
|
||||
cancelReplicationHandshake();
|
||||
@@ -1290,7 +1278,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
close(server.repl_transfer_fd);
|
||||
replicationCreateMasterClient(server.repl_transfer_s,rsi.repl_stream_db);
|
||||
server.repl_state = REPL_STATE_CONNECTED;
|
||||
server.repl_down_since = 0;
|
||||
/* After a full resynchroniziation we use the replication ID and
|
||||
* offset of the master. The secondary ID / offset are cleared since
|
||||
* we are starting a new history. */
|
||||
@@ -1343,8 +1330,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
cmd = sdscat(cmd,arg);
|
||||
}
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
va_end(ap);
|
||||
|
||||
|
||||
/* Transfer command to the server. */
|
||||
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
|
||||
== -1)
|
||||
@@ -1354,6 +1340,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
strerror(errno));
|
||||
}
|
||||
sdsfree(cmd);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Read the reply from the server. */
|
||||
@@ -1955,6 +1942,7 @@ void replicationSetMaster(char *ip, int port) {
|
||||
* our own parameters, to later PSYNC with the new master. */
|
||||
if (was_master) replicationCacheMasterUsingMyself();
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
server.repl_down_since = 0;
|
||||
}
|
||||
|
||||
/* Cancel replication, setting the instance as a master itself. */
|
||||
@@ -1982,12 +1970,6 @@ void replicationUnsetMaster(void) {
|
||||
* with PSYNC version 2, there is no need for full resync after a
|
||||
* master switch. */
|
||||
server.slaveseldb = -1;
|
||||
|
||||
/* Once we turn from slave to master, we consider the starting time without
|
||||
* slaves (that is used to count the replication backlog time to live) as
|
||||
* starting from now. Otherwise the backlog will be freed after a
|
||||
* failover if slaves do not connect immediately. */
|
||||
server.repl_no_slaves_since = server.unixtime;
|
||||
}
|
||||
|
||||
/* This function is called when the slave lose the connection with the
|
||||
@@ -2023,15 +2005,6 @@ void slaveofCommand(client *c) {
|
||||
} else {
|
||||
long port;
|
||||
|
||||
if (c->flags & CLIENT_SLAVE)
|
||||
{
|
||||
/* If a client is already a replica they cannot run this command,
|
||||
* because it involves flushing all replicas (including this
|
||||
* client) */
|
||||
addReplyError(c, "Command is not valid when client is a replica.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
@@ -2161,8 +2134,6 @@ void replicationCacheMaster(client *c) {
|
||||
server.master->read_reploff = server.master->reploff;
|
||||
if (c->flags & CLIENT_MULTI) discardTransaction(c);
|
||||
listEmpty(c->reply);
|
||||
c->sentlen = 0;
|
||||
c->reply_bytes = 0;
|
||||
c->bufpos = 0;
|
||||
resetClient(c);
|
||||
|
||||
@@ -2232,10 +2203,9 @@ void replicationResurrectCachedMaster(int newfd) {
|
||||
server.master->authenticated = 1;
|
||||
server.master->lastinteraction = server.unixtime;
|
||||
server.repl_state = REPL_STATE_CONNECTED;
|
||||
server.repl_down_since = 0;
|
||||
|
||||
/* Re-add to the list of clients. */
|
||||
linkClient(server.master);
|
||||
listAddNodeTail(server.clients,server.master);
|
||||
if (aeCreateFileEvent(server.el, newfd, AE_READABLE,
|
||||
readQueryFromClient, server.master)) {
|
||||
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
|
||||
@@ -2643,23 +2613,6 @@ void replicationCron(void) {
|
||||
time_t idle = server.unixtime - server.repl_no_slaves_since;
|
||||
|
||||
if (idle > server.repl_backlog_time_limit) {
|
||||
/* When we free the backlog, we always use a new
|
||||
* replication ID and clear the ID2. This is needed
|
||||
* because when there is no backlog, the master_repl_offset
|
||||
* is not updated, but we would still retain our replication
|
||||
* ID, leading to the following problem:
|
||||
*
|
||||
* 1. We are a master instance.
|
||||
* 2. Our slave is promoted to master. It's repl-id-2 will
|
||||
* be the same as our repl-id.
|
||||
* 3. We, yet as master, receive some updates, that will not
|
||||
* increment the master_repl_offset.
|
||||
* 4. Later we are turned into a slave, connecto to the new
|
||||
* master that will accept our PSYNC request by second
|
||||
* replication ID, but there will be data inconsistency
|
||||
* because we received writes. */
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
freeReplicationBacklog();
|
||||
serverLog(LL_NOTICE,
|
||||
"Replication backlog freed after %d seconds "
|
||||
|
||||
@@ -310,7 +310,7 @@ void rioSetAutoSync(rio *r, off_t bytes) {
|
||||
* generating the Redis protocol for the Append Only File. */
|
||||
|
||||
/* Write multi bulk count in the format: "*<count>\r\n". */
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, long count) {
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, int count) {
|
||||
char cbuf[128];
|
||||
int clen;
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ void rioInitWithFdset(rio *r, int *fds, int numfds);
|
||||
|
||||
void rioFreeFdset(rio *r);
|
||||
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, long count);
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, int count);
|
||||
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
|
||||
size_t rioWriteBulkLongLong(rio *r, long long l);
|
||||
size_t rioWriteBulkDouble(rio *r, double d);
|
||||
|
||||
+39
-59
@@ -358,13 +358,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
static size_t cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int inuse = 0; /* Recursive calls detection. */
|
||||
|
||||
/* Reflect MULTI state */
|
||||
if (server.lua_multi_emitted || (server.lua_caller->flags & CLIENT_MULTI)) {
|
||||
c->flags |= CLIENT_MULTI;
|
||||
} else {
|
||||
c->flags &= ~CLIENT_MULTI;
|
||||
}
|
||||
|
||||
/* By using Lua debug hooks it is possible to trigger a recursive call
|
||||
* to luaRedisGenericCommand(), which normally should never happen.
|
||||
* To make this function reentrant is futile and makes it slower, but
|
||||
@@ -542,7 +535,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
* a Lua script in the context of AOF and slaves. */
|
||||
if (server.lua_replicate_commands &&
|
||||
!server.lua_multi_emitted &&
|
||||
!(server.lua_caller->flags & CLIENT_MULTI) &&
|
||||
server.lua_write_dirty &&
|
||||
server.lua_repl != PROPAGATE_NONE)
|
||||
{
|
||||
@@ -1141,38 +1133,18 @@ int redis_math_randomseed (lua_State *L) {
|
||||
* EVAL and SCRIPT commands implementation
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Define a Lua function with the specified body.
|
||||
* The function name will be generated in the following form:
|
||||
/* Define a lua function with the specified function name and body.
|
||||
* The function name musts be a 42 characters long string, since all the
|
||||
* functions we defined in the Lua context are in the form:
|
||||
*
|
||||
* f_<hex sha1 sum>
|
||||
*
|
||||
* The function increments the reference count of the 'body' object as a
|
||||
* side effect of a successful call.
|
||||
*
|
||||
* On success a pointer to an SDS string representing the function SHA1 of the
|
||||
* just added function is returned (and will be valid until the next call
|
||||
* to scriptingReset() function), otherwise NULL is returned.
|
||||
*
|
||||
* The function handles the fact of being called with a script that already
|
||||
* exists, and in such a case, it behaves like in the success case.
|
||||
*
|
||||
* If 'c' is not NULL, on error the client is informed with an appropriate
|
||||
* error describing the nature of the problem and the Lua interpreter error. */
|
||||
sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
char funcname[43];
|
||||
dictEntry *de;
|
||||
|
||||
funcname[0] = 'f';
|
||||
funcname[1] = '_';
|
||||
sha1hex(funcname+2,body->ptr,sdslen(body->ptr));
|
||||
|
||||
sds sha = sdsnewlen(funcname+2,40);
|
||||
if ((de = dictFind(server.lua_scripts,sha)) != NULL) {
|
||||
sdsfree(sha);
|
||||
return dictGetKey(de);
|
||||
}
|
||||
|
||||
* On success C_OK is returned, and nothing is left on the Lua stack.
|
||||
* On error C_ERR is returned and an appropriate error is set in the
|
||||
* client context. */
|
||||
int luaCreateFunction(client *c, lua_State *lua, char *funcname, robj *body) {
|
||||
sds funcdef = sdsempty();
|
||||
|
||||
funcdef = sdscat(funcdef,"function ");
|
||||
funcdef = sdscatlen(funcdef,funcname,42);
|
||||
funcdef = sdscatlen(funcdef,"() ",3);
|
||||
@@ -1180,35 +1152,30 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
funcdef = sdscatlen(funcdef,"\nend",4);
|
||||
|
||||
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,
|
||||
"Error compiling script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
}
|
||||
addReplyErrorFormat(c,"Error compiling script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
lua_pop(lua,1);
|
||||
sdsfree(sha);
|
||||
sdsfree(funcdef);
|
||||
return NULL;
|
||||
return C_ERR;
|
||||
}
|
||||
sdsfree(funcdef);
|
||||
|
||||
if (lua_pcall(lua,0,0,0)) {
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,"Error running script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
}
|
||||
addReplyErrorFormat(c,"Error running script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
lua_pop(lua,1);
|
||||
sdsfree(sha);
|
||||
return NULL;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* We also save a SHA1 -> Original script map in a dictionary
|
||||
* so that we can replicate / write in the AOF all the
|
||||
* EVALSHA commands as EVAL using the original script. */
|
||||
int retval = dictAdd(server.lua_scripts,sha,body);
|
||||
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK);
|
||||
incrRefCount(body);
|
||||
return sha;
|
||||
{
|
||||
int retval = dictAdd(server.lua_scripts,
|
||||
sdsnewlen(funcname+2,40),body);
|
||||
serverAssertWithInfo(c,NULL,retval == DICT_OK);
|
||||
incrRefCount(body);
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
|
||||
@@ -1307,10 +1274,10 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
addReply(c, shared.noscripterr);
|
||||
return;
|
||||
}
|
||||
if (luaCreateFunction(c,lua,c->argv[1]) == NULL) {
|
||||
if (luaCreateFunction(c,lua,funcname,c->argv[1]) == C_ERR) {
|
||||
lua_pop(lua,1); /* remove the error handler from the stack. */
|
||||
/* The error is sent to the client by luaCreateFunction()
|
||||
* itself when it returns NULL. */
|
||||
* itself when it returns C_ERR. */
|
||||
return;
|
||||
}
|
||||
/* Now the following is guaranteed to return non nil */
|
||||
@@ -1471,9 +1438,22 @@ void scriptCommand(client *c) {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"load")) {
|
||||
sds sha = luaCreateFunction(c,server.lua,c->argv[2]);
|
||||
if (sha == NULL) return; /* The error was sent by luaCreateFunction(). */
|
||||
addReplyBulkCBuffer(c,sha,40);
|
||||
char funcname[43];
|
||||
sds sha;
|
||||
|
||||
funcname[0] = 'f';
|
||||
funcname[1] = '_';
|
||||
sha1hex(funcname+2,c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
|
||||
sha = sdsnewlen(funcname+2,40);
|
||||
if (dictFind(server.lua_scripts,sha) == NULL) {
|
||||
if (luaCreateFunction(c,server.lua,funcname,c->argv[2])
|
||||
== C_ERR) {
|
||||
sdsfree(sha);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addReplyBulkCBuffer(c,funcname+2,40);
|
||||
sdsfree(sha);
|
||||
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
|
||||
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
|
||||
if (server.lua_caller == NULL) {
|
||||
|
||||
@@ -175,7 +175,7 @@ void sdsfree(sds s) {
|
||||
* the output will be "6" as the string was modified but the logical length
|
||||
* remains 6 bytes. */
|
||||
void sdsupdatelen(sds s) {
|
||||
size_t reallen = strlen(s);
|
||||
int reallen = strlen(s);
|
||||
sdssetlen(s, reallen);
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ void *sdsAllocPtr(sds s) {
|
||||
* ... check for nread <= 0 and handle it ...
|
||||
* sdsIncrLen(s, nread);
|
||||
*/
|
||||
void sdsIncrLen(sds s, ssize_t incr) {
|
||||
void sdsIncrLen(sds s, int incr) {
|
||||
unsigned char flags = s[-1];
|
||||
size_t len;
|
||||
switch(flags&SDS_TYPE_MASK) {
|
||||
@@ -589,7 +589,7 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
size_t initlen = sdslen(s);
|
||||
const char *f = fmt;
|
||||
long i;
|
||||
int i;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
@@ -721,7 +721,7 @@ sds sdstrim(sds s, const char *cset) {
|
||||
* s = sdsnew("Hello World");
|
||||
* sdsrange(s,1,-1); => "ello World"
|
||||
*/
|
||||
void sdsrange(sds s, ssize_t start, ssize_t end) {
|
||||
void sdsrange(sds s, int start, int end) {
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
if (len == 0) return;
|
||||
@@ -735,9 +735,9 @@ void sdsrange(sds s, ssize_t start, ssize_t end) {
|
||||
}
|
||||
newlen = (start > end) ? 0 : (end-start)+1;
|
||||
if (newlen != 0) {
|
||||
if (start >= (ssize_t)len) {
|
||||
if (start >= (signed)len) {
|
||||
newlen = 0;
|
||||
} else if (end >= (ssize_t)len) {
|
||||
} else if (end >= (signed)len) {
|
||||
end = len-1;
|
||||
newlen = (start > end) ? 0 : (end-start)+1;
|
||||
}
|
||||
@@ -751,14 +751,14 @@ void sdsrange(sds s, ssize_t start, ssize_t end) {
|
||||
|
||||
/* Apply tolower() to every character of the sds string 's'. */
|
||||
void sdstolower(sds s) {
|
||||
size_t len = sdslen(s), j;
|
||||
int len = sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
|
||||
}
|
||||
|
||||
/* Apply toupper() to every character of the sds string 's'. */
|
||||
void sdstoupper(sds s) {
|
||||
size_t len = sdslen(s), j;
|
||||
int len = sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
|
||||
}
|
||||
@@ -782,7 +782,7 @@ int sdscmp(const sds s1, const sds s2) {
|
||||
l2 = sdslen(s2);
|
||||
minlen = (l1 < l2) ? l1 : l2;
|
||||
cmp = memcmp(s1,s2,minlen);
|
||||
if (cmp == 0) return l1>l2? 1: (l1<l2? -1: 0);
|
||||
if (cmp == 0) return l1-l2;
|
||||
return cmp;
|
||||
}
|
||||
|
||||
@@ -802,9 +802,8 @@ int sdscmp(const sds s1, const sds s2) {
|
||||
* requires length arguments. sdssplit() is just the
|
||||
* same function but for zero-terminated strings.
|
||||
*/
|
||||
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) {
|
||||
int elements = 0, slots = 5;
|
||||
long start = 0, j;
|
||||
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {
|
||||
int elements = 0, slots = 5, start = 0, j;
|
||||
sds *tokens;
|
||||
|
||||
if (seplen < 1 || len < 0) return NULL;
|
||||
|
||||
@@ -236,11 +236,11 @@ sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
|
||||
sds sdscatfmt(sds s, char const *fmt, ...);
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
void sdsrange(sds s, ssize_t start, ssize_t end);
|
||||
void sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
void sdsclear(sds s);
|
||||
int sdscmp(const sds s1, const sds s2);
|
||||
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count);
|
||||
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
|
||||
void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
void sdstoupper(sds s);
|
||||
@@ -253,7 +253,7 @@ sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
|
||||
|
||||
/* Low level functions exposed to the user API */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen);
|
||||
void sdsIncrLen(sds s, ssize_t incr);
|
||||
void sdsIncrLen(sds s, int incr);
|
||||
sds sdsRemoveFreeSpace(sds s);
|
||||
size_t sdsAllocSize(sds s);
|
||||
void *sdsAllocPtr(sds s);
|
||||
|
||||
+5
-41
@@ -84,7 +84,6 @@ typedef struct sentinelAddr {
|
||||
#define SENTINEL_MAX_PENDING_COMMANDS 100
|
||||
#define SENTINEL_ELECTION_TIMEOUT 10000
|
||||
#define SENTINEL_MAX_DESYNC 1000
|
||||
#define SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG 1
|
||||
|
||||
/* Failover machine different states. */
|
||||
#define SENTINEL_FAILOVER_STATE_NONE 0 /* No failover in progress. */
|
||||
@@ -242,8 +241,6 @@ struct sentinelState {
|
||||
int announce_port; /* Port that is gossiped to other sentinels if
|
||||
non zero. */
|
||||
unsigned long simfailure_flags; /* Failures simulation. */
|
||||
int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
|
||||
paths at runtime? */
|
||||
} sentinel;
|
||||
|
||||
/* A script execution job. */
|
||||
@@ -471,7 +468,6 @@ void initSentinel(void) {
|
||||
sentinel.announce_ip = NULL;
|
||||
sentinel.announce_port = 0;
|
||||
sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
|
||||
sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG;
|
||||
memset(sentinel.myid,0,sizeof(sentinel.myid));
|
||||
}
|
||||
|
||||
@@ -1688,12 +1684,6 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
} else if (!strcasecmp(argv[0],"announce-port") && argc == 2) {
|
||||
/* announce-port <port> */
|
||||
sentinel.announce_port = atoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"deny-scripts-reconfig") && argc == 2) {
|
||||
/* deny-scripts-reconfig <yes|no> */
|
||||
if ((sentinel.deny_scripts_reconfig = yesnotoi(argv[1])) == -1) {
|
||||
return "Please specify yes or no for the "
|
||||
"deny-scripts-reconfig options.";
|
||||
}
|
||||
} else {
|
||||
return "Unrecognized sentinel configuration statement.";
|
||||
}
|
||||
@@ -1714,12 +1704,6 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
line = sdscatprintf(sdsempty(), "sentinel myid %s", sentinel.myid);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
|
||||
/* sentinel deny-scripts-reconfig. */
|
||||
line = sdscatprintf(sdsempty(), "sentinel deny-scripts-reconfig %s",
|
||||
sentinel.deny_scripts_reconfig ? "yes" : "no");
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,
|
||||
sentinel.deny_scripts_reconfig != SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG);
|
||||
|
||||
/* For every master emit a "sentinel monitor" config entry. */
|
||||
di = dictGetIterator(sentinel.masters);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
@@ -2615,24 +2599,20 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
ping_period = ri->down_after_period;
|
||||
if (ping_period > SENTINEL_PING_PERIOD) ping_period = SENTINEL_PING_PERIOD;
|
||||
|
||||
/* Send INFO to masters and slaves, not sentinels. */
|
||||
if ((ri->flags & SRI_SENTINEL) == 0 &&
|
||||
(ri->info_refresh == 0 ||
|
||||
(now - ri->info_refresh) > info_period))
|
||||
{
|
||||
/* Send INFO to masters and slaves, not sentinels. */
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelInfoReplyCallback, ri, "INFO");
|
||||
if (retval == C_OK) ri->link->pending_commands++;
|
||||
}
|
||||
|
||||
/* Send PING to all the three kinds of instances. */
|
||||
if ((now - ri->link->last_pong_time) > ping_period &&
|
||||
} else if ((now - ri->link->last_pong_time) > ping_period &&
|
||||
(now - ri->link->last_ping_time) > ping_period/2) {
|
||||
/* Send PING to all the three kinds of instances. */
|
||||
sentinelSendPing(ri);
|
||||
}
|
||||
|
||||
/* PUBLISH hello messages to all the three kinds of instances. */
|
||||
if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
|
||||
} else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
|
||||
/* PUBLISH hello messages to all the three kinds of instances. */
|
||||
sentinelSendHello(ri);
|
||||
}
|
||||
}
|
||||
@@ -3347,14 +3327,6 @@ void sentinelSetCommand(client *c) {
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"notification-script")) {
|
||||
/* notification-script <path> */
|
||||
if (sentinel.deny_scripts_reconfig) {
|
||||
addReplyError(c,
|
||||
"Reconfiguration of scripts path is denied for "
|
||||
"security reasons. Check the deny-scripts-reconfig "
|
||||
"configuration directive in your Sentinel configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(value) && access(value,X_OK) == -1) {
|
||||
addReplyError(c,
|
||||
"Notification script seems non existing or non executable");
|
||||
@@ -3366,14 +3338,6 @@ void sentinelSetCommand(client *c) {
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"client-reconfig-script")) {
|
||||
/* client-reconfig-script <path> */
|
||||
if (sentinel.deny_scripts_reconfig) {
|
||||
addReplyError(c,
|
||||
"Reconfiguration of scripts path is denied for "
|
||||
"security reasons. Check the deny-scripts-reconfig "
|
||||
"configuration directive in your Sentinel configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(value) && access(value,X_OK) == -1) {
|
||||
addReplyError(c,
|
||||
"Client reconfiguration script seems non existing or "
|
||||
|
||||
+11
-46
@@ -125,7 +125,7 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
|
||||
* are not fast commands.
|
||||
*/
|
||||
struct redisCommand redisCommandTable[] = {
|
||||
{"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"module",moduleCommand,-2,"as",0,NULL,1,1,1,0,0},
|
||||
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
@@ -276,7 +276,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"readonly",readonlyCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"readwrite",readwriteCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,-2,"r",0,NULL,2,2,2,0,0},
|
||||
{"object",objectCommand,3,"r",0,NULL,2,2,2,0,0},
|
||||
{"memory",memoryCommand,-2,"r",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
@@ -1079,7 +1079,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
}
|
||||
} else {
|
||||
/* If there is not a background saving/rewrite in progress check if
|
||||
* we have to save/rewrite now. */
|
||||
* we have to save/rewrite now */
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
struct saveparam *sp = server.saveparams+j;
|
||||
|
||||
@@ -1102,9 +1102,8 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Trigger an AOF rewrite if needed. */
|
||||
if (server.aof_state == AOF_ON &&
|
||||
server.rdb_child_pid == -1 &&
|
||||
/* Trigger an AOF rewrite if needed */
|
||||
if (server.rdb_child_pid == -1 &&
|
||||
server.aof_child_pid == -1 &&
|
||||
server.aof_rewrite_perc &&
|
||||
server.aof_current_size > server.aof_rewrite_min_size)
|
||||
@@ -1320,7 +1319,6 @@ void createSharedObjects(void) {
|
||||
shared.rpop = createStringObject("RPOP",4);
|
||||
shared.lpop = createStringObject("LPOP",4);
|
||||
shared.lpush = createStringObject("LPUSH",5);
|
||||
shared.rpoplpush = createStringObject("RPOPLPUSH",9);
|
||||
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
|
||||
shared.integers[j] =
|
||||
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
|
||||
@@ -1374,7 +1372,6 @@ void initServerConfig(void) {
|
||||
server.active_defrag_threshold_upper = CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER;
|
||||
server.active_defrag_cycle_min = CONFIG_DEFAULT_DEFRAG_CYCLE_MIN;
|
||||
server.active_defrag_cycle_max = CONFIG_DEFAULT_DEFRAG_CYCLE_MAX;
|
||||
server.proto_max_bulk_len = CONFIG_DEFAULT_PROTO_MAX_BULK_LEN;
|
||||
server.client_max_querybuf_len = PROTO_MAX_QUERYBUF_LEN;
|
||||
server.saveparams = NULL;
|
||||
server.loading = 0;
|
||||
@@ -1434,7 +1431,6 @@ void initServerConfig(void) {
|
||||
server.cluster_migration_barrier = CLUSTER_DEFAULT_MIGRATION_BARRIER;
|
||||
server.cluster_slave_validity_factor = CLUSTER_DEFAULT_SLAVE_VALIDITY;
|
||||
server.cluster_require_full_coverage = CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE;
|
||||
server.cluster_slave_no_failover = CLUSTER_DEFAULT_SLAVE_NO_FAILOVER;
|
||||
server.cluster_configfile = zstrdup(CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
server.cluster_announce_ip = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_IP;
|
||||
server.cluster_announce_port = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT;
|
||||
@@ -1553,29 +1549,16 @@ int restartServer(int flags, mstime_t delay) {
|
||||
|
||||
/* Check if we still have accesses to the executable that started this
|
||||
* server instance. */
|
||||
if (access(server.executable,X_OK) == -1) {
|
||||
serverLog(LL_WARNING,"Can't restart: this process has no "
|
||||
"permissions to execute %s", server.executable);
|
||||
return C_ERR;
|
||||
}
|
||||
if (access(server.executable,X_OK) == -1) return C_ERR;
|
||||
|
||||
/* Config rewriting. */
|
||||
if (flags & RESTART_SERVER_CONFIG_REWRITE &&
|
||||
server.configfile &&
|
||||
rewriteConfig(server.configfile) == -1)
|
||||
{
|
||||
serverLog(LL_WARNING,"Can't restart: configuration rewrite process "
|
||||
"failed");
|
||||
return C_ERR;
|
||||
}
|
||||
rewriteConfig(server.configfile) == -1) return C_ERR;
|
||||
|
||||
/* Perform a proper shutdown. */
|
||||
if (flags & RESTART_SERVER_GRACEFULLY &&
|
||||
prepareForShutdown(SHUTDOWN_NOFLAGS) != C_OK)
|
||||
{
|
||||
serverLog(LL_WARNING,"Can't restart: error preparing for shutdown");
|
||||
return C_ERR;
|
||||
}
|
||||
prepareForShutdown(SHUTDOWN_NOFLAGS) != C_OK) return C_ERR;
|
||||
|
||||
/* Close all file descriptors, with the exception of stdin, stdout, strerr
|
||||
* which are useful if we restart a Redis server which is not daemonized. */
|
||||
@@ -1587,8 +1570,6 @@ int restartServer(int flags, mstime_t delay) {
|
||||
|
||||
/* Execute the server with the original command line. */
|
||||
if (delay) usleep(delay*1000);
|
||||
zfree(server.exec_argv[0]);
|
||||
server.exec_argv[0] = zstrdup(server.executable);
|
||||
execve(server.executable,server.exec_argv,environ);
|
||||
|
||||
/* If an error occurred here, there is nothing we can do, but exit. */
|
||||
@@ -1766,10 +1747,6 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
"Creating Server TCP listening socket %s:%d: %s",
|
||||
server.bindaddr[j] ? server.bindaddr[j] : "*",
|
||||
port, server.neterr);
|
||||
if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
|
||||
errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
|
||||
errno == EAFNOSUPPORT || errno == EADDRNOTAVAIL)
|
||||
continue;
|
||||
return C_ERR;
|
||||
}
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
@@ -1787,8 +1764,6 @@ void resetServerStats(void) {
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_expired_stale_perc = 0;
|
||||
server.stat_expired_time_cap_reached_count = 0;
|
||||
server.stat_evictedkeys = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
@@ -2350,13 +2325,8 @@ int processCommand(client *c) {
|
||||
c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
|
||||
if (!c->cmd) {
|
||||
flagTransaction(c);
|
||||
sds args = sdsempty();
|
||||
int i;
|
||||
for (i=1; i < c->argc && sdslen(args) < 128; i++)
|
||||
args = sdscatprintf(args, "`%.*s`, ", 128-(int)sdslen(args), (char*)c->argv[i]->ptr);
|
||||
addReplyErrorFormat(c,"unknown command `%s`, with args beginning with: %s",
|
||||
(char*)c->argv[0]->ptr, args);
|
||||
sdsfree(args);
|
||||
addReplyErrorFormat(c,"unknown command '%s'",
|
||||
(char*)c->argv[0]->ptr);
|
||||
return C_OK;
|
||||
} else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
|
||||
(c->argc < -c->cmd->arity)) {
|
||||
@@ -3118,8 +3088,6 @@ sds genRedisInfoString(char *section) {
|
||||
"sync_partial_ok:%lld\r\n"
|
||||
"sync_partial_err:%lld\r\n"
|
||||
"expired_keys:%lld\r\n"
|
||||
"expired_stale_perc:%.2f\r\n"
|
||||
"expired_time_cap_reached_count:%lld\r\n"
|
||||
"evicted_keys:%lld\r\n"
|
||||
"keyspace_hits:%lld\r\n"
|
||||
"keyspace_misses:%lld\r\n"
|
||||
@@ -3144,8 +3112,6 @@ sds genRedisInfoString(char *section) {
|
||||
server.stat_sync_partial_ok,
|
||||
server.stat_sync_partial_err,
|
||||
server.stat_expiredkeys,
|
||||
server.stat_expired_stale_perc*100,
|
||||
server.stat_expired_time_cap_reached_count,
|
||||
server.stat_evictedkeys,
|
||||
server.stat_keyspace_hits,
|
||||
server.stat_keyspace_misses,
|
||||
@@ -3574,8 +3540,7 @@ void loadDataFromDisk(void) {
|
||||
rsi.repl_id_is_set &&
|
||||
rsi.repl_offset != -1 &&
|
||||
/* Note that older implementations may save a repl_stream_db
|
||||
* of -1 inside the RDB file in a wrong way, see more information
|
||||
* in function rdbPopulateSaveInfo. */
|
||||
* of -1 inside the RDB file. */
|
||||
rsi.repl_stream_db != -1)
|
||||
{
|
||||
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
||||
|
||||
+5
-17
@@ -160,7 +160,6 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES (100<<20) /* don't defrag if frag overhead is below 100mb */
|
||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MIN 25 /* 25% CPU min (at lower threshold) */
|
||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 75 /* 75% CPU max (at upper threshold) */
|
||||
#define CONFIG_DEFAULT_PROTO_MAX_BULK_LEN (512ll*1024*1024) /* Bulk request max size */
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
@@ -425,7 +424,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define NOTIFY_ZSET (1<<7) /* z */
|
||||
#define NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED) /* A flag */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED) /* A */
|
||||
|
||||
/* Get the first bind addr or NULL */
|
||||
#define NET_FIRST_BIND_ADDR (server.bindaddr_count ? server.bindaddr[0] : NULL)
|
||||
@@ -587,7 +586,7 @@ typedef struct redisObject {
|
||||
unsigned encoding:4;
|
||||
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
|
||||
* LFU data (least significant 8 bits frequency
|
||||
* and most significant 16 bits access time). */
|
||||
* and most significant 16 bits decreas time). */
|
||||
int refcount;
|
||||
void *ptr;
|
||||
} robj;
|
||||
@@ -723,7 +722,6 @@ typedef struct client {
|
||||
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
|
||||
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
listNode *client_list_node; /* list node in client list */
|
||||
|
||||
/* Response buffer */
|
||||
int bufpos;
|
||||
@@ -749,7 +747,7 @@ struct sharedObjectsStruct {
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
|
||||
*rpop, *lpop, *lpush, *rpoplpush, *emptyscan,
|
||||
*rpop, *lpop, *lpush, *emptyscan,
|
||||
*select[PROTO_SHARED_SELECT_CMDS],
|
||||
*integers[OBJ_SHARED_INTEGERS],
|
||||
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
@@ -939,8 +937,6 @@ struct redisServer {
|
||||
long long stat_numcommands; /* Number of processed commands */
|
||||
long long stat_numconnections; /* Number of connections received */
|
||||
long long stat_expiredkeys; /* Number of expired keys */
|
||||
double stat_expired_stale_perc; /* Percentage of keys probably expired */
|
||||
long long stat_expired_time_cap_reached_count; /* Early expire cylce stops.*/
|
||||
long long stat_evictedkeys; /* Number of evicted keys (maxmemory) */
|
||||
long long stat_keyspace_hits; /* Number of successful lookups of keys */
|
||||
long long stat_keyspace_misses; /* Number of failed lookups of keys */
|
||||
@@ -1122,9 +1118,8 @@ struct redisServer {
|
||||
unsigned long long maxmemory; /* Max number of memory bytes to use */
|
||||
int maxmemory_policy; /* Policy for key eviction */
|
||||
int maxmemory_samples; /* Pricision of random sampling */
|
||||
int lfu_log_factor; /* LFU logarithmic counter factor. */
|
||||
int lfu_decay_time; /* LFU counter decay factor. */
|
||||
long long proto_max_bulk_len; /* Protocol bulk length maximum size. */
|
||||
unsigned int lfu_log_factor; /* LFU logarithmic counter factor. */
|
||||
unsigned int lfu_decay_time; /* LFU counter decay factor. */
|
||||
/* Blocked clients */
|
||||
unsigned int bpop_blocked_clients; /* Number of clients blocked by lists */
|
||||
list *unblocked_clients; /* list of clients to unblock before next loop */
|
||||
@@ -1162,8 +1157,6 @@ struct redisServer {
|
||||
int cluster_slave_validity_factor; /* Slave max data age for failover. */
|
||||
int cluster_require_full_coverage; /* If true, put the cluster down if
|
||||
there is at least an uncovered slot.*/
|
||||
int cluster_slave_no_failover; /* Prevent slave from starting a failover
|
||||
if the master is in failure state. */
|
||||
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
|
||||
int cluster_announce_port; /* base port to announce on cluster bus. */
|
||||
int cluster_announce_bus_port; /* bus port to announce on cluster bus. */
|
||||
@@ -1326,8 +1319,6 @@ void moduleBlockedClientPipeReadable(aeEventLoop *el, int fd, void *privdata, in
|
||||
size_t moduleCount(void);
|
||||
void moduleAcquireGIL(void);
|
||||
void moduleReleaseGIL(void);
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
@@ -1394,7 +1385,6 @@ int handleClientsWithPendingWrites(void);
|
||||
int clientHasPendingReplies(client *c);
|
||||
void unlinkClient(client *c);
|
||||
int writeToClient(int fd, client *c, int handler_installed);
|
||||
void linkClient(client *c);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(client *c, const char *fmt, ...)
|
||||
@@ -1791,7 +1781,6 @@ void scriptingInit(int setup);
|
||||
int ldbRemoveChild(pid_t pid);
|
||||
void ldbKillForkedSessions(void);
|
||||
int ldbPendingChildren(void);
|
||||
sds luaCreateFunction(client *c, lua_State *lua, robj *body);
|
||||
|
||||
/* Blocked clients */
|
||||
void processUnblockedClients(void);
|
||||
@@ -1813,7 +1802,6 @@ void evictionPoolAlloc(void);
|
||||
#define LFU_INIT_VAL 5
|
||||
unsigned long LFUGetTimeInMinutes(void);
|
||||
uint8_t LFULogIncr(uint8_t value);
|
||||
unsigned long LFUDecrAndReturn(robj *o);
|
||||
|
||||
/* Keys hashing / comparison functions for dict.c hash tables. */
|
||||
uint64_t dictSdsHash(const void *key);
|
||||
|
||||
+1
-5
@@ -39,11 +39,7 @@
|
||||
#include <errno.h> /* errno program_invocation_name program_invocation_short_name */
|
||||
|
||||
#if !defined(HAVE_SETPROCTITLE)
|
||||
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
|
||||
#define HAVE_SETPROCTITLE 1
|
||||
#else
|
||||
#define HAVE_SETPROCTITLE 0
|
||||
#endif
|
||||
#define HAVE_SETPROCTITLE (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -287,8 +287,8 @@ int hashTypeDelete(robj *o, sds field) {
|
||||
if (fptr != NULL) {
|
||||
fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);
|
||||
if (fptr != NULL) {
|
||||
zl = ziplistDelete(zl,&fptr); /* Delete the key. */
|
||||
zl = ziplistDelete(zl,&fptr); /* Delete the value. */
|
||||
zl = ziplistDelete(zl,&fptr);
|
||||
zl = ziplistDelete(zl,&fptr);
|
||||
o->ptr = zl;
|
||||
deleted = 1;
|
||||
}
|
||||
@@ -616,7 +616,7 @@ void hincrbyfloatCommand(client *c) {
|
||||
|
||||
value += incr;
|
||||
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
char buf[256];
|
||||
int len = ld2string(buf,sizeof(buf),value,1);
|
||||
new = sdsnewlen(buf,len);
|
||||
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
|
||||
|
||||
+1
-4
@@ -520,7 +520,7 @@ void lremCommand(client *c) {
|
||||
|
||||
if (removed) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"lrem",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
if (listTypeLength(subject) == 0) {
|
||||
@@ -596,9 +596,6 @@ void rpoplpushCommand(client *c) {
|
||||
signalModifiedKey(c->db,touchedkey);
|
||||
decrRefCount(touchedkey);
|
||||
server.dirty++;
|
||||
if (c->cmd->proc == brpoplpushCommand) {
|
||||
rewriteClientCommandVector(c,3,shared.rpoplpush,c->argv[1],c->argv[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-10
@@ -407,7 +407,7 @@ void spopWithCountCommand(client *c) {
|
||||
/* Get the count argument */
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != C_OK) return;
|
||||
if (l >= 0) {
|
||||
count = (unsigned long) l;
|
||||
count = (unsigned) l;
|
||||
} else {
|
||||
addReply(c,shared.outofrangeerr);
|
||||
return;
|
||||
@@ -626,7 +626,7 @@ void srandmemberWithCountCommand(client *c) {
|
||||
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != C_OK) return;
|
||||
if (l >= 0) {
|
||||
count = (unsigned long) l;
|
||||
count = (unsigned) l;
|
||||
} else {
|
||||
/* A negative count means: return the same elements multiple times
|
||||
* (i.e. don't remove the extracted element after every extraction). */
|
||||
@@ -774,21 +774,15 @@ void srandmemberCommand(client *c) {
|
||||
}
|
||||
|
||||
int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
|
||||
if (setTypeSize(*(robj**)s1) > setTypeSize(*(robj**)s2)) return 1;
|
||||
if (setTypeSize(*(robj**)s1) < setTypeSize(*(robj**)s2)) return -1;
|
||||
return 0;
|
||||
return setTypeSize(*(robj**)s1)-setTypeSize(*(robj**)s2);
|
||||
}
|
||||
|
||||
/* This is used by SDIFF and in this case we can receive NULL that should
|
||||
* be handled as empty sets. */
|
||||
int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
|
||||
robj *o1 = *(robj**)s1, *o2 = *(robj**)s2;
|
||||
unsigned long first = o1 ? setTypeSize(o1) : 0;
|
||||
unsigned long second = o2 ? setTypeSize(o2) : 0;
|
||||
|
||||
if (first < second) return 1;
|
||||
if (first > second) return -1;
|
||||
return 0;
|
||||
return (o2 ? setTypeSize(o2) : 0) - (o1 ? setTypeSize(o1) : 0);
|
||||
}
|
||||
|
||||
void sinterGenericCommand(client *c, robj **setkeys,
|
||||
|
||||
+9
-10
@@ -519,12 +519,12 @@ int zslParseLexRangeItem(robj *item, sds *dest, int *ex) {
|
||||
switch(c[0]) {
|
||||
case '+':
|
||||
if (c[1] != '\0') return C_ERR;
|
||||
*ex = 1;
|
||||
*ex = 0;
|
||||
*dest = shared.maxstring;
|
||||
return C_OK;
|
||||
case '-':
|
||||
if (c[1] != '\0') return C_ERR;
|
||||
*ex = 1;
|
||||
*ex = 0;
|
||||
*dest = shared.minstring;
|
||||
return C_OK;
|
||||
case '(':
|
||||
@@ -597,8 +597,9 @@ int zslIsInLexRange(zskiplist *zsl, zlexrangespec *range) {
|
||||
zskiplistNode *x;
|
||||
|
||||
/* Test for ranges that will always be empty. */
|
||||
int cmp = sdscmplex(range->min,range->max);
|
||||
if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))
|
||||
if (sdscmplex(range->min,range->max) > 1 ||
|
||||
(sdscmp(range->min,range->max) == 0 &&
|
||||
(range->minex || range->maxex)))
|
||||
return 0;
|
||||
x = zsl->tail;
|
||||
if (x == NULL || !zslLexValueGteMin(x->ele,range))
|
||||
@@ -871,8 +872,9 @@ int zzlIsInLexRange(unsigned char *zl, zlexrangespec *range) {
|
||||
unsigned char *p;
|
||||
|
||||
/* Test for ranges that will always be empty. */
|
||||
int cmp = sdscmplex(range->min,range->max);
|
||||
if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))
|
||||
if (sdscmplex(range->min,range->max) > 1 ||
|
||||
(sdscmp(range->min,range->max) == 0 &&
|
||||
(range->minex || range->maxex)))
|
||||
return 0;
|
||||
|
||||
p = ziplistIndex(zl,-2); /* Last element. */
|
||||
@@ -2854,10 +2856,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
while (remaining) {
|
||||
if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL) != C_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) {
|
||||
zslFreeLexRange(&range);
|
||||
return;
|
||||
}
|
||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) return;
|
||||
pos += 3; remaining -= 3;
|
||||
} else {
|
||||
zslFreeLexRange(&range);
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@
|
||||
int stringmatchlen(const char *pattern, int patternLen,
|
||||
const char *string, int stringLen, int nocase)
|
||||
{
|
||||
while(patternLen && stringLen) {
|
||||
while(patternLen) {
|
||||
switch(pattern[0]) {
|
||||
case '*':
|
||||
while (pattern[1] == '*') {
|
||||
@@ -84,7 +84,7 @@ int stringmatchlen(const char *pattern, int patternLen,
|
||||
}
|
||||
match = 0;
|
||||
while(1) {
|
||||
if (pattern[0] == '\\' && patternLen >= 2) {
|
||||
if (pattern[0] == '\\') {
|
||||
pattern++;
|
||||
patternLen--;
|
||||
if (pattern[0] == string[0])
|
||||
|
||||
@@ -33,11 +33,6 @@
|
||||
#include <stdint.h>
|
||||
#include "sds.h"
|
||||
|
||||
/* The maximum number of characters needed to represent a long double
|
||||
* as a string (long double has a huge range).
|
||||
* This should be the size of the buffer given to ld2string */
|
||||
#define MAX_LONG_DOUBLE_CHARS 5*1024
|
||||
|
||||
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
|
||||
int stringmatch(const char *p, const char *s, int nocase);
|
||||
long long memtoll(const char *p, int *err);
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "4.0.14"
|
||||
#define REDIS_VERSION "999.999.999"
|
||||
|
||||
+7
-7
@@ -53,20 +53,20 @@
|
||||
* <prevlen> <encoding>
|
||||
*
|
||||
* The length of the previous entry, <prevlen>, is encoded in the following way:
|
||||
* If this length is smaller than 254 bytes, it will only consume a single
|
||||
* If this length is smaller than 255 bytes, it will only consume a single
|
||||
* byte representing the length as an unsinged 8 bit integer. When the length
|
||||
* is greater than or equal to 254, it will consume 5 bytes. The first byte is
|
||||
* set to 254 (FE) to indicate a larger value is following. The remaining 4
|
||||
* is greater than or equal to 255, it will consume 5 bytes. The first byte is
|
||||
* set to 255 (FF) to indicate a larger value is following. The remaining 4
|
||||
* bytes take the length of the previous entry as value.
|
||||
*
|
||||
* So practically an entry is encoded in the following way:
|
||||
*
|
||||
* <prevlen from 0 to 253> <encoding> <entry>
|
||||
* <prevlen from 0 to 254> <encoding> <entry>
|
||||
*
|
||||
* Or alternatively if the previous entry length is greater than 253 bytes
|
||||
* Or alternatively if the previous entry length is greater than 254 bytes
|
||||
* the following encoding is used:
|
||||
*
|
||||
* 0xFE <4 bytes unsigned little endian prevlen> <encoding> <entry>
|
||||
* 0xFF <4 bytes unsigned little endian prevlen> <encoding> <entry>
|
||||
*
|
||||
* The encoding field of the entry depends on the content of the
|
||||
* entry. When the entry is a string, the first 2 bits of the encoding first
|
||||
@@ -440,7 +440,7 @@ unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
|
||||
if ((prevlensize) == 1) { \
|
||||
(prevlen) = (ptr)[0]; \
|
||||
} else if ((prevlensize) == 5) { \
|
||||
assert(sizeof((prevlen)) == 4); \
|
||||
assert(sizeof((prevlensize)) == 4); \
|
||||
memcpy(&(prevlen), ((char*)(ptr)) + 1, 4); \
|
||||
memrev32ifbe(&prevlen); \
|
||||
} \
|
||||
|
||||
@@ -42,16 +42,6 @@ proc get_myself id {
|
||||
return {}
|
||||
}
|
||||
|
||||
# Get a specific node by ID by parsing the CLUSTER NODES output
|
||||
# of the instance Number 'instance_id'
|
||||
proc get_node_by_id {instance_id node_id} {
|
||||
set nodes [get_cluster_nodes $instance_id]
|
||||
foreach n $nodes {
|
||||
if {[dict get $n id] eq $node_id} {return $n}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
# Return the value of the specified CLUSTER INFO field.
|
||||
proc CI {n field} {
|
||||
get_info_field [R $n cluster info] $field
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# Check that the no-failover option works
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 5
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
|
||||
test "Instance #5 is a slave" {
|
||||
assert {[RI 5 role] eq {slave}}
|
||||
|
||||
# Configure it to never failover the master
|
||||
R 5 CONFIG SET cluster-slave-no-failover yes
|
||||
}
|
||||
|
||||
test "Instance #5 synced with the master" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 5 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Instance #5 master link status is not up"
|
||||
}
|
||||
}
|
||||
|
||||
test "The nofailover flag is propagated" {
|
||||
set slave5_id [dict get [get_myself 5] id]
|
||||
|
||||
foreach_redis_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[has_flag [get_node_by_id $id $slave5_id] nofailover]
|
||||
} else {
|
||||
fail "Instance $id can't see the nofailover flag of slave"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set current_epoch [CI 1 cluster_current_epoch]
|
||||
|
||||
test "Killing one master node" {
|
||||
kill_instance redis 0
|
||||
}
|
||||
|
||||
test "Cluster should be still down after some time" {
|
||||
after 10000
|
||||
assert_cluster_state fail
|
||||
}
|
||||
|
||||
test "Instance #5 is still a slave" {
|
||||
assert {[RI 5 role] eq {slave}}
|
||||
}
|
||||
|
||||
test "Restarting the previously killed master node" {
|
||||
restart_instance redis 0
|
||||
}
|
||||
+1
-1
@@ -318,7 +318,7 @@ proc end_tests {} {
|
||||
puts "GOOD! No errors."
|
||||
exit 0
|
||||
} else {
|
||||
puts "WARNING $::failed test(s) failed."
|
||||
puts "WARNING $::failed tests faield."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ start_server {} {
|
||||
# Config
|
||||
set debug_msg 0 ; # Enable additional debug messages
|
||||
|
||||
set no_exit 0 ; # Do not exit at end of the test
|
||||
set no_exit 0; ; # Do not exit at end of the test
|
||||
|
||||
set duration 20 ; # Total test seconds
|
||||
|
||||
@@ -175,69 +175,6 @@ start_server {} {
|
||||
assert {$sync_count == $new_sync_count}
|
||||
}
|
||||
|
||||
test "PSYNC2: Slave RDB restart with EVALSHA in backlog issue #4483" {
|
||||
# Pick a random slave
|
||||
set slave_id [expr {($master_id+1)%5}]
|
||||
set sync_count [status $R($master_id) sync_full]
|
||||
|
||||
# Make sure to replicate the first EVAL while the salve is online
|
||||
# so that it's part of the scripts the master believes it's safe
|
||||
# to propagate as EVALSHA.
|
||||
$R($master_id) EVAL {return redis.call("incr","__mycounter")} 0
|
||||
$R($master_id) EVALSHA e6e0b547500efcec21eddb619ac3724081afee89 0
|
||||
|
||||
# Wait for the two to sync
|
||||
wait_for_condition 50 1000 {
|
||||
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
|
||||
} else {
|
||||
fail "Slave not reconnecting"
|
||||
}
|
||||
|
||||
# Prevent the slave from receiving master updates, and at
|
||||
# the same time send a new script several times to the
|
||||
# master, so that we'll end with EVALSHA into the backlog.
|
||||
$R($slave_id) slaveof 127.0.0.1 0
|
||||
|
||||
$R($master_id) EVALSHA e6e0b547500efcec21eddb619ac3724081afee89 0
|
||||
$R($master_id) EVALSHA e6e0b547500efcec21eddb619ac3724081afee89 0
|
||||
$R($master_id) EVALSHA e6e0b547500efcec21eddb619ac3724081afee89 0
|
||||
|
||||
catch {
|
||||
$R($slave_id) config rewrite
|
||||
$R($slave_id) debug restart
|
||||
}
|
||||
|
||||
# Reconfigure the slave correctly again, when it's back online.
|
||||
set retry 50
|
||||
while {$retry} {
|
||||
if {[catch {
|
||||
$R($slave_id) slaveof $master_host $master_port
|
||||
}]} {
|
||||
after 1000
|
||||
} else {
|
||||
break
|
||||
}
|
||||
incr retry -1
|
||||
}
|
||||
|
||||
# The master should be back at 4 slaves eventually
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R($master_id) connected_slaves] == 4
|
||||
} else {
|
||||
fail "Slave not reconnecting"
|
||||
}
|
||||
set new_sync_count [status $R($master_id) sync_full]
|
||||
assert {$sync_count == $new_sync_count}
|
||||
|
||||
# However if the slave started with the full state of the
|
||||
# scripting engine, we should now have the same digest.
|
||||
wait_for_condition 50 1000 {
|
||||
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
|
||||
} else {
|
||||
fail "Debug digest mismatch between master and slave in post-restart handshake"
|
||||
}
|
||||
}
|
||||
|
||||
if {$no_exit} {
|
||||
while 1 { puts -nonewline .; flush stdout; after 1000}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,9 @@ start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
r -1 slaveof [srv 0 host] [srv 0 port]
|
||||
wait_for_condition 50 100 {
|
||||
[s -1 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
after 1000
|
||||
s -1 role
|
||||
} {slave}
|
||||
|
||||
test {If min-slaves-to-write is honored, write is accepted} {
|
||||
r config set min-slaves-to-write 1
|
||||
|
||||
@@ -100,6 +100,7 @@ start_server {tags {"repl"}} {
|
||||
close $fd
|
||||
puts "Master - Slave inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
|
||||
}
|
||||
|
||||
set old_digest [r debug digest]
|
||||
@@ -108,27 +109,5 @@ start_server {tags {"repl"}} {
|
||||
set new_digest [r debug digest]
|
||||
assert {$old_digest eq $new_digest}
|
||||
}
|
||||
|
||||
test {SLAVE can reload "lua" AUX RDB fields of duplicated scripts} {
|
||||
# Force a Slave full resynchronization
|
||||
r debug change-repl-id
|
||||
r -1 client kill type master
|
||||
|
||||
# Check that after a full resync the slave can still load
|
||||
# correctly the RDB file: such file will contain "lua" AUX
|
||||
# sections with scripts already in the memory of the master.
|
||||
|
||||
wait_for_condition 50 100 {
|
||||
[s -1 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
|
||||
wait_for_condition 50 100 {
|
||||
[r debug digest] eq [r -1 debug digest]
|
||||
} else {
|
||||
fail "DEBUG DIGEST mismatch after full SYNC with many scripts"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,28 +308,4 @@ start_server {tags {"dump"}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE AUTH: correct and wrong password cases} {
|
||||
set first [srv 0 client]
|
||||
r del list
|
||||
r lpush list a b c d
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
$second config set requirepass foobar
|
||||
$second auth foobar
|
||||
|
||||
assert {[$first exists list] == 1}
|
||||
assert {[$second exists list] == 0}
|
||||
set ret [r -1 migrate $second_host $second_port list 9 5000 AUTH foobar]
|
||||
assert {$ret eq {OK}}
|
||||
assert {[$second exists list] == 1}
|
||||
assert {[$second lrange list 0 -1] eq {d c b a}}
|
||||
|
||||
r -1 lpush list a b c d
|
||||
$second config set requirepass foobar2
|
||||
catch {r -1 migrate $second_host $second_port list 9 5000 AUTH foobar} err
|
||||
assert_match {*invalid password*} $err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,18 +47,4 @@ start_server {tags {"latency-monitor"}} {
|
||||
assert {[r latency reset] > 0}
|
||||
assert {[r latency latest] eq {}}
|
||||
}
|
||||
|
||||
test {LATENCY of expire events are correctly collected} {
|
||||
r config set latency-monitor-threshold 20
|
||||
r eval {
|
||||
local i = 0
|
||||
while (i < 1000000) do
|
||||
redis.call('sadd','mybigkey',i)
|
||||
i = i+1
|
||||
end
|
||||
} 0
|
||||
r pexpire mybigkey 1
|
||||
after 500
|
||||
assert_match {*expire-cycle*} [r latency latest]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +144,4 @@ start_server {tags {"incr"}} {
|
||||
r set foo 1
|
||||
roundFloat [r incrbyfloat foo -1.1]
|
||||
} {-0.1}
|
||||
|
||||
test {string to double with null terminator} {
|
||||
r set foo 1
|
||||
r setrange foo 2 2
|
||||
catch {r incrbyfloat foo 1} err
|
||||
format $err
|
||||
} {ERR*valid*}
|
||||
}
|
||||
|
||||
@@ -388,7 +388,7 @@ start_server {tags {"zset"}} {
|
||||
0 omega}
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZLEXCOUNT basics" {
|
||||
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZCOUNT basics" {
|
||||
create_default_lex_zset
|
||||
|
||||
# inclusive range
|
||||
@@ -416,22 +416,6 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {} [r zrevrangebylex zset \[elez \[elex]
|
||||
assert_equal {} [r zrevrangebylex zset (hill (omega]
|
||||
}
|
||||
|
||||
test "ZLEXCOUNT advanced" {
|
||||
create_default_lex_zset
|
||||
|
||||
assert_equal 9 [r zlexcount zset - +]
|
||||
assert_equal 0 [r zlexcount zset + -]
|
||||
assert_equal 0 [r zlexcount zset + \[c]
|
||||
assert_equal 0 [r zlexcount zset \[c -]
|
||||
assert_equal 8 [r zlexcount zset \[bar +]
|
||||
assert_equal 5 [r zlexcount zset \[bar \[foo]
|
||||
assert_equal 4 [r zlexcount zset \[bar (foo]
|
||||
assert_equal 4 [r zlexcount zset (bar \[foo]
|
||||
assert_equal 3 [r zlexcount zset (bar (foo]
|
||||
assert_equal 5 [r zlexcount zset - (foo]
|
||||
assert_equal 1 [r zlexcount zset (maxstring +]
|
||||
}
|
||||
|
||||
test "ZRANGEBYSLEX with LIMIT" {
|
||||
create_default_lex_zset
|
||||
|
||||
@@ -3,14 +3,6 @@
|
||||
# Simple Redis init.d script conceived to work on Linux systems
|
||||
# as it does use of the /proc filesystem.
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: redis_6379
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Redis data structure server
|
||||
# Description: Redis data structure server. See https://redis.io
|
||||
### END INIT INFO
|
||||
|
||||
REDISPORT=6379
|
||||
EXEC=/usr/local/bin/redis-server
|
||||
CLIEXEC=/usr/local/bin/redis-cli
|
||||
|
||||
@@ -21,7 +21,7 @@ append template "\n\n"
|
||||
set date [clock format [clock seconds]]
|
||||
set template [string map [list %ver% $ver %date% $date] $template]
|
||||
|
||||
append template [exec git log $branch~30..$branch "--format=format:%an in commit %h:%n %s" --shortstat]
|
||||
append template [exec git log $branch~100..$branch "--format=format:%an in commit %h:%n %s" --shortstat]
|
||||
|
||||
#Older, more verbose version.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user