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
20618c71
Commit
20618c71
authored
Apr 27, 2022
by
Oran Agra
Browse files
Merge remote-tracking branch 'origin/unstable' into 7.0
parents
fb4e0d40
89772ed8
Changes
122
Show whitespace changes
Inline
Side-by-side
src/script_lua.c
View file @
20618c71
...
...
@@ -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
);
}
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 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!
/* 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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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,9 +1335,12 @@ 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
)
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. */
checkClientPauseTimeoutAndReturnIfPaused
();
...
...
@@ -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
);
sds
err
;
if
(
!
commandCheckExistence
(
c
,
&
err
))
{
rejectCommandSds
(
c
,
err
);
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
);
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,16 +3767,30 @@ 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
))
{
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
* user configured the min-slaves-to-write option. */
...
...
@@ -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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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
;
if
(
hashTypeGet
FromListpack
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
)
==
0
)
if
(
hashTypeGet
Value
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
)
==
C_OK
)
len
=
vstr
?
vlen
:
sdigits10
(
vll
);
}
else
if
(
o
->
encoding
==
OBJ_ENCODING_HT
)
{
sds
aux
;
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
;
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
;
ret
=
hashTypeGetFromListpack
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
);
if
(
ret
<
0
)
{
addReplyNull
(
c
);
}
else
{
if
(
hashTypeGetValue
(
o
,
field
,
&
vstr
,
&
vlen
,
&
vll
)
==
C_OK
)
{
if
(
vstr
)
{
addReplyBulkCBuffer
(
c
,
vstr
,
vlen
);
}
else
{
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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -1029,16 +1029,24 @@ unsigned char *zzlInsertAt(unsigned char *zl, unsigned char *eptr, sds ele, doub
unsigned
char
*
sptr
;
char
scorebuf
[
MAX_D2STRING_CHARS
];
int
scorelen
;
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
));
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. */
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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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
);
...
...
tests/assets/default.conf
View file @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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,17 +1160,173 @@ 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
]
r debug loadaof
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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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 @
20618c71
...
...
@@ -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
;
}
...
...
tests/modules/basics.c
View file @
20618c71
...
...
@@ -866,10 +866,10 @@ int TestBasics(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if
(
!
TestAssertStringReply
(
ctx
,
RedisModule_CallReplyArrayElement
(
reply
,
1
),
"1234"
,
4
))
goto
fail
;
T
(
"foo"
,
"E"
);
if
(
!
TestAssertErrorReply
(
ctx
,
reply
,
"ERR
U
nknown
Redis
command 'foo'
."
,
32
))
goto
fail
;
if
(
!
TestAssertErrorReply
(
ctx
,
reply
,
"ERR
u
nknown command 'foo'
, with args beginning with: "
,
53
))
goto
fail
;
T
(
"set"
,
"Ec"
,
"x"
);
if
(
!
TestAssertErrorReply
(
ctx
,
reply
,
"ERR
W
rong number of arg
s calling Redis command 'set'."
,
53
))
goto
fail
;
if
(
!
TestAssertErrorReply
(
ctx
,
reply
,
"ERR
w
rong number of arg
uments for 'set' command"
,
47
))
goto
fail
;
T
(
"shutdown"
,
"SE"
);
if
(
!
TestAssertErrorReply
(
ctx
,
reply
,
"ERR command 'shutdown' is not allowed on script mode"
,
52
))
goto
fail
;
...
...
tests/modules/keyspace_events.c
View file @
20618c71
...
...
@@ -79,6 +79,17 @@ static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const cha
return
REDISMODULE_OK
;
}
static
int
KeySpace_NotificationExpired
(
RedisModuleCtx
*
ctx
,
int
type
,
const
char
*
event
,
RedisModuleString
*
key
)
{
REDISMODULE_NOT_USED
(
type
);
REDISMODULE_NOT_USED
(
event
);
REDISMODULE_NOT_USED
(
key
);
RedisModuleCallReply
*
rep
=
RedisModule_Call
(
ctx
,
"INCR"
,
"c!"
,
"testkeyspace:expired"
);
RedisModule_FreeCallReply
(
rep
);
return
REDISMODULE_OK
;
}
static
int
KeySpace_NotificationModule
(
RedisModuleCtx
*
ctx
,
int
type
,
const
char
*
event
,
RedisModuleString
*
key
)
{
REDISMODULE_NOT_USED
(
ctx
);
REDISMODULE_NOT_USED
(
type
);
...
...
@@ -233,6 +244,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return
REDISMODULE_ERR
;
}
if
(
RedisModule_SubscribeToKeyspaceEvents
(
ctx
,
REDISMODULE_NOTIFY_EXPIRED
,
KeySpace_NotificationExpired
)
!=
REDISMODULE_OK
){
return
REDISMODULE_ERR
;
}
if
(
RedisModule_SubscribeToKeyspaceEvents
(
ctx
,
REDISMODULE_NOTIFY_MODULE
,
KeySpace_NotificationModule
)
!=
REDISMODULE_OK
){
return
REDISMODULE_ERR
;
}
...
...
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