Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
d375595d
Unverified
Commit
d375595d
authored
Apr 27, 2022
by
Oran Agra
Committed by
GitHub
Apr 27, 2022
Browse files
Merge pull request #10652 from oranagra/redis-7.0.0
Redis 7.0.0
parents
fb4e0d40
c1f30206
Changes
124
Hide whitespace changes
Inline
Side-by-side
src/script.h
View file @
d375595d
...
...
@@ -64,6 +64,7 @@
#define SCRIPT_READ_ONLY (1ULL<<5)
/* indicate that the current script should only perform read commands */
#define SCRIPT_ALLOW_OOM (1ULL<<6)
/* indicate to allow any command even if OOM reached */
#define SCRIPT_EVAL_MODE (1ULL<<7)
/* Indicate that the current script called from legacy Lua */
#define SCRIPT_ALLOW_CROSS_SLOT (1ULL<<8)
/* Indicate that the current script may access keys from multiple slots */
typedef
struct
scriptRunCtx
scriptRunCtx
;
struct
scriptRunCtx
{
...
...
@@ -82,6 +83,7 @@ struct scriptRunCtx {
#define SCRIPT_FLAG_ALLOW_STALE (1ULL<<2)
#define SCRIPT_FLAG_NO_CLUSTER (1ULL<<3)
#define SCRIPT_FLAG_EVAL_COMPAT_MODE (1ULL<<4)
/* EVAL Script backwards compatible behavior, no shebang provided */
#define SCRIPT_FLAG_ALLOW_CROSS_SLOT (1ULL<<5)
/* Defines a script flags */
typedef
struct
scriptFlag
{
...
...
src/script_lua.c
View file @
d375595d
...
...
@@ -41,6 +41,97 @@
#include <ctype.h>
#include <math.h>
/* Globals that are added by the Lua libraries */
static
char
*
libraries_allow_list
[]
=
{
"string"
,
"cjson"
,
"bit"
,
"cmsgpack"
,
"math"
,
"table"
,
"struct"
,
NULL
,
};
/* Redis Lua API globals */
static
char
*
redis_api_allow_list
[]
=
{
"redis"
,
"__redis__err__handler"
,
/* error handler for eval, currently located on globals.
Should move to registry. */
NULL
,
};
/* Lua builtins */
static
char
*
lua_builtins_allow_list
[]
=
{
"xpcall"
,
"tostring"
,
"getfenv"
,
"setmetatable"
,
"next"
,
"assert"
,
"tonumber"
,
"rawequal"
,
"collectgarbage"
,
"getmetatable"
,
"rawset"
,
"pcall"
,
"coroutine"
,
"type"
,
"_G"
,
"select"
,
"unpack"
,
"gcinfo"
,
"pairs"
,
"rawget"
,
"loadstring"
,
"ipairs"
,
"_VERSION"
,
"setfenv"
,
"load"
,
"error"
,
NULL
,
};
/* Lua builtins which are not documented on the Lua documentation */
static
char
*
lua_builtins_not_documented_allow_list
[]
=
{
"newproxy"
,
NULL
,
};
/* Lua builtins which are allowed on initialization but will be removed right after */
static
char
*
lua_builtins_removed_after_initialization_allow_list
[]
=
{
"debug"
,
/* debug will be set to nil after the error handler will be created */
NULL
,
};
/* Those allow lists was created from the globals that was
* available to the user when the allow lists was first introduce.
* Because we do not want to break backward compatibility we keep
* all the globals. The allow lists will prevent us from accidentally
* creating unwanted globals in the future.
*
* Also notice that the allow list is only checked on start time,
* after that the global table is locked so not need to check anything.*/
static
char
**
allow_lists
[]
=
{
libraries_allow_list
,
redis_api_allow_list
,
lua_builtins_allow_list
,
lua_builtins_not_documented_allow_list
,
lua_builtins_removed_after_initialization_allow_list
,
NULL
,
};
/* Deny list contains elements which we know we do not want to add to globals
* and there is no need to print a warning message form them. We will print a
* log message only if an element was added to the globals and the element is
* not on the allow list nor on the back list. */
static
char
*
deny_list
[]
=
{
"dofile"
,
"loadfile"
,
"print"
,
NULL
,
};
static
int
redis_math_random
(
lua_State
*
L
);
static
int
redis_math_randomseed
(
lua_State
*
L
);
static
void
redisProtocolToLuaType_Int
(
void
*
ctx
,
long
long
val
,
const
char
*
proto
,
size_t
proto_len
);
...
...
@@ -1113,15 +1204,6 @@ static void luaLoadLibraries(lua_State *lua) {
#endif
}
/* Remove a functions that we don't want to expose to the Redis scripting
* environment. */
static
void
luaRemoveUnsupportedFunctions
(
lua_State
*
lua
)
{
lua_pushnil
(
lua
);
lua_setglobal
(
lua
,
"loadfile"
);
lua_pushnil
(
lua
);
lua_setglobal
(
lua
,
"dofile"
);
}
/* Return sds of the string value located on stack at the given index.
* Return NULL if the value is not a string. */
sds
luaGetStringSds
(
lua_State
*
lua
,
int
index
)
{
...
...
@@ -1135,107 +1217,120 @@ sds luaGetStringSds(lua_State *lua, int index) {
return
str_sds
;
}
/* This function installs metamethods in the global table _G that prevent
* the creation of globals accidentally.
*
* It should be the last to be called in the scripting engine initialization
* sequence, because it may interact with creation of globals.
*
* On Legacy Lua (eval) we need to check 'w ~= \"main\"' otherwise we will not be able
* to create the global 'function <sha> ()' variable. On Functions Lua engine we do not use
* this trick so it's not needed. */
void
luaEnableGlobalsProtection
(
lua_State
*
lua
,
int
is_eval
)
{
char
*
s
[
32
];
sds
code
=
sdsempty
();
int
j
=
0
;
/* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html.
* Modified to be adapted to Redis. */
s
[
j
++
]
=
"local dbg=debug
\n
"
;
s
[
j
++
]
=
"local mt = {}
\n
"
;
s
[
j
++
]
=
"setmetatable(_G, mt)
\n
"
;
s
[
j
++
]
=
"mt.__newindex = function (t, n, v)
\n
"
;
s
[
j
++
]
=
" if dbg.getinfo(2) then
\n
"
;
s
[
j
++
]
=
" local w = dbg.getinfo(2,
\"
S
\"
).what
\n
"
;
s
[
j
++
]
=
is_eval
?
" if w ~=
\"
main
\"
and w ~=
\"
C
\"
then
\n
"
:
" if w ~=
\"
C
\"
then
\n
"
;
s
[
j
++
]
=
" error(
\"
Script attempted to create global variable '
\"
..tostring(n)..
\"
'
\"
, 2)
\n
"
;
s
[
j
++
]
=
" end
\n
"
;
s
[
j
++
]
=
" end
\n
"
;
s
[
j
++
]
=
" rawset(t, n, v)
\n
"
;
s
[
j
++
]
=
"end
\n
"
;
s
[
j
++
]
=
"mt.__index = function (t, n)
\n
"
;
s
[
j
++
]
=
" if dbg.getinfo(2) and dbg.getinfo(2,
\"
S
\"
).what ~=
\"
C
\"
then
\n
"
;
s
[
j
++
]
=
" error(
\"
Script attempted to access nonexistent global variable '
\"
..tostring(n)..
\"
'
\"
, 2)
\n
"
;
s
[
j
++
]
=
" end
\n
"
;
s
[
j
++
]
=
" return rawget(t, n)
\n
"
;
s
[
j
++
]
=
"end
\n
"
;
s
[
j
++
]
=
"debug = nil
\n
"
;
s
[
j
++
]
=
NULL
;
for
(
j
=
0
;
s
[
j
]
!=
NULL
;
j
++
)
code
=
sdscatlen
(
code
,
s
[
j
],
strlen
(
s
[
j
]));
luaL_loadbuffer
(
lua
,
code
,
sdslen
(
code
),
"@enable_strict_lua"
);
lua_pcall
(
lua
,
0
,
0
,
0
);
sdsfree
(
code
);
static
int
luaProtectedTableError
(
lua_State
*
lua
)
{
int
argc
=
lua_gettop
(
lua
);
if
(
argc
!=
2
)
{
serverLog
(
LL_WARNING
,
"malicious code trying to call luaProtectedTableError with wrong arguments"
);
luaL_error
(
lua
,
"Wrong number of arguments to luaProtectedTableError"
);
}
if
(
!
lua_isstring
(
lua
,
-
1
)
&&
!
lua_isnumber
(
lua
,
-
1
))
{
luaL_error
(
lua
,
"Second argument to luaProtectedTableError must be a string or number"
);
}
const
char
*
variable_name
=
lua_tostring
(
lua
,
-
1
);
luaL_error
(
lua
,
"Script attempted to access nonexistent global variable '%s'"
,
variable_name
);
return
0
;
}
/* Create a global protection function and put it to registry.
* This need to be called once in the lua_State lifetime.
* After called it is possible to use luaSetGlobalProtection
* to set global protection on a give table.
/* Set a special metatable on the table on the top of the stack.
* The metatable will raise an error if the user tries to fetch
* an un-existing value.
*
* The function assumes the Lua stack have a least enough
* space to push 2 element, its up to the caller to verify
* this before calling this function.
*
* Notice, the difference between this and luaEnableGlobalsProtection
* is that luaEnableGlobalsProtection is enabling global protection
* on the current Lua globals. This registering a global protection
* function that later can be applied on any table. */
void
luaRegisterGlobalProtectionFunction
(
lua_State
*
lua
)
{
lua_pushstring
(
lua
,
REGISTRY_SET_GLOBALS_PROTECTION_NAME
);
char
*
global_protection_func
=
"local dbg = debug
\n
"
"local globals_protection = function (t)
\n
"
" local mt = {}
\n
"
" setmetatable(t, mt)
\n
"
" mt.__newindex = function (t, n, v)
\n
"
" if dbg.getinfo(2) then
\n
"
" local w = dbg.getinfo(2,
\"
S
\"
).what
\n
"
" if w ~=
\"
C
\"
then
\n
"
" error(
\"
Script attempted to create global variable '
\"
..tostring(n)..
\"
'
\"
, 2)
\n
"
" end"
" end"
" rawset(t, n, v)
\n
"
" end
\n
"
" mt.__index = function (t, n)
\n
"
" if dbg.getinfo(2) and dbg.getinfo(2,
\"
S
\"
).what ~=
\"
C
\"
then
\n
"
" error(
\"
Script attempted to access nonexistent global variable '
\"
..tostring(n)..
\"
'
\"
, 2)
\n
"
" end
\n
"
" return rawget(t, n)
\n
"
" end
\n
"
"end
\n
"
"return globals_protection"
;
int
res
=
luaL_loadbuffer
(
lua
,
global_protection_func
,
strlen
(
global_protection_func
),
"@global_protection_def"
);
serverAssert
(
res
==
0
);
res
=
lua_pcall
(
lua
,
0
,
1
,
0
);
serverAssert
(
res
==
0
);
lua_settable
(
lua
,
LUA_REGISTRYINDEX
);
* this before calling this function. */
void
luaSetErrorMetatable
(
lua_State
*
lua
)
{
lua_newtable
(
lua
);
/* push metatable */
lua_pushcfunction
(
lua
,
luaProtectedTableError
);
/* push get error handler */
lua_setfield
(
lua
,
-
2
,
"__index"
);
lua_setmetatable
(
lua
,
-
2
);
}
/* Set global protection on a given table.
* The table need to be located on the top of the lua stack.
* After called, it will no longer be possible to set
* new items on the table. The function is not removing
* the table from the top of the stack!
static
int
luaNewIndexAllowList
(
lua_State
*
lua
)
{
int
argc
=
lua_gettop
(
lua
);
if
(
argc
!=
3
)
{
serverLog
(
LL_WARNING
,
"malicious code trying to call luaProtectedTableError with wrong arguments"
);
luaL_error
(
lua
,
"Wrong number of arguments to luaNewIndexAllowList"
);
}
if
(
!
lua_istable
(
lua
,
-
3
))
{
luaL_error
(
lua
,
"first argument to luaNewIndexAllowList must be a table"
);
}
if
(
!
lua_isstring
(
lua
,
-
2
)
&&
!
lua_isnumber
(
lua
,
-
2
))
{
luaL_error
(
lua
,
"Second argument to luaNewIndexAllowList must be a string or number"
);
}
const
char
*
variable_name
=
lua_tostring
(
lua
,
-
2
);
/* check if the key is in our allow list */
char
***
allow_l
=
allow_lists
;
for
(;
*
allow_l
;
++
allow_l
){
char
**
c
=
*
allow_l
;
for
(;
*
c
;
++
c
)
{
if
(
strcmp
(
*
c
,
variable_name
)
==
0
)
{
break
;
}
}
if
(
*
c
)
{
break
;
}
}
if
(
!*
allow_l
)
{
/* Search the value on the back list, if its there we know that it was removed
* on purpose and there is no need to print a warning. */
char
**
c
=
deny_list
;
for
(
;
*
c
;
++
c
)
{
if
(
strcmp
(
*
c
,
variable_name
)
==
0
)
{
break
;
}
}
if
(
!*
c
)
{
serverLog
(
LL_WARNING
,
"A key '%s' was added to Lua globals which is not on the globals allow list nor listed on the deny list."
,
variable_name
);
}
}
else
{
lua_rawset
(
lua
,
-
3
);
}
return
0
;
}
/* Set a metatable with '__newindex' function that verify that
* the new index appears on our globals while list.
*
* The function assumes the Lua stack have a least enough
* space to push 2 element, its up to the caller to verify
* this before calling this function. */
void
luaSetGlobalProtection
(
lua_State
*
lua
)
{
lua_pushstring
(
lua
,
REGISTRY_SET_GLOBALS_PROTECTION_NAME
);
lua_gettable
(
lua
,
LUA_REGISTRYINDEX
);
lua_pushvalue
(
lua
,
-
2
);
int
res
=
lua_pcall
(
lua
,
1
,
0
,
0
);
serverAssert
(
res
==
0
);
* The metatable is set on the table which located on the top
* of the stack.
*/
void
luaSetAllowListProtection
(
lua_State
*
lua
)
{
lua_newtable
(
lua
);
/* push metatable */
lua_pushcfunction
(
lua
,
luaNewIndexAllowList
);
/* push get error handler */
lua_setfield
(
lua
,
-
2
,
"__newindex"
);
lua_setmetatable
(
lua
,
-
2
);
}
/* Set the readonly flag on the table located on the top of the stack
* and recursively call this function on each table located on the original
* table. Also, recursively call this function on the metatables.*/
void
luaSetTableProtectionRecursively
(
lua_State
*
lua
)
{
/* This protect us from a loop in case we already visited the table
* For example, globals has '_G' key which is pointing back to globals. */
if
(
lua_isreadonlytable
(
lua
,
-
1
))
{
return
;
}
/* protect the current table */
lua_enablereadonlytable
(
lua
,
-
1
,
1
);
lua_checkstack
(
lua
,
2
);
lua_pushnil
(
lua
);
/* Use nil to start iteration. */
while
(
lua_next
(
lua
,
-
2
))
{
/* Stack now: table, key, value */
if
(
lua_istable
(
lua
,
-
1
))
{
luaSetTableProtectionRecursively
(
lua
);
}
lua_pop
(
lua
,
1
);
}
/* protect the metatable if exists */
if
(
lua_getmetatable
(
lua
,
-
1
))
{
luaSetTableProtectionRecursively
(
lua
);
lua_pop
(
lua
,
1
);
/* pop the metatable */
}
}
void
luaRegisterVersion
(
lua_State
*
lua
)
{
...
...
@@ -1272,8 +1367,11 @@ void luaRegisterLogFunction(lua_State* lua) {
}
void
luaRegisterRedisAPI
(
lua_State
*
lua
)
{
lua_pushvalue
(
lua
,
LUA_GLOBALSINDEX
);
luaSetAllowListProtection
(
lua
);
lua_pop
(
lua
,
1
);
luaLoadLibraries
(
lua
);
luaRemoveUnsupportedFunctions
(
lua
);
lua_pushcfunction
(
lua
,
luaRedisPcall
);
lua_setglobal
(
lua
,
"pcall"
);
...
...
@@ -1504,9 +1602,19 @@ void luaCallFunction(scriptRunCtx* run_ctx, lua_State *lua, robj** keys, size_t
* EVAL received. */
luaCreateArray
(
lua
,
keys
,
nkeys
);
/* On eval, keys and arguments are globals. */
if
(
run_ctx
->
flags
&
SCRIPT_EVAL_MODE
)
lua_setglobal
(
lua
,
"KEYS"
);
if
(
run_ctx
->
flags
&
SCRIPT_EVAL_MODE
){
/* open global protection to set KEYS */
lua_enablereadonlytable
(
lua
,
LUA_GLOBALSINDEX
,
0
);
lua_setglobal
(
lua
,
"KEYS"
);
lua_enablereadonlytable
(
lua
,
LUA_GLOBALSINDEX
,
1
);
}
luaCreateArray
(
lua
,
args
,
nargs
);
if
(
run_ctx
->
flags
&
SCRIPT_EVAL_MODE
)
lua_setglobal
(
lua
,
"ARGV"
);
if
(
run_ctx
->
flags
&
SCRIPT_EVAL_MODE
){
/* open global protection to set ARGV */
lua_enablereadonlytable
(
lua
,
LUA_GLOBALSINDEX
,
0
);
lua_setglobal
(
lua
,
"ARGV"
);
lua_enablereadonlytable
(
lua
,
LUA_GLOBALSINDEX
,
1
);
}
/* At this point whether this script was never seen before or if it was
* already defined, we can call it.
...
...
src/script_lua.h
View file @
d375595d
...
...
@@ -67,9 +67,10 @@ typedef struct errorInfo {
void
luaRegisterRedisAPI
(
lua_State
*
lua
);
sds
luaGetStringSds
(
lua_State
*
lua
,
int
index
);
void
luaEnableGlobalsProtection
(
lua_State
*
lua
,
int
is_eval
);
void
luaRegisterGlobalProtectionFunction
(
lua_State
*
lua
);
void
luaSetGlobalProtection
(
lua_State
*
lua
);
void
luaSetErrorMetatable
(
lua_State
*
lua
);
void
luaSetAllowListProtection
(
lua_State
*
lua
);
void
luaSetTableProtectionRecursively
(
lua_State
*
lua
);
void
luaRegisterLogFunction
(
lua_State
*
lua
);
void
luaRegisterVersion
(
lua_State
*
lua
);
void
luaPushErrorBuff
(
lua_State
*
lua
,
sds
err_buff
);
...
...
src/sentinel.c
View file @
d375595d
...
...
@@ -705,7 +705,7 @@ void sentinelEvent(int level, char *type, sentinelRedisInstance *ri,
if
(
level
!=
LL_DEBUG
)
{
channel
=
createStringObject
(
type
,
strlen
(
type
));
payload
=
createStringObject
(
msg
,
strlen
(
msg
));
pubsubPublishMessage
(
channel
,
payload
);
pubsubPublishMessage
(
channel
,
payload
,
0
);
decrRefCount
(
channel
);
decrRefCount
(
payload
);
}
...
...
src/server.c
View file @
d375595d
...
...
@@ -36,7 +36,6 @@
#include "atomicvar.h"
#include "mt19937-64.h"
#include "functions.h"
#include "hdr_alloc.h"
#include <time.h>
#include <signal.h>
...
...
@@ -1016,18 +1015,8 @@ void databasesCron(void) {
}
}
/* We take a cached value of the unix time in the global state because with
* virtual memory and aging there is to store the current time in objects at
* every object access, and accuracy is not needed. To access a global var is
* a lot faster than calling time(NULL).
*
* This function should be fast because it is called at every command execution
* in call(), so it is possible to decide if to update the daylight saving
* info or not using the 'update_daylight_info' argument. Normally we update
* such info only when calling this function from serverCron() but not when
* calling it from call(). */
void
updateCachedTime
(
int
update_daylight_info
)
{
server
.
ustime
=
ustime
();
static
inline
void
updateCachedTimeWithUs
(
int
update_daylight_info
,
const
long
long
ustime
)
{
server
.
ustime
=
ustime
;
server
.
mstime
=
server
.
ustime
/
1000
;
time_t
unixtime
=
server
.
mstime
/
1000
;
atomicSet
(
server
.
unixtime
,
unixtime
);
...
...
@@ -1045,6 +1034,21 @@ void updateCachedTime(int update_daylight_info) {
}
}
/* We take a cached value of the unix time in the global state because with
* virtual memory and aging there is to store the current time in objects at
* every object access, and accuracy is not needed. To access a global var is
* a lot faster than calling time(NULL).
*
* This function should be fast because it is called at every command execution
* in call(), so it is possible to decide if to update the daylight saving
* info or not using the 'update_daylight_info' argument. Normally we update
* such info only when calling this function from serverCron() but not when
* calling it from call(). */
void
updateCachedTime
(
int
update_daylight_info
)
{
const
long
long
us
=
ustime
();
updateCachedTimeWithUs
(
update_daylight_info
,
us
);
}
void
checkChildrenDone
(
void
)
{
int
statloc
=
0
;
pid_t
pid
;
...
...
@@ -1209,10 +1213,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
cronUpdateMemoryStats
();
/* We received a SIGTERM, shutting down here in a safe way, as it is
/* We received a SIGTERM
or SIGINT
, shutting down here in a safe way, as it is
* not ok doing so inside the signal handler. */
if
(
server
.
shutdown_asap
&&
!
isShutdownInitiated
())
{
if
(
prepareForShutdown
(
SHUTDOWN_NOFLAGS
)
==
C_OK
)
exit
(
0
);
int
shutdownFlags
=
SHUTDOWN_NOFLAGS
;
if
(
server
.
last_sig_received
==
SIGINT
&&
server
.
shutdown_on_sigint
)
shutdownFlags
=
server
.
shutdown_on_sigint
;
else
if
(
server
.
last_sig_received
==
SIGTERM
&&
server
.
shutdown_on_sigterm
)
shutdownFlags
=
server
.
shutdown_on_sigterm
;
if
(
prepareForShutdown
(
shutdownFlags
)
==
C_OK
)
exit
(
0
);
}
else
if
(
isShutdownInitiated
())
{
if
(
server
.
mstime
>=
server
.
shutdown_mstime
||
isReadyToShutdown
())
{
if
(
finishShutdown
()
==
C_OK
)
exit
(
0
);
...
...
@@ -1296,13 +1306,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
if
(
server
.
aof_state
==
AOF_ON
&&
!
hasActiveChildProcess
()
&&
server
.
aof_rewrite_perc
&&
server
.
aof_current_size
>
server
.
aof_rewrite_min_size
&&
!
aofRewriteLimited
())
server
.
aof_current_size
>
server
.
aof_rewrite_min_size
)
{
long
long
base
=
server
.
aof_rewrite_base_size
?
server
.
aof_rewrite_base_size
:
1
;
long
long
growth
=
(
server
.
aof_current_size
*
100
/
base
)
-
100
;
if
(
growth
>=
server
.
aof_rewrite_perc
)
{
if
(
growth
>=
server
.
aof_rewrite_perc
&&
!
aofRewriteLimited
()
)
{
serverLog
(
LL_NOTICE
,
"Starting automatic rewriting of AOF on %lld%% growth"
,
growth
);
rewriteAppendOnlyFileBackground
();
}
...
...
@@ -1326,8 +1335,11 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
* however to try every second is enough in case of 'hz' is set to
* a higher frequency. */
run_with_period
(
1000
)
{
if
(
server
.
aof_state
==
AOF_ON
&&
server
.
aof_last_write_status
==
C_ERR
)
flushAppendOnlyFile
(
0
);
if
((
server
.
aof_state
==
AOF_ON
||
server
.
aof_state
==
AOF_WAIT_REWRITE
)
&&
server
.
aof_last_write_status
==
C_ERR
)
{
flushAppendOnlyFile
(
0
);
}
}
/* Clear the paused clients state if needed. */
...
...
@@ -1466,6 +1478,7 @@ void whileBlockedCron() {
if
(
prepareForShutdown
(
SHUTDOWN_NOSAVE
)
==
C_OK
)
exit
(
0
);
serverLog
(
LL_WARNING
,
"SIGTERM received but errors trying to shut down the server, check the logs for more information"
);
server
.
shutdown_asap
=
0
;
server
.
last_sig_received
=
0
;
}
}
...
...
@@ -1509,6 +1522,8 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
uint64_t
processed
=
0
;
processed
+=
handleClientsWithPendingReadsUsingThreads
();
processed
+=
tlsProcessPendingData
();
if
(
server
.
aof_state
==
AOF_ON
||
server
.
aof_state
==
AOF_WAIT_REWRITE
)
flushAppendOnlyFile
(
0
);
processed
+=
handleClientsWithPendingWrites
();
processed
+=
freeClientsInAsyncFreeQueue
();
server
.
events_processed_while_blocked
+=
processed
;
...
...
@@ -1584,15 +1599,21 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
* client side caching protocol in broadcasting (BCAST) mode. */
trackingBroadcastInvalidationMessages
();
/* Write the AOF buffer on disk */
/* Try to process blocked clients every once in while.
*
* Example: A module calls RM_SignalKeyAsReady from within a timer callback
* (So we don't visit processCommand() at all).
*
* must be done before flushAppendOnlyFile, in case of appendfsync=always,
* since the unblocked clients may write data. */
handleClientsBlockedOnKeys
();
/* Write the AOF buffer on disk,
* must be done before handleClientsWithPendingWritesUsingThreads,
* in case of appendfsync=always. */
if
(
server
.
aof_state
==
AOF_ON
||
server
.
aof_state
==
AOF_WAIT_REWRITE
)
flushAppendOnlyFile
(
0
);
/* Try to process blocked clients every once in while. Example: A module
* calls RM_SignalKeyAsReady from within a timer callback (So we don't
* visit processCommand() at all). */
handleClientsBlockedOnKeys
();
/* Handle writes with pending output buffers. */
handleClientsWithPendingWritesUsingThreads
();
...
...
@@ -1878,15 +1899,6 @@ void initServerConfig(void) {
appendServerSaveParams
(
300
,
100
);
/* save after 5 minutes and 100 changes */
appendServerSaveParams
(
60
,
10000
);
/* save after 1 minute and 10000 changes */
/* Specify the allocation function for the hdr histogram */
hdrAllocFuncs
hdrallocfn
=
{
.
mallocFn
=
zmalloc
,
.
callocFn
=
zcalloc_num
,
.
reallocFn
=
zrealloc
,
.
freeFn
=
zfree
,
};
hdrSetAllocators
(
&
hdrallocfn
);
/* Replication related */
server
.
masterhost
=
NULL
;
server
.
masterport
=
6379
;
...
...
@@ -2286,6 +2298,7 @@ int listenToPort(int port, socketFds *sfd) {
closeSocketListeners
(
sfd
);
return
C_ERR
;
}
if
(
server
.
socket_mark_id
>
0
)
anetSetSockMarkId
(
NULL
,
sfd
->
fd
[
sfd
->
count
],
server
.
socket_mark_id
);
anetNonBlock
(
NULL
,
sfd
->
fd
[
sfd
->
count
]);
anetCloexec
(
sfd
->
fd
[
sfd
->
count
]);
sfd
->
count
++
;
...
...
@@ -2338,6 +2351,7 @@ void resetServerStats(void) {
}
server
.
stat_aof_rewrites
=
0
;
server
.
stat_rdb_saves
=
0
;
server
.
stat_aofrw_consecutive_failures
=
0
;
atomicSet
(
server
.
stat_net_input_bytes
,
0
);
atomicSet
(
server
.
stat_net_output_bytes
,
0
);
server
.
stat_unexpected_error_replies
=
0
;
...
...
@@ -2539,6 +2553,7 @@ void initServer(void) {
server
.
aof_last_write_status
=
C_OK
;
server
.
aof_last_write_errno
=
0
;
server
.
repl_good_slaves_count
=
0
;
server
.
last_sig_received
=
0
;
/* Create the timer callback, this is our way to process many background
* operations incrementally, like clients timeout, eviction of unaccessed
...
...
@@ -2978,6 +2993,11 @@ struct redisCommand *lookupCommandOrOriginal(robj **argv ,int argc) {
return
cmd
;
}
/* Commands arriving from the master client or AOF client, should never be rejected. */
int
mustObeyClient
(
client
*
c
)
{
return
c
->
id
==
CLIENT_ID_AOF
||
c
->
flags
&
CLIENT_MASTER
;
}
static
int
shouldPropagate
(
int
target
)
{
if
(
!
server
.
replication_allowed
||
target
==
PROPAGATE_NONE
||
server
.
loading
)
return
0
;
...
...
@@ -3205,7 +3225,6 @@ int incrCommandStatsOnError(struct redisCommand *cmd, int flags) {
*/
void
call
(
client
*
c
,
int
flags
)
{
long
long
dirty
;
monotime
call_timer
;
uint64_t
client_old_flags
=
c
->
flags
;
struct
redisCommand
*
real_cmd
=
c
->
realcmd
;
...
...
@@ -3230,22 +3249,34 @@ void call(client *c, int flags) {
dirty
=
server
.
dirty
;
incrCommandStatsOnError
(
NULL
,
0
);
const
long
long
call_timer
=
ustime
();
/* Update cache time, in case we have nested calls we want to
* update only on the first call*/
if
(
server
.
fixed_time_expire
++
==
0
)
{
updateCachedTime
(
0
);
updateCachedTime
WithUs
(
0
,
call_timer
);
}
server
.
in_nested_call
++
;
elapsedStart
(
&
call_timer
);
monotime
monotonic_start
=
0
;
if
(
monotonicGetType
()
==
MONOTONIC_CLOCK_HW
)
monotonic_start
=
getMonotonicUs
();
server
.
in_nested_call
++
;
c
->
cmd
->
proc
(
c
);
const
long
duration
=
elapsedUs
(
call_timer
);
server
.
in_nested_call
--
;
/* In order to avoid performance implication due to querying the clock using a system call 3 times,
* we use a monotonic clock, when we are sure its cost is very low, and fall back to non-monotonic call otherwise. */
ustime_t
duration
;
if
(
monotonicGetType
()
==
MONOTONIC_CLOCK_HW
)
duration
=
getMonotonicUs
()
-
monotonic_start
;
else
duration
=
ustime
()
-
call_timer
;
c
->
duration
=
duration
;
dirty
=
server
.
dirty
-
dirty
;
if
(
dirty
<
0
)
dirty
=
0
;
server
.
in_nested_call
--
;
/* Update failed command calls if required. */
if
(
!
incrCommandStatsOnError
(
real_cmd
,
ERROR_COMMAND_FAILED
)
&&
c
->
deferred_reply_errors
)
{
...
...
@@ -3410,6 +3441,8 @@ void rejectCommand(client *c, robj *reply) {
}
void
rejectCommandSds
(
client
*
c
,
sds
s
)
{
flagTransaction
(
c
);
if
(
c
->
cmd
)
c
->
cmd
->
rejected_calls
++
;
if
(
c
->
cmd
&&
c
->
cmd
->
proc
==
execCommand
)
{
execCommandAbort
(
c
,
s
);
sdsfree
(
s
);
...
...
@@ -3420,8 +3453,6 @@ void rejectCommandSds(client *c, sds s) {
}
void
rejectCommandFormat
(
client
*
c
,
const
char
*
fmt
,
...)
{
if
(
c
->
cmd
)
c
->
cmd
->
rejected_calls
++
;
flagTransaction
(
c
);
va_list
ap
;
va_start
(
ap
,
fmt
);
sds
s
=
sdscatvprintf
(
sdsempty
(),
fmt
,
ap
);
...
...
@@ -3475,6 +3506,54 @@ void populateCommandMovableKeys(struct redisCommand *cmd) {
cmd
->
flags
|=
CMD_MOVABLE_KEYS
;
}
/* Check if c->cmd exists, fills `err` with details in case it doesn't.
* Return 1 if exists. */
int
commandCheckExistence
(
client
*
c
,
sds
*
err
)
{
if
(
c
->
cmd
)
return
1
;
if
(
!
err
)
return
0
;
if
(
isContainerCommandBySds
(
c
->
argv
[
0
]
->
ptr
))
{
/* If we can't find the command but argv[0] by itself is a command
* it means we're dealing with an invalid subcommand. Print Help. */
sds
cmd
=
sdsnew
((
char
*
)
c
->
argv
[
0
]
->
ptr
);
sdstoupper
(
cmd
);
*
err
=
sdsnew
(
NULL
);
*
err
=
sdscatprintf
(
*
err
,
"unknown subcommand '%.128s'. Try %s HELP."
,
(
char
*
)
c
->
argv
[
1
]
->
ptr
,
cmd
);
sdsfree
(
cmd
);
}
else
{
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
);
*
err
=
sdsnew
(
NULL
);
*
err
=
sdscatprintf
(
*
err
,
"unknown command '%.128s', with args beginning with: %s"
,
(
char
*
)
c
->
argv
[
0
]
->
ptr
,
args
);
sdsfree
(
args
);
}
/* Make sure there are no newlines in the string, otherwise invalid protocol
* is emitted (The args come from the user, they may contain any character). */
sdsmapchars
(
*
err
,
"
\r\n
"
,
" "
,
2
);
return
0
;
}
/* Check if c->argc is valid for c->cmd, fills `err` with details in case it isn't.
* Return 1 if valid. */
int
commandCheckArity
(
client
*
c
,
sds
*
err
)
{
if
((
c
->
cmd
->
arity
>
0
&&
c
->
cmd
->
arity
!=
c
->
argc
)
||
(
c
->
argc
<
-
c
->
cmd
->
arity
))
{
if
(
err
)
{
*
err
=
sdsnew
(
NULL
);
*
err
=
sdscatprintf
(
*
err
,
"wrong number of arguments for '%s' command"
,
c
->
cmd
->
fullname
);
}
return
0
;
}
return
1
;
}
/* If this function gets called we already read a whole
* command, arguments are in the client argv/argc fields.
* processCommand() execute the command or prepare the
...
...
@@ -3514,29 +3593,13 @@ int processCommand(client *c) {
/* Now lookup the command and check ASAP about trivial error conditions
* such as wrong arity, bad command name and so forth. */
c
->
cmd
=
c
->
lastcmd
=
c
->
realcmd
=
lookupCommand
(
c
->
argv
,
c
->
argc
);
if
(
!
c
->
cmd
)
{
if
(
isContainerCommandBySds
(
c
->
argv
[
0
]
->
ptr
))
{
/* If we can't find the command but argv[0] by itself is a command
* it means we're dealing with an invalid subcommand. Print Help. */
sds
cmd
=
sdsnew
((
char
*
)
c
->
argv
[
0
]
->
ptr
);
sdstoupper
(
cmd
);
rejectCommandFormat
(
c
,
"Unknown subcommand '%.128s'. Try %s HELP."
,
(
char
*
)
c
->
argv
[
1
]
->
ptr
,
cmd
);
sdsfree
(
cmd
);
return
C_OK
;
}
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
);
rejectCommandFormat
(
c
,
"unknown command '%s', with args beginning with: %s"
,
(
char
*
)
c
->
argv
[
0
]
->
ptr
,
args
);
sdsfree
(
args
);
sds
err
;
if
(
!
commandCheckExistence
(
c
,
&
err
))
{
rejectCommandSds
(
c
,
err
);
return
C_OK
;
}
else
if
((
c
->
cmd
->
arity
>
0
&&
c
->
cmd
->
arity
!=
c
->
argc
)
||
(
c
->
argc
<
-
c
->
cmd
->
arity
))
{
rejectCommandFormat
(
c
,
"wrong number of arguments for '%s' command"
,
c
->
cmd
->
fullname
);
}
if
(
!
commandCheckArity
(
c
,
&
err
))
{
rejectCommandSds
(
c
,
err
);
return
C_OK
;
}
...
...
@@ -3569,6 +3632,7 @@ int processCommand(client *c) {
(
c
->
cmd
->
proc
==
execCommand
&&
(
c
->
mstate
.
cmd_flags
&
(
CMD_WRITE
|
CMD_MAY_REPLICATE
)));
int
is_deny_async_loading_command
=
(
c
->
cmd
->
flags
&
CMD_NO_ASYNC_LOADING
)
||
(
c
->
cmd
->
proc
==
execCommand
&&
(
c
->
mstate
.
cmd_flags
&
CMD_NO_ASYNC_LOADING
));
int
obey_client
=
mustObeyClient
(
c
);
if
(
authRequired
(
c
))
{
/* AUTH and HELLO and no auth commands are valid even in
...
...
@@ -3620,23 +3684,20 @@ int processCommand(client *c) {
* 1) The sender of this command is our master.
* 2) The command has no key arguments. */
if
(
server
.
cluster_enabled
&&
!
(
c
->
flags
&
CLIENT_MASTER
)
&&
!
(
c
->
flags
&
CLIENT_SCRIPT
&&
server
.
script_caller
->
flags
&
CLIENT_MASTER
)
&&
!
mustObeyClient
(
c
)
&&
!
(
!
(
c
->
cmd
->
flags
&
CMD_MOVABLE_KEYS
)
&&
c
->
cmd
->
key_specs_num
==
0
&&
c
->
cmd
->
proc
!=
execCommand
))
{
int
hashslot
;
int
error_code
;
clusterNode
*
n
=
getNodeByQuery
(
c
,
c
->
cmd
,
c
->
argv
,
c
->
argc
,
&
hash
slot
,
&
error_code
);
&
c
->
slot
,
&
error_code
);
if
(
n
==
NULL
||
n
!=
server
.
cluster
->
myself
)
{
if
(
c
->
cmd
->
proc
==
execCommand
)
{
discardTransaction
(
c
);
}
else
{
flagTransaction
(
c
);
}
clusterRedirectClient
(
c
,
n
,
hash
slot
,
error_code
);
clusterRedirectClient
(
c
,
n
,
c
->
slot
,
error_code
);
c
->
cmd
->
rejected_calls
++
;
return
C_OK
;
}
...
...
@@ -3706,15 +3767,29 @@ int processCommand(client *c) {
if
(
server
.
tracking_clients
)
trackingLimitUsedSlots
();
/* Don't accept write commands if there are problems persisting on disk
* and if this is a master instance. */
* unless coming from our master, in which case check the replica ignore
* disk write error config to either log or crash. */
int
deny_write_type
=
writeCommandsDeniedByDiskError
();
if
(
deny_write_type
!=
DISK_ERROR_TYPE_NONE
&&
server
.
masterhost
==
NULL
&&
(
is_write_command
||
c
->
cmd
->
proc
==
pingCommand
))
(
is_write_command
||
c
->
cmd
->
proc
==
pingCommand
))
{
sds
err
=
writeCommandsGetDiskErrorMessage
(
deny_write_type
);
rejectCommandSds
(
c
,
err
);
return
C_OK
;
if
(
obey_client
)
{
if
(
!
server
.
repl_ignore_disk_write_error
&&
c
->
cmd
->
proc
!=
pingCommand
)
{
serverPanic
(
"Replica was unable to write command to disk."
);
}
else
{
static
mstime_t
last_log_time_ms
=
0
;
const
mstime_t
log_interval_ms
=
10000
;
if
(
server
.
mstime
>
last_log_time_ms
+
log_interval_ms
)
{
last_log_time_ms
=
server
.
mstime
;
serverLog
(
LL_WARNING
,
"Replica is applying a command even though "
"it is unable to write to disk."
);
}
}
}
else
{
sds
err
=
writeCommandsGetDiskErrorMessage
(
deny_write_type
);
rejectCommandSds
(
c
,
err
);
return
C_OK
;
}
}
/* Don't accept write commands if there are not enough good slaves and
...
...
@@ -3727,7 +3802,7 @@ int processCommand(client *c) {
/* Don't accept write commands if this is a read only slave. But
* accept write commands if this is our master. */
if
(
server
.
masterhost
&&
server
.
repl_slave_ro
&&
!
(
c
->
flags
&
CLIENT_MASTER
)
&&
!
obey_client
&&
is_write_command
)
{
rejectCommand
(
c
,
shared
.
roslaveerr
);
...
...
@@ -3949,6 +4024,7 @@ static void cancelShutdown(void) {
server
.
shutdown_asap
=
0
;
server
.
shutdown_flags
=
0
;
server
.
shutdown_mstime
=
0
;
server
.
last_sig_received
=
0
;
replyToClientsBlockedOnShutdown
();
unpauseClients
(
PAUSE_DURING_SHUTDOWN
);
}
...
...
@@ -4309,6 +4385,7 @@ void addReplyCommandArgList(client *c, struct redisCommandArg *args, int num_arg
if
(
args
[
j
].
token
)
maplen
++
;
if
(
args
[
j
].
summary
)
maplen
++
;
if
(
args
[
j
].
since
)
maplen
++
;
if
(
args
[
j
].
deprecated_since
)
maplen
++
;
if
(
args
[
j
].
flags
)
maplen
++
;
if
(
args
[
j
].
type
==
ARG_TYPE_ONEOF
||
args
[
j
].
type
==
ARG_TYPE_BLOCK
)
maplen
++
;
...
...
@@ -4336,6 +4413,10 @@ void addReplyCommandArgList(client *c, struct redisCommandArg *args, int num_arg
addReplyBulkCString
(
c
,
"since"
);
addReplyBulkCString
(
c
,
args
[
j
].
since
);
}
if
(
args
[
j
].
deprecated_since
)
{
addReplyBulkCString
(
c
,
"deprecated_since"
);
addReplyBulkCString
(
c
,
args
[
j
].
deprecated_since
);
}
if
(
args
[
j
].
flags
)
{
addReplyBulkCString
(
c
,
"flags"
);
addReplyFlagsForArg
(
c
,
args
[
j
].
flags
);
...
...
@@ -4562,6 +4643,7 @@ void addReplyCommandDocs(client *c, struct redisCommand *cmd) {
long
maplen
=
1
;
if
(
cmd
->
summary
)
maplen
++
;
if
(
cmd
->
since
)
maplen
++
;
if
(
cmd
->
flags
&
CMD_MODULE
)
maplen
++
;
if
(
cmd
->
complexity
)
maplen
++
;
if
(
cmd
->
doc_flags
)
maplen
++
;
if
(
cmd
->
deprecated_since
)
maplen
++
;
...
...
@@ -4588,6 +4670,10 @@ void addReplyCommandDocs(client *c, struct redisCommand *cmd) {
addReplyBulkCString
(
c
,
"complexity"
);
addReplyBulkCString
(
c
,
cmd
->
complexity
);
}
if
(
cmd
->
flags
&
CMD_MODULE
)
{
addReplyBulkCString
(
c
,
"module"
);
addReplyBulkCString
(
c
,
moduleNameFromCommand
(
cmd
));
}
if
(
cmd
->
doc_flags
)
{
addReplyBulkCString
(
c
,
"doc_flags"
);
addReplyDocFlagsForCommand
(
c
,
cmd
);
...
...
@@ -5123,6 +5209,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
"redis_mode:%s
\r\n
"
"os:%s %s %s
\r\n
"
"arch_bits:%i
\r\n
"
"monotonic_clock:%s
\r\n
"
"multiplexing_api:%s
\r\n
"
"atomicvar_api:%s
\r\n
"
"gcc_version:%i.%i.%i
\r\n
"
...
...
@@ -5146,6 +5233,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
mode
,
name
.
sysname
,
name
.
release
,
name
.
machine
,
server
.
arch_bits
,
monotonicInfoString
(),
aeGetApiName
(),
REDIS_ATOMIC_API
,
#ifdef __GNUC__
...
...
@@ -5383,6 +5471,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
"aof_current_rewrite_time_sec:%jd
\r\n
"
"aof_last_bgrewrite_status:%s
\r\n
"
"aof_rewrites:%lld
\r\n
"
"aof_rewrites_consecutive_failures:%lld
\r\n
"
"aof_last_write_status:%s
\r\n
"
"aof_last_cow_size:%zu
\r\n
"
"module_fork_in_progress:%d
\r\n
"
...
...
@@ -5414,6 +5503,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
-
1
:
time
(
NULL
)
-
server
.
aof_rewrite_time_start
),
(
server
.
aof_lastbgrewrite_status
==
C_OK
)
?
"ok"
:
"err"
,
server
.
stat_aof_rewrites
,
server
.
stat_aofrw_consecutive_failures
,
(
server
.
aof_last_write_status
==
C_OK
&&
aof_bio_fsync_status
==
C_OK
)
?
"ok"
:
"err"
,
server
.
stat_aof_cow_bytes
,
...
...
@@ -6227,6 +6317,7 @@ static void sigShutdownHandler(int sig) {
serverLogFromHandler
(
LL_WARNING
,
msg
);
server
.
shutdown_asap
=
1
;
server
.
last_sig_received
=
sig
;
}
void
setupSignalHandlers
(
void
)
{
...
...
src/server.h
View file @
d375595d
...
...
@@ -603,6 +603,7 @@ typedef enum {
#define NOTIFY_KEY_MISS (1<<11)
/* m (Note: This one is excluded from NOTIFY_ALL on purpose) */
#define NOTIFY_LOADED (1<<12)
/* module only key space notification, indicate a key loaded from rdb */
#define NOTIFY_MODULE (1<<13)
/* d, module key space notification */
#define NOTIFY_NEW (1<<14)
/* n, new key notification */
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE)
/* A flag */
/* Using the following macro you can run code inside serverCron() with the
...
...
@@ -1061,7 +1062,7 @@ typedef struct replBacklog {
listNode
*
ref_repl_buf_node
;
/* Referenced node of replication buffer blocks,
* see the definition of replBufBlock. */
size_t
unindexed_count
;
/* The count from last creating index block. */
rax
*
blocks_index
;
/* The index of re
o
crded blocks of replication
rax
*
blocks_index
;
/* The index of rec
o
rded blocks of replication
* buffer for quickly searching replication
* offset on partial resynchronization. */
long
long
histlen
;
/* Backlog actual data length */
...
...
@@ -1106,6 +1107,7 @@ typedef struct client {
buffer or object being sent. */
time_t
ctime
;
/* Client creation time. */
long
duration
;
/* Current command duration. Used for measuring latency of blocking/non-blocking cmds */
int
slot
;
/* The slot the client is executing against. Set to -1 if no slot is being used */
time_t
lastinteraction
;
/* Time of the last interaction, used for timeout */
time_t
obuf_soft_limit_reached_time
;
uint64_t
flags
;
/* Client flags: CLIENT_* macros. */
...
...
@@ -1323,6 +1325,15 @@ struct redisMemOverhead {
}
*
db
;
};
/* Replication error behavior determines the replica behavior
* when it receives an error over the replication stream. In
* either case the error is logged. */
typedef
enum
{
PROPAGATION_ERR_BEHAVIOR_IGNORE
=
0
,
PROPAGATION_ERR_BEHAVIOR_PANIC
,
PROPAGATION_ERR_BEHAVIOR_PANIC_ON_REPLICAS
}
replicationErrorBehavior
;
/* This structure can be optionally passed to RDB save/load functions in
* order to implement additional functionalities, by storing and loading
* metadata to the RDB file.
...
...
@@ -1451,6 +1462,7 @@ struct redisServer {
redisAtomic
unsigned
int
lruclock
;
/* Clock for LRU eviction */
volatile
sig_atomic_t
shutdown_asap
;
/* Shutdown ordered by signal handler. */
mstime_t
shutdown_mstime
;
/* Timestamp to limit graceful shutdown. */
int
last_sig_received
;
/* Indicates the last SIGNAL received, if any (e.g., SIGINT or SIGTERM). */
int
shutdown_flags
;
/* Flags passed to prepareForShutdown(). */
int
activerehashing
;
/* Incremental rehash in serverCron() */
int
active_defrag_running
;
/* Active defragmentation running (holds current scan aggressiveness) */
...
...
@@ -1493,6 +1505,7 @@ struct redisServer {
socketFds
ipfd
;
/* TCP socket file descriptors */
socketFds
tlsfd
;
/* TLS socket file descriptors */
int
sofd
;
/* Unix socket file descriptor */
uint32_t
socket_mark_id
;
/* ID for listen socket marking */
socketFds
cfd
;
/* Cluster bus listening socket */
list
*
clients
;
/* List of active clients */
list
*
clients_to_close
;
/* Clients to close asynchronously */
...
...
@@ -1555,6 +1568,7 @@ struct redisServer {
monotime
stat_last_active_defrag_time
;
/* Timestamp of current active defrag start */
size_t
stat_peak_memory
;
/* Max used memory record */
long
long
stat_aof_rewrites
;
/* number of aof file rewrites performed */
long
long
stat_aofrw_consecutive_failures
;
/* The number of consecutive failures of aofrw */
long
long
stat_rdb_saves
;
/* number of rdb saves performed */
long
long
stat_fork_time
;
/* Time needed to perform latest fork() */
double
stat_fork_rate
;
/* Fork rate in GB/sec. */
...
...
@@ -1717,6 +1731,8 @@ struct redisServer {
* abort(). useful for Valgrind. */
/* Shutdown */
int
shutdown_timeout
;
/* Graceful shutdown time limit in seconds. */
int
shutdown_on_sigint
;
/* Shutdown flags configured for SIGINT. */
int
shutdown_on_sigterm
;
/* Shutdown flags configured for SIGTERM. */
/* Replication (master) */
char
replid
[
CONFIG_RUN_ID_SIZE
+
1
];
/* My current replication ID. */
...
...
@@ -1769,6 +1785,10 @@ struct redisServer {
int
replica_announced
;
/* If true, replica is announced by Sentinel */
int
slave_announce_port
;
/* Give the master this listening port. */
char
*
slave_announce_ip
;
/* Give the master this ip address. */
int
propagation_error_behavior
;
/* Configures the behavior of the replica
* when it receives an error on the replication stream */
int
repl_ignore_disk_write_error
;
/* Configures whether replicas panic when unable to
* persist writes to AOF. */
/* The following two fields is where we store master PSYNC replid/offset
* while the PSYNC is in progress. At the end we'll copy the fields into
* the server->master client structure. */
...
...
@@ -2042,6 +2062,7 @@ typedef struct redisCommandArg {
const
char
*
summary
;
const
char
*
since
;
int
flags
;
const
char
*
deprecated_since
;
struct
redisCommandArg
*
subargs
;
/* runtime populated data */
int
num_args
;
...
...
@@ -2349,6 +2370,7 @@ int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int ar
moduleType
*
moduleTypeLookupModuleByID
(
uint64_t
id
);
void
moduleTypeNameByID
(
char
*
name
,
uint64_t
moduleid
);
const
char
*
moduleTypeModuleName
(
moduleType
*
mt
);
const
char
*
moduleNameFromCommand
(
struct
redisCommand
*
cmd
);
void
moduleFreeContext
(
struct
RedisModuleCtx
*
ctx
);
void
unblockClientFromModule
(
client
*
c
);
void
moduleHandleBlockedClients
(
void
);
...
...
@@ -2509,7 +2531,7 @@ void unprotectClient(client *c);
void
initThreadedIO
(
void
);
client
*
lookupClientByID
(
uint64_t
id
);
int
authRequired
(
client
*
c
);
void
c
lientIn
stallWriteHandler
(
client
*
c
);
void
putC
lientIn
PendingWriteQueue
(
client
*
c
);
#ifdef __GNUC__
void
addReplyErrorFormatEx
(
client
*
c
,
int
flags
,
const
char
*
fmt
,
...)
...
...
@@ -2860,6 +2882,8 @@ struct redisCommand *lookupCommandBySds(sds s);
struct
redisCommand
*
lookupCommandByCStringLogic
(
dict
*
commands
,
const
char
*
s
);
struct
redisCommand
*
lookupCommandByCString
(
const
char
*
s
);
struct
redisCommand
*
lookupCommandOrOriginal
(
robj
**
argv
,
int
argc
);
int
commandCheckExistence
(
client
*
c
,
sds
*
err
);
int
commandCheckArity
(
client
*
c
,
sds
*
err
);
void
startCommandExecution
();
int
incrCommandStatsOnError
(
struct
redisCommand
*
cmd
,
int
flags
);
void
call
(
client
*
c
,
int
flags
);
...
...
@@ -2877,7 +2901,7 @@ int prepareForShutdown(int flags);
void
replyToClientsBlockedOnShutdown
(
void
);
int
abortShutdown
(
void
);
void
afterCommand
(
client
*
c
);
int
inNestedCall
(
void
);
int
mustObeyClient
(
client
*
c
);
#ifdef __GNUC__
void
_serverLog
(
int
level
,
const
char
*
fmt
,
...)
__attribute__
((
format
(
printf
,
2
,
3
)));
...
...
@@ -2962,8 +2986,8 @@ int pubsubUnsubscribeAllChannels(client *c, int notify);
int
pubsubUnsubscribeShardAllChannels
(
client
*
c
,
int
notify
);
void
pubsubUnsubscribeShardChannels
(
robj
**
channels
,
unsigned
int
count
);
int
pubsubUnsubscribeAllPatterns
(
client
*
c
,
int
notify
);
int
pubsubPublishMessage
(
robj
*
channel
,
robj
*
message
);
int
pubsubPublishMessage
Shard
(
robj
*
channel
,
robj
*
message
);
int
pubsubPublishMessage
(
robj
*
channel
,
robj
*
message
,
int
sharded
);
int
pubsubPublishMessage
AndPropagateToCluster
(
robj
*
channel
,
robj
*
message
,
int
sharded
);
void
addReplyPubsubMessage
(
client
*
c
,
robj
*
channel
,
robj
*
msg
);
int
serverPubsubSubscriptionCount
();
int
serverPubsubShardSubscriptionCount
();
...
...
src/t_hash.c
View file @
d375595d
...
...
@@ -146,39 +146,24 @@ robj *hashTypeGetValueObject(robj *o, sds field) {
* exist. */
size_t
hashTypeGetValueLength
(
robj
*
o
,
sds
field
)
{
size_t
len
=
0
;
if
(
o
->
encoding
==
OBJ_ENCODING_LISTPACK
)
{
unsigned
char
*
vstr
=
NULL
;
unsigned
int
vlen
=
UINT_MAX
;
long
long
vll
=
LLONG_MAX
;
unsigned
char
*
vstr
=
NULL
;
unsigned
int
vlen
=
UINT_MAX
;
long
long
vll
=
LLONG_MAX
;
if
(
hashTypeGetFromListpack
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
)
==
0
)
len
=
vstr
?
vlen
:
sdigits10
(
vll
);
}
else
if
(
o
->
encoding
==
OBJ_ENCODING_HT
)
{
sds
aux
;
if
(
hashTypeGetValue
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
)
==
C_OK
)
len
=
vstr
?
vlen
:
sdigits10
(
vll
);
if
((
aux
=
hashTypeGetFromHashTable
(
o
,
field
))
!=
NULL
)
len
=
sdslen
(
aux
);
}
else
{
serverPanic
(
"Unknown hash encoding"
);
}
return
len
;
}
/* Test if the specified field exists in the given hash. Returns 1 if the field
* exists, and 0 when it doesn't. */
int
hashTypeExists
(
robj
*
o
,
sds
field
)
{
if
(
o
->
encoding
==
OBJ_ENCODING_LISTPACK
)
{
unsigned
char
*
vstr
=
NULL
;
unsigned
int
vlen
=
UINT_MAX
;
long
long
vll
=
LLONG_MAX
;
unsigned
char
*
vstr
=
NULL
;
unsigned
int
vlen
=
UINT_MAX
;
long
long
vll
=
LLONG_MAX
;
if
(
hashTypeGetFromListpack
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
)
==
0
)
return
1
;
}
else
if
(
o
->
encoding
==
OBJ_ENCODING_HT
)
{
if
(
hashTypeGetFromHashTable
(
o
,
field
)
!=
NULL
)
return
1
;
}
else
{
serverPanic
(
"Unknown hash encoding"
);
}
return
0
;
return
hashTypeGetValue
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
)
==
C_OK
;
}
/* Add a new field, overwrite the old with the new value if it already exists.
...
...
@@ -205,6 +190,14 @@ int hashTypeExists(robj *o, sds field) {
int
hashTypeSet
(
robj
*
o
,
sds
field
,
sds
value
,
int
flags
)
{
int
update
=
0
;
/* Check if the field is too long for listpack, and convert before adding the item.
* This is needed for HINCRBY* case since in other commands this is handled early by
* hashTypeTryConversion, so this check will be a NOP. */
if
(
o
->
encoding
==
OBJ_ENCODING_LISTPACK
)
{
if
(
sdslen
(
field
)
>
server
.
hash_max_listpack_value
||
sdslen
(
value
)
>
server
.
hash_max_listpack_value
)
hashTypeConvert
(
o
,
OBJ_ENCODING_HT
);
}
if
(
o
->
encoding
==
OBJ_ENCODING_LISTPACK
)
{
unsigned
char
*
zl
,
*
fptr
,
*
vptr
;
...
...
@@ -717,37 +710,23 @@ void hincrbyfloatCommand(client *c) {
}
static
void
addHashFieldToReply
(
client
*
c
,
robj
*
o
,
sds
field
)
{
int
ret
;
if
(
o
==
NULL
)
{
addReplyNull
(
c
);
return
;
}
if
(
o
->
encoding
==
OBJ_ENCODING_LISTPACK
)
{
unsigned
char
*
vstr
=
NULL
;
unsigned
int
vlen
=
UINT_MAX
;
long
long
vll
=
LLONG_MAX
;
unsigned
char
*
vstr
=
NULL
;
unsigned
int
vlen
=
UINT_MAX
;
long
long
vll
=
LLONG_MAX
;
ret
=
hashTypeGet
FromListpack
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
)
;
if
(
ret
<
0
)
{
addReply
N
ul
l
(
c
);
if
(
hashTypeGet
Value
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
)
==
C_OK
)
{
if
(
vstr
)
{
addReply
B
ul
kCBuffer
(
c
,
vstr
,
vlen
);
}
else
{
if
(
vstr
)
{
addReplyBulkCBuffer
(
c
,
vstr
,
vlen
);
}
else
{
addReplyBulkLongLong
(
c
,
vll
);
}
addReplyBulkLongLong
(
c
,
vll
);
}
}
else
if
(
o
->
encoding
==
OBJ_ENCODING_HT
)
{
sds
value
=
hashTypeGetFromHashTable
(
o
,
field
);
if
(
value
==
NULL
)
addReplyNull
(
c
);
else
addReplyBulkCBuffer
(
c
,
value
,
sdslen
(
value
));
}
else
{
serverPanic
(
"Unknown hash encoding"
);
addReplyNull
(
c
);
}
}
...
...
@@ -907,7 +886,7 @@ void hscanCommand(client *c) {
scanGenericCommand
(
c
,
o
,
cursor
);
}
static
void
h
a
rndfieldReplyWithListpack
(
client
*
c
,
unsigned
int
count
,
listpackEntry
*
keys
,
listpackEntry
*
vals
)
{
static
void
hr
a
ndfieldReplyWithListpack
(
client
*
c
,
unsigned
int
count
,
listpackEntry
*
keys
,
listpackEntry
*
vals
)
{
for
(
unsigned
long
i
=
0
;
i
<
count
;
i
++
)
{
if
(
vals
&&
c
->
resp
>
2
)
addReplyArrayLen
(
c
,
2
);
...
...
@@ -990,7 +969,7 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
sample_count
=
count
>
limit
?
limit
:
count
;
count
-=
sample_count
;
lpRandomPairs
(
hash
->
ptr
,
sample_count
,
keys
,
vals
);
h
a
rndfieldReplyWithListpack
(
c
,
sample_count
,
keys
,
vals
);
hr
a
ndfieldReplyWithListpack
(
c
,
sample_count
,
keys
,
vals
);
}
zfree
(
keys
);
zfree
(
vals
);
...
...
@@ -1092,7 +1071,7 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
if
(
withvalues
)
vals
=
zmalloc
(
sizeof
(
listpackEntry
)
*
count
);
serverAssert
(
lpRandomPairsUnique
(
hash
->
ptr
,
count
,
keys
,
vals
)
==
count
);
h
a
rndfieldReplyWithListpack
(
c
,
count
,
keys
,
vals
);
hr
a
ndfieldReplyWithListpack
(
c
,
count
,
keys
,
vals
);
zfree
(
keys
);
zfree
(
vals
);
return
;
...
...
src/t_stream.c
View file @
d375595d
...
...
@@ -1000,7 +1000,7 @@ static int streamParseAddOrTrimArgsOrReply(client *c, streamAddTrimArgs *args, i
return
-
1
;
}
if
(
c
==
server
.
master
||
c
->
id
==
CLIENT_ID_AOF
)
{
if
(
mustObeyClient
(
c
)
)
{
/* If command came from master or from AOF we must not enforce maxnodes
* (The maxlen/minid argument was re-written to make sure there's no
* inconsistency). */
...
...
@@ -1370,24 +1370,35 @@ void streamLastValidID(stream *s, streamID *maxid)
streamIteratorStop
(
&
si
);
}
/* Maximum size for a stream ID string. In theory 20*2+1 should be enough,
* But to avoid chance for off by one issues and null-term, in case this will
* be used as parsing buffer, we use a slightly larger buffer. On the other
* hand considering sds header is gonna add 4 bytes, we wanna keep below the
* allocator's 48 bytes bin. */
#define STREAM_ID_STR_LEN 44
sds
createStreamIDString
(
streamID
*
id
)
{
/* Optimization: pre-allocate a big enough buffer to avoid reallocs. */
sds
str
=
sdsnewlen
(
SDS_NOINIT
,
STREAM_ID_STR_LEN
);
sdssetlen
(
str
,
0
);
return
sdscatfmt
(
str
,
"%U-%U"
,
id
->
ms
,
id
->
seq
);
}
/* Emit a reply in the client output buffer by formatting a Stream ID
* in the standard <ms>-<seq> format, using the simple string protocol
* of REPL. */
void
addReplyStreamID
(
client
*
c
,
streamID
*
id
)
{
sds
replyid
=
sdscatfmt
(
sdsempty
(),
"%U-%U"
,
id
->
ms
,
id
->
seq
);
addReplyBulkSds
(
c
,
replyid
);
addReplyBulkSds
(
c
,
createStreamIDString
(
id
));
}
void
setDeferredReplyStreamID
(
client
*
c
,
void
*
dr
,
streamID
*
id
)
{
sds
replyid
=
sdscatfmt
(
sdsempty
(),
"%U-%U"
,
id
->
ms
,
id
->
seq
);
setDeferredReplyBulkSds
(
c
,
dr
,
replyid
);
setDeferredReplyBulkSds
(
c
,
dr
,
createStreamIDString
(
id
));
}
/* Similar to the above function, but just creates an object, usually useful
* for replication purposes to create arguments. */
robj
*
createObjectFromStreamID
(
streamID
*
id
)
{
return
createObject
(
OBJ_STRING
,
sdscatfmt
(
sdsempty
(),
"%U-%U"
,
id
->
ms
,
id
->
seq
));
return
createObject
(
OBJ_STRING
,
createStreamIDString
(
id
));
}
/* Returns non-zero if the ID is 0-0. */
...
...
@@ -2025,7 +2036,8 @@ void xaddCommand(client *c) {
addReplyError
(
c
,
"Elements are too large to be stored"
);
return
;
}
addReplyStreamID
(
c
,
&
id
);
sds
replyid
=
createStreamIDString
(
&
id
);
addReplyBulkCBuffer
(
c
,
replyid
,
sdslen
(
replyid
));
signalModifiedKey
(
c
,
c
->
db
,
c
->
argv
[
1
]);
notifyKeyspaceEvent
(
NOTIFY_STREAM
,
"xadd"
,
c
->
argv
[
1
],
c
->
db
->
id
);
...
...
@@ -2050,9 +2062,11 @@ void xaddCommand(client *c) {
/* Let's rewrite the ID argument with the one actually generated for
* AOF/replication propagation. */
if
(
!
parsed_args
.
id_given
||
!
parsed_args
.
seq_given
)
{
robj
*
idarg
=
createObject
FromStreamID
(
&
id
);
robj
*
idarg
=
createObject
(
OBJ_STRING
,
reply
id
);
rewriteClientCommandArgument
(
c
,
idpos
,
idarg
);
decrRefCount
(
idarg
);
}
else
{
sdsfree
(
replyid
);
}
/* We need to signal to blocked clients that there is new data on this
...
...
src/t_string.c
View file @
d375595d
...
...
@@ -38,7 +38,7 @@ int getGenericCommand(client *c);
*----------------------------------------------------------------------------*/
static
int
checkStringLength
(
client
*
c
,
long
long
size
)
{
if
(
!
(
c
->
flags
&
CLIENT_MASTER
)
&&
size
>
server
.
proto_max_bulk_len
)
{
if
(
!
mustObeyClient
(
c
)
&&
size
>
server
.
proto_max_bulk_len
)
{
addReplyError
(
c
,
"string exceeds maximum allowed size (proto-max-bulk-len)"
);
return
C_ERR
;
}
...
...
@@ -792,7 +792,7 @@ void lcsCommand(client *c) {
/* Setup an uint32_t array to store at LCS[i,j] the length of the
* LCS A0..i-1, B0..j-1. Note that we have a linear array here, so
* we index it as LCS[j+(blen+1)*
j
] */
* we index it as LCS[j+(blen+1)*
i
] */
#define LCS(A,B) lcs[(B)+((A)*(blen+1))]
/* Try to allocate the LCS table, and abort on overflow or insufficient memory. */
...
...
src/t_zset.c
View file @
d375595d
...
...
@@ -1029,17 +1029,25 @@ unsigned char *zzlInsertAt(unsigned char *zl, unsigned char *eptr, sds ele, doub
unsigned
char
*
sptr
;
char
scorebuf
[
MAX_D2STRING_CHARS
];
int
scorelen
;
scorelen
=
d2string
(
scorebuf
,
sizeof
(
scorebuf
),
score
);
long
long
lscore
;
int
score_is_long
=
double2ll
(
score
,
&
lscore
);
if
(
!
score_is_long
)
scorelen
=
d2string
(
scorebuf
,
sizeof
(
scorebuf
),
score
);
if
(
eptr
==
NULL
)
{
zl
=
lpAppend
(
zl
,(
unsigned
char
*
)
ele
,
sdslen
(
ele
));
zl
=
lpAppend
(
zl
,(
unsigned
char
*
)
scorebuf
,
scorelen
);
if
(
score_is_long
)
zl
=
lpAppendInteger
(
zl
,
lscore
);
else
zl
=
lpAppend
(
zl
,(
unsigned
char
*
)
scorebuf
,
scorelen
);
}
else
{
/* Insert member before the element 'eptr'. */
zl
=
lpInsertString
(
zl
,(
unsigned
char
*
)
ele
,
sdslen
(
ele
),
eptr
,
LP_BEFORE
,
&
sptr
);
/* Insert score after the member. */
zl
=
lpInsertString
(
zl
,(
unsigned
char
*
)
scorebuf
,
scorelen
,
sptr
,
LP_AFTER
,
NULL
);
if
(
score_is_long
)
zl
=
lpInsertInteger
(
zl
,
lscore
,
sptr
,
LP_AFTER
,
NULL
);
else
zl
=
lpInsertString
(
zl
,(
unsigned
char
*
)
scorebuf
,
scorelen
,
sptr
,
LP_AFTER
,
NULL
);
}
return
zl
;
}
...
...
@@ -3964,7 +3972,7 @@ void zpopminCommand(client *c) {
zpopMinMaxCommand
(
c
,
ZSET_MIN
);
}
/* Z
MAX
POP key [<count>] */
/* ZPOP
MAX
key [<count>] */
void
zpopmaxCommand
(
client
*
c
)
{
zpopMinMaxCommand
(
c
,
ZSET_MAX
);
}
...
...
@@ -4351,12 +4359,12 @@ void zmpopGenericCommand(client *c, int numkeys_idx, int is_block) {
}
}
/* ZMPOP numkeys [<key> ...] MIN|MAX [COUNT count] */
/* ZMPOP numkeys
key
[<key> ...] MIN|MAX [COUNT count] */
void
zmpopCommand
(
client
*
c
)
{
zmpopGenericCommand
(
c
,
1
,
0
);
}
/* BZMPOP timeout numkeys [<key> ...] MIN|MAX [COUNT count] */
/* BZMPOP timeout numkeys
key
[<key> ...] MIN|MAX [COUNT count] */
void
bzmpopCommand
(
client
*
c
)
{
zmpopGenericCommand
(
c
,
2
,
1
);
}
src/timeout.c
View file @
d375595d
...
...
@@ -58,7 +58,7 @@ int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
if
(
server
.
maxidletime
&&
/* This handles the idle clients connection timeout if set. */
!
(
c
->
flags
&
CLIENT_SLAVE
)
&&
/* No timeout for slaves and monitors */
!
(
c
->
flags
&
CLIENT_MASTER
)
&&
/* No timeout for masters */
!
mustObeyClient
(
c
)
&&
/* No timeout for masters
and AOF
*/
!
(
c
->
flags
&
CLIENT_BLOCKED
)
&&
/* No timeout for BLPOP */
!
(
c
->
flags
&
CLIENT_PUBSUB
)
&&
/* No timeout for Pub/Sub clients */
(
now
-
c
->
lastinteraction
>
server
.
maxidletime
))
...
...
src/util.c
View file @
d375595d
...
...
@@ -552,6 +552,36 @@ int string2d(const char *s, size_t slen, double *dp) {
return
1
;
}
/* Returns 1 if the double value can safely be represented in long long without
* precision loss, in which case the corresponding long long is stored in the out variable. */
int
double2ll
(
double
d
,
long
long
*
out
)
{
#if (DBL_MANT_DIG >= 52) && (DBL_MANT_DIG <= 63) && (LLONG_MAX == 0x7fffffffffffffffLL)
/* Check if the float is in a safe range to be casted into a
* long long. We are assuming that long long is 64 bit here.
* Also we are assuming that there are no implementations around where
* double has precision < 52 bit.
*
* Under this assumptions we test if a double is inside a range
* where casting to long long is safe. Then using two castings we
* make sure the decimal part is zero. If all this is true we can use
* integer without precision loss.
*
* Note that numbers above 2^52 and below 2^63 use all the fraction bits as real part,
* and the exponent bits are positive, which means the "decimal" part must be 0.
* i.e. all double values in that range are representable as a long without precision loss,
* but not all long values in that range can be represented as a double.
* we only care about the first part here. */
if
(
d
<
(
double
)(
-
LLONG_MAX
/
2
)
||
d
>
(
double
)(
LLONG_MAX
/
2
))
return
0
;
long
long
ll
=
d
;
if
(
ll
==
d
)
{
*
out
=
ll
;
return
1
;
}
#endif
return
0
;
}
/* Convert a double to a string representation. Returns the number of bytes
* required. The representation should always be parsable by strtod(3).
* This function does not support human-friendly formatting like ld2string
...
...
@@ -572,22 +602,11 @@ int d2string(char *buf, size_t len, double value) {
else
len
=
snprintf
(
buf
,
len
,
"0"
);
}
else
{
#if (DBL_MANT_DIG >= 52) && (LLONG_MAX == 0x7fffffffffffffffLL)
/* Check if the float is in a safe range to be casted into a
* long long. We are assuming that long long is 64 bit here.
* Also we are assuming that there are no implementations around where
* double has precision < 52 bit.
*
* Under this assumptions we test if a double is inside an interval
* where casting to long long is safe. Then using two castings we
* make sure the decimal part is zero. If all this is true we use
* integer printing function that is much faster. */
double
min
=
-
4503599627370495
;
/* (2^52)-1 */
double
max
=
4503599627370496
;
/* -(2^52) */
if
(
value
>
min
&&
value
<
max
&&
value
==
((
double
)((
long
long
)
value
)))
len
=
ll2string
(
buf
,
len
,(
long
long
)
value
);
long
long
lvalue
;
/* Integer printing function is much faster, check if we can safely use it. */
if
(
double2ll
(
value
,
&
lvalue
))
len
=
ll2string
(
buf
,
len
,
lvalue
);
else
#endif
len
=
snprintf
(
buf
,
len
,
"%.17g"
,
value
);
}
...
...
src/util.h
View file @
d375595d
...
...
@@ -75,6 +75,7 @@ int string2d(const char *s, size_t slen, double *dp);
int
trimDoubleString
(
char
*
buf
,
size_t
len
);
int
d2string
(
char
*
buf
,
size_t
len
,
double
value
);
int
ld2string
(
char
*
buf
,
size_t
len
,
long
double
value
,
ld2string_mode
mode
);
int
double2ll
(
double
d
,
long
long
*
out
);
int
yesnotoi
(
char
*
s
);
sds
getAbsolutePath
(
char
*
filename
);
long
getTimeZone
(
void
);
...
...
src/version.h
View file @
d375595d
#define REDIS_VERSION "
6.9.242
"
#define REDIS_VERSION_NUM 0x000
609f2
#define REDIS_VERSION "
7.0.0
"
#define REDIS_VERSION_NUM 0x000
70000
tests/assets/default.conf
View file @
d375595d
...
...
@@ -30,3 +30,5 @@ activerehashing yes
enable
-
protected
-
configs
yes
enable
-
debug
-
command
yes
enable
-
module
-
command
yes
propagation
-
error
-
behavior
panic
\ No newline at end of file
tests/integration/aof-multi-part.tcl
View file @
d375595d
...
...
@@ -1104,7 +1104,11 @@ tags {"external:skip"} {
# Set a key so that AOFRW can be delayed
r set k v
# Let AOFRW fail two times, this will trigger AOFRW limit
# Let AOFRW fail 3 times, this will trigger AOFRW limit
r bgrewriteaof
catch
{
exec kill -9
[
get_child_pid 0
]}
waitForBgrewriteaof r
r bgrewriteaof
catch
{
exec kill -9
[
get_child_pid 0
]}
waitForBgrewriteaof r
...
...
@@ -1118,6 +1122,7 @@ tags {"external:skip"} {
{
file appendonly.aof.6.incr.aof seq 6 type i
}
{
file appendonly.aof.7.incr.aof seq 7 type i
}
{
file appendonly.aof.8.incr.aof seq 8 type i
}
{
file appendonly.aof.9.incr.aof seq 9 type i
}
}
# Write 1KB data to trigger AOFRW
...
...
@@ -1137,6 +1142,7 @@ tags {"external:skip"} {
{
file appendonly.aof.6.incr.aof seq 6 type i
}
{
file appendonly.aof.7.incr.aof seq 7 type i
}
{
file appendonly.aof.8.incr.aof seq 8 type i
}
{
file appendonly.aof.9.incr.aof seq 9 type i
}
}
# Turn off auto rewrite
...
...
@@ -1154,11 +1160,11 @@ tags {"external:skip"} {
waitForBgrewriteaof r
# Can create New INCR AOF
assert_equal 1
[
check_file_exist $aof_dirpath
"
${aof_basename}
.
9
${::incr_aof_sufix}${::aof_format_suffix}
"
]
assert_equal 1
[
check_file_exist $aof_dirpath
"
${aof_basename}
.
10
${::incr_aof_sufix}${::aof_format_suffix}
"
]
assert_aof_manifest_content $aof_manifest_file
{
{
file appendonly.aof.11.base.rdb seq 11 type b
}
{
file appendonly.aof.
9
.incr.aof seq
9
type i
}
{
file appendonly.aof.
10
.incr.aof seq
10
type i
}
}
set d1
[
r debug digest
]
...
...
@@ -1166,5 +1172,161 @@ tags {"external:skip"} {
set d2
[
r debug digest
]
assert
{
$d1
eq $d2
}
}
start_server
{
overrides
{
aof-use-rdb-preamble
{
yes
}
appendonly
{
no
}}}
{
set dir
[
get_redis_dir
]
set aof_basename
"appendonly.aof"
set aof_dirname
"appendonlydir"
set aof_dirpath
"
$dir
/
$aof
_dirname"
set aof_manifest_name
"
$aof
_basename
$::manifest
_suffix"
set aof_manifest_file
"
$dir
/
$aof
_dirname/
$aof
_manifest_name"
set master
[
srv 0 client
]
set master_host
[
srv 0 host
]
set master_port
[
srv 0 port
]
test
"AOF will open a temporary INCR AOF to accumulate data until the first AOFRW success when AOF is dynamically enabled"
{
r config set save
""
# Increase AOFRW execution time to give us enough time to kill it
r config set rdb-key-save-delay 10000000
# Start write load
set load_handle0
[
start_write_load $master_host $master_port 10
]
wait_for_condition 50 100
{
[
r dbsize
]
> 0
}
else
{
fail
"No write load detected."
}
# Enable AOF will trigger an initialized AOFRW
r config set appendonly yes
# Let AOFRW fail
assert_equal 1
[
s aof_rewrite_in_progress
]
set pid1
[
get_child_pid 0
]
catch
{
exec kill -9 $pid1
}
# Wait for AOFRW to exit and delete temp incr aof
wait_for_condition 1000 100
{
[
count_log_message 0
"Removing the temp incr aof file"
]
== 1
}
else
{
fail
"temp aof did not delete"
}
# Make sure manifest file is not created
assert_equal 0
[
check_file_exist $aof_dirpath $aof_manifest_name
]
# Make sure BASE AOF is not created
assert_equal 0
[
check_file_exist $aof_dirpath
"
${aof_basename}
.1
${::base_aof_sufix}${::rdb_format_suffix}
"
]
# Make sure the next AOFRW has started
wait_for_condition 1000 50
{
[
s aof_rewrite_in_progress
]
== 1
}
else
{
fail
"aof rewrite did not scheduled"
}
# Do a successful AOFRW
set total_forks
[
s total_forks
]
r config set rdb-key-save-delay 0
catch
{
exec kill -9
[
get_child_pid 0
]}
# Make sure the next AOFRW has started
wait_for_condition 1000 10
{
[
s total_forks
]
==
[
expr $total_forks + 1
]
}
else
{
fail
"aof rewrite did not scheduled"
}
waitForBgrewriteaof r
assert_equal 2
[
count_log_message 0
"Removing the temp incr aof file"
]
# BASE and INCR AOF are successfully created
assert_aof_manifest_content $aof_manifest_file
{
{
file appendonly.aof.1.base.rdb seq 1 type b
}
{
file appendonly.aof.1.incr.aof seq 1 type i
}
}
stop_write_load $load_handle0
wait_load_handlers_disconnected
set d1
[
r debug digest
]
r debug loadaof
set d2
[
r debug digest
]
assert
{
$d1
eq $d2
}
# Dynamic disable AOF again
r config set appendonly no
# Disabling AOF does not delete previous AOF files
r debug loadaof
set d2
[
r debug digest
]
assert
{
$d1
eq $d2
}
assert_equal 0
[
s rdb_changes_since_last_save
]
r config set rdb-key-save-delay 10000000
set load_handle0
[
start_write_load $master_host $master_port 10
]
wait_for_condition 50 100
{
[
s rdb_changes_since_last_save
]
> 0
}
else
{
fail
"No write load detected."
}
# Re-enable AOF
r config set appendonly yes
# Let AOFRW fail
assert_equal 1
[
s aof_rewrite_in_progress
]
set pid1
[
get_child_pid 0
]
catch
{
exec kill -9 $pid1
}
# Wait for AOFRW to exit and delete temp incr aof
wait_for_condition 1000 100
{
[
count_log_message 0
"Removing the temp incr aof file"
]
== 3
}
else
{
fail
"temp aof did not delete 3 times"
}
# Make sure no new incr AOF was created
assert_aof_manifest_content $aof_manifest_file
{
{
file appendonly.aof.1.base.rdb seq 1 type b
}
{
file appendonly.aof.1.incr.aof seq 1 type i
}
}
# Make sure the next AOFRW has started
wait_for_condition 1000 50
{
[
s aof_rewrite_in_progress
]
== 1
}
else
{
fail
"aof rewrite did not scheduled"
}
# Do a successful AOFRW
set total_forks
[
s total_forks
]
r config set rdb-key-save-delay 0
catch
{
exec kill -9
[
get_child_pid 0
]}
wait_for_condition 1000 10
{
[
s total_forks
]
==
[
expr $total_forks + 1
]
}
else
{
fail
"aof rewrite did not scheduled"
}
waitForBgrewriteaof r
assert_equal 4
[
count_log_message 0
"Removing the temp incr aof file"
]
# New BASE and INCR AOF are successfully created
assert_aof_manifest_content $aof_manifest_file
{
{
file appendonly.aof.2.base.rdb seq 2 type b
}
{
file appendonly.aof.2.incr.aof seq 2 type i
}
}
stop_write_load $load_handle0
wait_load_handlers_disconnected
set d1
[
r debug digest
]
r debug loadaof
set d2
[
r debug digest
]
assert
{
$d1
eq $d2
}
}
}
}
}
tests/integration/corrupt-dump.tcl
View file @
d375595d
...
...
@@ -632,7 +632,6 @@ test {corrupt payload: fuzzer findings - stream PEL without consumer} {
r debug set-skip-checksum-validation 1
catch
{
r restore _stream 0
"
\x0F\x01\x10\x00\x00\x01\x7B\x08\xF0\xB2\x34\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x3B\x40\x42\x19\x42\x00\x00\x00\x18\x00\x02\x01\x01\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x00\x20\x10\x00\x00\x20\x01\x00\x01\x20\x03\x02\x05\x01\x03\x20\x05\x40\x00\x04\x82\x5F\x31\x03\x05\x60\x19\x80\x32\x02\x05\x01\xFF\x02\x81\x00\x00\x01\x7B\x08\xF0\xB2\x34\x02\x01\x07\x6D\x79\x67\x72\x6F\x75\x70\x81\x00\x00\x01\x7B\x08\xF0\xB2\x34\x01\x01\x00\x00\x01\x7B\x08\xF0\xB2\x34\x00\x00\x00\x00\x00\x00\x00\x01\x35\xB2\xF0\x08\x7B\x01\x00\x00\x01\x01\x13\x41\x6C\x69\x63\x65\x35\xB2\xF0\x08\x7B\x01\x00\x00\x01\x00\x00\x01\x7B\x08\xF0\xB2\x34\x00\x00\x00\x00\x00\x00\x00\x01\x09\x00\x28\x2F\xE0\xC5\x04\xBB\xA7\x31
"
}
err
assert_match
"*Bad data format*"
$err
#catch
{
r XINFO STREAM _stream FULL
}
r ping
}
}
...
...
@@ -674,7 +673,6 @@ test {corrupt payload: fuzzer findings - stream with non-integer entry id} {
r config set sanitize-dump-payload yes
r debug set-skip-checksum-validation 1
catch
{
r restore _streambig 0
"
\x0F\x03\x10\x00\x00\x01\x7B\x13\x34\xC3\xB2\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x40\x4F\x40\x5C\x18\x5C\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x80\x20\x01\x00\x01\x20\x03\x00\x05\x20\x1C\x40\x09\x05\x01\x01\x82\x5F\x31\x03\x80\x0D\x00\x02\x20\x0D\x00\x02\xA0\x19\x00\x03\x20\x0B\x02\x82\x5F\x33\xA0\x19\x00\x04\x20\x0D\x00\x04\x20\x19\x00\xFF\x10\x00\x00\x01\x7B\x13\x34\xC3\xB2\x00\x00\x00\x00\x00\x00\x00\x05\xC3\x40\x56\x40\x61\x18\x61\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x06\x01\x01\x82\x5F\x35\x03\x05\x20\x1E\x40\x0B\x03\x01\x01\x06\x01\x40\x0B\x03\x01\x01\xDF\xFB\x20\x05\x02\x82\x5F\x37\x60\x1A\x20\x0E\x00\xFC\x20\x05\x00\x08\xC0\x1B\x00\xFD\x20\x0C\x02\x82\x5F\x39\x20\x1B\x00\xFF\x10\x00\x00\x01\x7B\x13\x34\xC3\xB3\x00\x00\x00\x00\x00\x00\x00\x03\xC3\x3D\x40\x4A\x18\x4A\x00\x00\x00\x15\x00\x02\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x40\x00\x00\x05\x60\x07\x02\xDF\xFD\x02\xC0\x23\x09\x01\x01\x86\x75\x6E\x69\x71\x75\x65\x07\xA0\x2D\x02\x08\x01\xFF\x0C\x81\x00\x00\x01\x7B\x13\x34\xC3\xB4\x00\x00\x09\x00\x9D\xBD\xD5\xB9\x33\xC4\xC5\xFF
"
}
err
#catch
{
r XINFO STREAM _streambig FULL
}
assert_match
"*Bad data format*"
$err
r ping
}
...
...
@@ -782,5 +780,15 @@ test {corrupt payload: fuzzer findings - zset zslInsert with a NAN score} {
}
}
test
{
corrupt payload: fuzzer findings - streamLastValidID panic
}
{
start_server
[
list overrides
[
list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no
]
]
{
r config set sanitize-dump-payload yes
r debug set-skip-checksum-validation 1
catch
{
r restore _streambig 0
"
\x13\xC0\x10\x00\x00\x01\x80\x20\x48\xA0\x33\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x40\x4F\x40\x5C\x18\x5C\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x00\x01\x20\x03\x00\x05\x20\x1C\x40\x09\x05\x01\x01\x82\x5F\x31\x03\x80\x0D\x00\x02\x20\x0D\x00\x02\xA0\x19\x00\x03\x20\x0B\x02\x82\x5F\x33\x60\x19\x40\x2F\x02\x01\x01\x04\x20\x19\x00\xFF\x10\x00\x00\x01\x80\x20\x48\xA0\x34\x00\x00\x00\x00\x00\x00\x00\x01\xC3\x40\x51\x40\x5E\x18\x5E\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x06\x01\x01\x82\x5F\x35\x03\x05\x20\x1E\x40\x0B\x03\x01\x01\x06\x01\x80\x0B\x00\x02\x20\x0B\x02\x82\x5F\x37\xA0\x19\x00\x03\x20\x0D\x00\x08\xA0\x19\x00\x04\x20\x0B\x02\x82\x5F\x39\x20\x19\x00\xFF\x10\x00\x00\x01\x80\x20\x48\xA0\x34\x00\x00\x00\x00\x00\x00\x00\x06\xC3\x3D\x40\x4A\x18\x4A\x00\x00\x00\x15\x00\x02\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x40\x00\x00\x05\x60\x07\x02\xDF\xFA\x02\xC0\x23\x09\x01\x01\x86\x75\x6E\x69\x71\x75\x65\x07\xA0\x2D\x02\x08\x01\xFF\x0C\x81\x00\x00\x01\x80\x20\x48\xA0\x35\x00\x81\x00\x00\x01\x80\x20\x48\xA0\x33\x00\x00\x00\x0C\x00\x0A\x00\x34\x8B\x0E\x5B\x42\xCD\xD6\x08
"
}
err
assert_match
"*Bad data format*"
$err
r ping
}
}
}
;
# tags
tests/integration/replication-4.tcl
View file @
d375595d
...
...
@@ -195,3 +195,48 @@ start_server {tags {"repl external:skip"}} {
}
}
}
start_server
{
tags
{
"repl external:skip"
}}
{
start_server
{}
{
set master
[
srv -1 client
]
set master_host
[
srv -1 host
]
set master_port
[
srv -1 port
]
set replica
[
srv 0 client
]
test
{
First server should have role slave after SLAVEOF
}
{
$replica slaveof $master_host $master_port
wait_for_condition 50 100
{
[
s 0 role
]
eq
{
slave
}
}
else
{
fail
"Replication not started."
}
wait_for_sync $replica
}
test
{
Data divergence can happen under default conditions
}
{
$replica config set propagation-error-behavior ignore
$master debug replicate fake-command-1
# Wait for replication to normalize
$master set foo bar2
$master wait 1 2000
# Make sure we triggered the error, by finding the critical
# message and the fake command.
assert_equal
[
count_log_message 0
"fake-command-1"
]
1
assert_equal
[
count_log_message 0
"== CRITICAL =="
]
1
}
test
{
Data divergence is allowed on writable replicas
}
{
$replica config set replica-read-only no
$replica set number2 foo
$master incrby number2 1
$master wait 1 2000
assert_equal
[
$master
get number2
]
1
assert_equal
[
$replica
get number2
]
foo
assert_equal
[
count_log_message 0
"incrby"
]
1
}
}
}
tests/modules/Makefile
View file @
d375595d
...
...
@@ -49,6 +49,7 @@ TEST_MODULES = \
hash.so
\
zset.so
\
stream.so
\
mallocsize.so
\
aclcheck.so
\
list.so
\
subcommands.so
\
...
...
@@ -56,7 +57,8 @@ TEST_MODULES = \
cmdintrospection.so
\
eventloop.so
\
moduleconfigs.so
\
moduleconfigstwo.so
moduleconfigstwo.so
\
publish.so
.PHONY
:
all
...
...
@@ -69,7 +71,7 @@ all: $(TEST_MODULES)
$(CC)
-I
../../src
$(CFLAGS)
$(SHOBJ_CFLAGS)
-fPIC
-c
$<
-o
$@
%.so
:
%.xo
$(LD)
-o
$@
$
<
$(SHOBJ_LDFLAGS)
$(LDFLAGS)
$(LIBS)
$(LD)
-o
$@
$
^
$(SHOBJ_LDFLAGS)
$(LDFLAGS)
$(LIBS)
.PHONY
:
clean
...
...
tests/modules/aclcheck.c
View file @
d375595d
...
...
@@ -92,7 +92,7 @@ int rm_call_aclcheck_cmd(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModule
if
(
ret
!=
0
)
{
RedisModule_ReplyWithError
(
ctx
,
"DENIED CMD"
);
/* Add entry to ACL log */
RedisModule_ACLAddLogEntry
(
ctx
,
user
,
argv
[
1
]);
RedisModule_ACLAddLogEntry
(
ctx
,
user
,
argv
[
1
]
,
REDISMODULE_ACL_LOG_CMD
);
return
REDISMODULE_OK
;
}
...
...
Prev
1
2
3
4
5
6
7
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment