Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
0f026af1
Commit
0f026af1
authored
Nov 06, 2019
by
antirez
Browse files
Merge branch 'unstable' of github.com:/antirez/redis into unstable
parents
8b2c0f90
a15a5d70
Changes
27
Hide whitespace changes
Inline
Side-by-side
runtest-moduleapi
View file @
0f026af1
...
...
@@ -21,4 +21,5 @@ $TCLSH tests/test_helper.tcl \
--single
unit/moduleapi/propagate
\
--single
unit/moduleapi/hooks
\
--single
unit/moduleapi/misc
\
--single
unit/moduleapi/blockonkeys
\
"
${
@
}
"
src/aof.c
View file @
0f026af1
...
...
@@ -731,7 +731,7 @@ int loadAppendOnlyFile(char *filename) {
server
.
aof_state
=
AOF_OFF
;
fakeClient
=
createAOFClient
();
startLoadingFile
(
fp
,
filename
);
startLoadingFile
(
fp
,
filename
,
RDBFLAGS_AOF_PREAMBLE
);
/* Check if this AOF file has an RDB preamble. In that case we need to
* load the RDB file and later continue loading the AOF tail. */
...
...
@@ -746,7 +746,7 @@ int loadAppendOnlyFile(char *filename) {
serverLog
(
LL_NOTICE
,
"Reading RDB preamble from AOF file..."
);
if
(
fseek
(
fp
,
0
,
SEEK_SET
)
==
-
1
)
goto
readerr
;
rioInitWithFile
(
&
rdb
,
fp
);
if
(
rdbLoadRio
(
&
rdb
,
NULL
,
1
)
!=
C_OK
)
{
if
(
rdbLoadRio
(
&
rdb
,
RDBFLAGS_AOF_PREAMBLE
,
NULL
)
!=
C_OK
)
{
serverLog
(
LL_WARNING
,
"Error reading the RDB preamble of the AOF file, AOF loading aborted"
);
goto
readerr
;
}
else
{
...
...
@@ -767,6 +767,7 @@ int loadAppendOnlyFile(char *filename) {
if
(
!
(
loops
++
%
1000
))
{
loadingProgress
(
ftello
(
fp
));
processEventsWhileBlocked
();
processModuleLoadingProgressEvent
(
1
);
}
if
(
fgets
(
buf
,
sizeof
(
buf
),
fp
)
==
NULL
)
{
...
...
@@ -859,7 +860,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
fclose
(
fp
);
freeFakeClient
(
fakeClient
);
server
.
aof_state
=
old_aof_state
;
stopLoading
();
stopLoading
(
1
);
aofUpdateCurrentSize
();
server
.
aof_rewrite_base_size
=
server
.
aof_current_size
;
server
.
aof_fsync_offset
=
server
.
aof_current_size
;
...
...
@@ -1400,9 +1401,11 @@ int rewriteAppendOnlyFile(char *filename) {
if
(
server
.
aof_rewrite_incremental_fsync
)
rioSetAutoSync
(
&
aof
,
REDIS_AUTOSYNC_BYTES
);
startSaving
(
RDBFLAGS_AOF_PREAMBLE
);
if
(
server
.
aof_use_rdb_preamble
)
{
int
error
;
if
(
rdbSaveRio
(
&
aof
,
&
error
,
RDB
_SAVE
_AOF_PREAMBLE
,
NULL
)
==
C_ERR
)
{
if
(
rdbSaveRio
(
&
aof
,
&
error
,
RDB
FLAGS
_AOF_PREAMBLE
,
NULL
)
==
C_ERR
)
{
errno
=
error
;
goto
werr
;
}
...
...
@@ -1465,15 +1468,18 @@ int rewriteAppendOnlyFile(char *filename) {
if
(
rename
(
tmpfile
,
filename
)
==
-
1
)
{
serverLog
(
LL_WARNING
,
"Error moving temp append only file on the final destination: %s"
,
strerror
(
errno
));
unlink
(
tmpfile
);
stopSaving
(
0
);
return
C_ERR
;
}
serverLog
(
LL_NOTICE
,
"SYNC append only file rewrite performed"
);
stopSaving
(
1
);
return
C_OK
;
werr:
serverLog
(
LL_WARNING
,
"Write error writing append only file on disk: %s"
,
strerror
(
errno
));
fclose
(
fp
);
unlink
(
tmpfile
);
stopSaving
(
0
);
return
C_ERR
;
}
...
...
src/db.c
View file @
0f026af1
...
...
@@ -151,9 +151,13 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
*
* Returns the linked value object if the key exists or NULL if the key
* does not exist in the specified DB. */
robj
*
lookupKeyWrite
(
redisDb
*
db
,
robj
*
key
)
{
robj
*
lookupKeyWrite
WithFlags
(
redisDb
*
db
,
robj
*
key
,
int
flags
)
{
expireIfNeeded
(
db
,
key
);
return
lookupKey
(
db
,
key
,
LOOKUP_NONE
);
return
lookupKey
(
db
,
key
,
flags
);
}
robj
*
lookupKeyWrite
(
redisDb
*
db
,
robj
*
key
)
{
return
lookupKeyWriteWithFlags
(
db
,
key
,
LOOKUP_NONE
);
}
robj
*
lookupKeyReadOrReply
(
client
*
c
,
robj
*
key
,
robj
*
reply
)
{
...
...
@@ -461,6 +465,29 @@ int getFlushCommandFlags(client *c, int *flags) {
return
C_OK
;
}
/* Flushes the whole server data set. */
void
flushAllDataAndResetRDB
(
int
flags
)
{
server
.
dirty
+=
emptyDb
(
-
1
,
flags
,
NULL
);
if
(
server
.
rdb_child_pid
!=
-
1
)
killRDBChild
();
if
(
server
.
saveparamslen
>
0
)
{
/* Normally rdbSave() will reset dirty, but we don't want this here
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
int
saved_dirty
=
server
.
dirty
;
rdbSaveInfo
rsi
,
*
rsiptr
;
rsiptr
=
rdbPopulateSaveInfo
(
&
rsi
);
rdbSave
(
server
.
rdb_filename
,
rsiptr
);
server
.
dirty
=
saved_dirty
;
}
server
.
dirty
++
;
#if defined(USE_JEMALLOC)
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
* for large databases, flushdb blocks for long anyway, so a bit more won't
* harm and this way the flush and purge will be synchroneus. */
if
(
!
(
flags
&
EMPTYDB_ASYNC
))
jemalloc_purge
();
#endif
}
/* FLUSHDB [ASYNC]
*
* Flushes the currently SELECTed Redis DB. */
...
...
@@ -484,28 +511,9 @@ void flushdbCommand(client *c) {
* Flushes the whole server data set. */
void
flushallCommand
(
client
*
c
)
{
int
flags
;
if
(
getFlushCommandFlags
(
c
,
&
flags
)
==
C_ERR
)
return
;
server
.
dirty
+=
emptyDb
(
-
1
,
flags
,
NULL
);
flushAllDataAndResetRDB
(
flags
);
addReply
(
c
,
shared
.
ok
);
if
(
server
.
rdb_child_pid
!=
-
1
)
killRDBChild
();
if
(
server
.
saveparamslen
>
0
)
{
/* Normally rdbSave() will reset dirty, but we don't want this here
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
int
saved_dirty
=
server
.
dirty
;
rdbSaveInfo
rsi
,
*
rsiptr
;
rsiptr
=
rdbPopulateSaveInfo
(
&
rsi
);
rdbSave
(
server
.
rdb_filename
,
rsiptr
);
server
.
dirty
=
saved_dirty
;
}
server
.
dirty
++
;
#if defined(USE_JEMALLOC)
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
* for large databases, flushdb blocks for long anyway, so a bit more won't
* harm and this way the flush and purge will be synchroneus. */
if
(
!
(
flags
&
EMPTYDB_ASYNC
))
jemalloc_purge
();
#endif
}
/* This command implements DEL and LAZYDEL. */
...
...
src/debug.c
View file @
0f026af1
...
...
@@ -417,7 +417,7 @@ NULL
}
emptyDb
(
-
1
,
EMPTYDB_NO_FLAGS
,
NULL
);
protectClient
(
c
);
int
ret
=
rdbLoad
(
server
.
rdb_filename
,
NULL
);
int
ret
=
rdbLoad
(
server
.
rdb_filename
,
NULL
,
RDBFLAGS_NONE
);
unprotectClient
(
c
);
if
(
ret
!=
C_OK
)
{
addReplyError
(
c
,
"Error trying to load the RDB dump"
);
...
...
src/module.c
View file @
0f026af1
...
...
@@ -1625,6 +1625,27 @@ int modulePopulateClientInfoStructure(void *ci, client *client, int structver) {
return
REDISMODULE_OK
;
}
/* This is an helper for moduleFireServerEvent() and other functions:
* It populates the replication info structure with the appropriate
* fields depending on the version provided. If the version is not valid
* then REDISMODULE_ERR is returned. Otherwise the function returns
* REDISMODULE_OK and the structure pointed by 'ri' gets populated. */
int
modulePopulateReplicationInfoStructure
(
void
*
ri
,
int
structver
)
{
if
(
structver
!=
1
)
return
REDISMODULE_ERR
;
RedisModuleReplicationInfoV1
*
ri1
=
ri
;
memset
(
ri1
,
0
,
sizeof
(
*
ri1
));
ri1
->
version
=
structver
;
ri1
->
master
=
server
.
masterhost
==
NULL
;
ri1
->
masterhost
=
server
.
masterhost
?
server
.
masterhost
:
""
;
ri1
->
masterport
=
server
.
masterport
;
ri1
->
replid1
=
server
.
replid
;
ri1
->
replid2
=
server
.
replid2
;
ri1
->
repl1_offset
=
server
.
master_repl_offset
;
ri1
->
repl2_offset
=
server
.
second_replid_offset
;
return
REDISMODULE_OK
;
}
/* Return information about the client with the specified ID (that was
* previously obtained via the RedisModule_GetClientId() API). If the
* client exists, REDISMODULE_OK is returned, otherwise REDISMODULE_ERR
...
...
@@ -1677,6 +1698,15 @@ int RM_GetClientInfoById(void *ci, uint64_t id) {
return
modulePopulateClientInfoStructure
(
ci
,
client
,
structver
);
}
/* Publish a message to subscribers (see PUBLISH command). */
int
RM_PublishMessage
(
RedisModuleCtx
*
ctx
,
RedisModuleString
*
channel
,
RedisModuleString
*
message
)
{
UNUSED
(
ctx
);
int
receivers
=
pubsubPublishMessage
(
channel
,
message
);
if
(
server
.
cluster_enabled
)
clusterPropagatePublish
(
channel
,
message
);
return
receivers
;
}
/* Return the currently selected DB. */
int
RM_GetSelectedDb
(
RedisModuleCtx
*
ctx
)
{
return
ctx
->
client
->
db
->
id
;
...
...
@@ -1835,11 +1865,12 @@ int RM_SelectDb(RedisModuleCtx *ctx, int newid) {
void
*
RM_OpenKey
(
RedisModuleCtx
*
ctx
,
robj
*
keyname
,
int
mode
)
{
RedisModuleKey
*
kp
;
robj
*
value
;
int
flags
=
mode
&
REDISMODULE_OPEN_KEY_NOTOUCH
?
LOOKUP_NOTOUCH
:
0
;
if
(
mode
&
REDISMODULE_WRITE
)
{
value
=
lookupKeyWrite
(
ctx
->
client
->
db
,
keyname
);
value
=
lookupKeyWrite
WithFlags
(
ctx
->
client
->
db
,
keyname
,
flags
);
}
else
{
value
=
lookupKeyRead
(
ctx
->
client
->
db
,
keyname
);
value
=
lookupKeyRead
WithFlags
(
ctx
->
client
->
db
,
keyname
,
flags
);
if
(
value
==
NULL
)
{
return
NULL
;
}
...
...
@@ -1964,6 +1995,28 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) {
return
REDISMODULE_OK
;
}
/* Performs similar operation to FLUSHALL, and optionally start a new AOF file (if enabled)
* If restart_aof is true, you must make sure the command that triggered this call is not
* propagated to the AOF file.
* When async is set to true, db contents will be freed by a background thread. */
void
RM_ResetDataset
(
int
restart_aof
,
int
async
)
{
if
(
restart_aof
&&
server
.
aof_state
!=
AOF_OFF
)
stopAppendOnly
();
flushAllDataAndResetRDB
(
async
?
EMPTYDB_ASYNC
:
EMPTYDB_NO_FLAGS
);
if
(
server
.
aof_enabled
&&
restart_aof
)
restartAOFAfterSYNC
();
}
/* Returns the number of keys in the current db. */
unsigned
long
long
RM_DbSize
(
RedisModuleCtx
*
ctx
)
{
return
dictSize
(
ctx
->
client
->
db
->
dict
);
}
/* Returns a name of a random key, or NULL if current db is empty. */
RedisModuleString
*
RM_RandomKey
(
RedisModuleCtx
*
ctx
)
{
robj
*
key
=
dbRandomKey
(
ctx
->
client
->
db
);
autoMemoryAdd
(
ctx
,
REDISMODULE_AM_STRING
,
key
);
return
key
;
}
/* --------------------------------------------------------------------------
* Key API for String type
* -------------------------------------------------------------------------- */
...
...
@@ -3716,6 +3769,31 @@ loaderr:
return
0
;
}
/* In the context of the rdb_save method of a module data type, saves a long double
* value to the RDB file. The double can be a valid number, a NaN or infinity.
* It is possible to load back the value with RedisModule_LoadLongDouble(). */
void
RM_SaveLongDouble
(
RedisModuleIO
*
io
,
long
double
value
)
{
if
(
io
->
error
)
return
;
char
buf
[
MAX_LONG_DOUBLE_CHARS
];
/* Long double has different number of bits in different platforms, so we
* save it as a string type. */
size_t
len
=
ld2string
(
buf
,
sizeof
(
buf
),
value
,
LD_STR_HEX
);
RM_SaveStringBuffer
(
io
,
buf
,
len
+
1
);
/* len+1 for '\0' */
}
/* In the context of the rdb_save method of a module data type, loads back the
* long double value saved by RedisModule_SaveLongDouble(). */
long
double
RM_LoadLongDouble
(
RedisModuleIO
*
io
)
{
if
(
io
->
error
)
return
0
;
long
double
value
;
size_t
len
;
char
*
str
=
RM_LoadStringBuffer
(
io
,
&
len
);
if
(
!
str
)
return
0
;
string2ld
(
str
,
len
,
&
value
);
RM_Free
(
str
);
return
value
;
}
/* Iterate over modules, and trigger rdb aux saving for the ones modules types
* who asked for it. */
ssize_t
rdbSaveModulesAux
(
rio
*
rdb
,
int
when
)
{
...
...
@@ -5949,8 +6027,8 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) {
*
* The following sub events are available:
*
* REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER
* REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA
* REDISMODULE_
SUB
EVENT_REPLROLECHANGED_NOW_MASTER
* REDISMODULE_
SUB
EVENT_REPLROLECHANGED_NOW_REPLICA
*
* The 'data' field can be casted by the callback to a
* RedisModuleReplicationInfo structure with the following fields:
...
...
@@ -5960,24 +6038,30 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) {
* int masterport; // master instance port for NOW_REPLICA
* char *replid1; // Main replication ID
* char *replid2; // Secondary replication ID
* uint64_t repl1_offset; // Main replication offset
* uint64_t repl2_offset; // Offset of replid2 validity
* uint64_t main_repl_offset; // Replication offset
*
* RedisModuleEvent_Persistence
*
* This event is called when RDB saving or AOF rewriting starts
* and ends. The following sub events are available:
*
* REDISMODULE_EVENT_LOADING_RDB_START // BGSAVE start
* REDISMODULE_EVENT_LOADING_RDB_END // BGSAVE end
* REDISMODULE_EVENT_LOADING_SYNC_RDB_START // SAVE start
* REDISMODULE_EVENT_LOADING_SYNC_RDB_START // SAVE end
* REDISMODULE_EVENT_LOADING_AOF_START // AOF rewrite start
* REDISMODULE_EVENT_LOADING_AOF_END // AOF rewrite end
* REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START
* REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START
* REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START
* REDISMODULE_SUBEVENT_PERSISTENCE_ENDED
* REDISMODULE_SUBEVENT_PERSISTENCE_FAILED
*
* The above events are triggered not just when the user calls the
* relevant commands like BGSAVE, but also when a saving operation
* or AOF rewriting occurs because of internal server triggers.
* The SYNC_RDB_START sub events are happening in the forground due to
* SAVE command, FLUSHALL, or server shutdown, and the other RDB and
* AOF sub events are executed in a background fork child, so any
* action the module takes can only affect the generated AOF or RDB,
* but will not be reflected in the parent process and affect connected
* clients and commands. Also note that the AOF_START sub event may end
* up saving RDB content in case of an AOF with rdb-preamble.
*
* RedisModuleEvent_FlushDB
*
...
...
@@ -5985,8 +6069,8 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) {
* because of replication, after the replica synchronization)
* happened. The following sub events are available:
*
* REDISMODULE_EVENT_FLUSHDB_START
* REDISMODULE_EVENT_FLUSHDB_END
* REDISMODULE_
SUB
EVENT_FLUSHDB_START
* REDISMODULE_
SUB
EVENT_FLUSHDB_END
*
* The data pointer can be casted to a RedisModuleFlushInfo
* structure with the following fields:
...
...
@@ -6010,12 +6094,15 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) {
* replica is loading the RDB file from the master.
* The following sub events are available:
*
* REDISMODULE_EVENT_LOADING_RDB_START
* REDISMODULE_EVENT_LOADING_RDB_END
* REDISMODULE_EVENT_LOADING_MASTER_RDB_START
* REDISMODULE_EVENT_LOADING_MASTER_RDB_END
* REDISMODULE_EVENT_LOADING_AOF_START
* REDISMODULE_EVENT_LOADING_AOF_END
* REDISMODULE_SUBEVENT_LOADING_RDB_START
* REDISMODULE_SUBEVENT_LOADING_AOF_START
* REDISMODULE_SUBEVENT_LOADING_REPL_START
* REDISMODULE_SUBEVENT_LOADING_ENDED
* REDISMODULE_SUBEVENT_LOADING_FAILED
*
* Note that AOF loading may start with an RDB data in case of
* rdb-preamble, in which case you'll only recieve an AOF_START event.
*
*
* RedisModuleEvent_ClientChange
*
...
...
@@ -6024,8 +6111,8 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) {
* structure, documented in RedisModule_GetClientInfoById().
* The following sub events are available:
*
* REDISMODULE_EVENT_CLIENT_CHANGE_CONNECTED
* REDISMODULE_EVENT_CLIENT_CHANGE_DISCONNECTED
* REDISMODULE_
SUB
EVENT_CLIENT_CHANGE_CONNECTED
* REDISMODULE_
SUB
EVENT_CLIENT_CHANGE_DISCONNECTED
*
* RedisModuleEvent_Shutdown
*
...
...
@@ -6038,8 +6125,8 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) {
* replica since it gets disconnected.
* The following sub events are availble:
*
* REDISMODULE_EVENT_REPLICA_CHANGE_ONLINE
* REDISMODULE_EVENT_REPLICA_CHANGE_OFFLINE
* REDISMODULE_
SUB
EVENT_REPLICA_CHANGE_ONLINE
* REDISMODULE_
SUB
EVENT_REPLICA_CHANGE_OFFLINE
*
* No additional information is available so far: future versions
* of Redis will have an API in order to enumerate the replicas
...
...
@@ -6054,6 +6141,11 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) {
* this changes depending on the "hz" configuration.
* No sub events are available.
*
* The data pointer can be casted to a RedisModuleCronLoop
* structure with the following fields:
*
* int32_t hz; // Approximate number of events per second.
*
* RedisModuleEvent_MasterLinkChange
*
* This is called for replicas in order to notify when the
...
...
@@ -6063,8 +6155,38 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) {
* replication is happening correctly.
* The following sub events are available:
*
* REDISMODULE_EVENT_MASTER_LINK_UP
* REDISMODULE_EVENT_MASTER_LINK_DOWN
* REDISMODULE_SUBEVENT_MASTER_LINK_UP
* REDISMODULE_SUBEVENT_MASTER_LINK_DOWN
*
* RedisModuleEvent_ModuleChange
*
* This event is called when a new module is loaded or one is unloaded.
* The following sub events are availble:
*
* REDISMODULE_SUBEVENT_MODULE_LOADED
* REDISMODULE_SUBEVENT_MODULE_UNLOADED
*
* The data pointer can be casted to a RedisModuleModuleChange
* structure with the following fields:
*
* const char* module_name; // Name of module loaded or unloaded.
* int32_t module_version; // Module version.
*
* RedisModuleEvent_LoadingProgress
*
* This event is called repeatedly called while an RDB or AOF file
* is being loaded.
* The following sub events are availble:
*
* REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB
* REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF
*
* The data pointer can be casted to a RedisModuleLoadingProgress
* structure with the following fields:
*
* int32_t hz; // Approximate number of events per second.
* int32_t progress; // Approximate progress between 0 and 1024,
* or -1 if unknown.
*
* The function returns REDISMODULE_OK if the module was successfully subscrived
* for the specified event. If the API is called from a wrong context then
...
...
@@ -6123,7 +6245,7 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data) {
listRewind
(
RedisModule_EventListeners
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
RedisModuleEventListener
*
el
=
ln
->
value
;
if
(
el
->
event
.
id
==
eid
&&
!
el
->
module
->
in_hook
)
{
if
(
el
->
event
.
id
==
eid
)
{
RedisModuleCtx
ctx
=
REDISMODULE_CTX_INIT
;
ctx
.
module
=
el
->
module
;
...
...
@@ -6137,6 +6259,8 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data) {
void
*
moduledata
=
NULL
;
RedisModuleClientInfoV1
civ1
;
RedisModuleReplicationInfoV1
riv1
;
RedisModuleModuleChangeV1
mcv1
;
/* Start at DB zero by default when calling the handler. It's
* up to the specific event setup to change it when it makes
* sense. For instance for FLUSHDB events we select the correct
...
...
@@ -6148,11 +6272,26 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data) {
modulePopulateClientInfoStructure
(
&
civ1
,
data
,
el
->
event
.
dataver
);
moduledata
=
&
civ1
;
}
else
if
(
eid
==
REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED
)
{
modulePopulateReplicationInfoStructure
(
&
riv1
,
el
->
event
.
dataver
);
moduledata
=
&
riv1
;
}
else
if
(
eid
==
REDISMODULE_EVENT_FLUSHDB
)
{
moduledata
=
data
;
RedisModuleFlushInfoV1
*
fi
=
data
;
if
(
fi
->
dbnum
!=
-
1
)
selectDb
(
ctx
.
client
,
fi
->
dbnum
);
}
else
if
(
eid
==
REDISMODULE_EVENT_MODULE_CHANGE
)
{
RedisModule
*
m
=
data
;
if
(
m
==
el
->
module
)
continue
;
mcv1
.
version
=
REDISMODULE_MODULE_CHANGE_VERSION
;
mcv1
.
module_name
=
m
->
name
;
mcv1
.
module_version
=
m
->
ver
;
moduledata
=
&
mcv1
;
}
else
if
(
eid
==
REDISMODULE_EVENT_LOADING_PROGRESS
)
{
moduledata
=
data
;
}
else
if
(
eid
==
REDISMODULE_EVENT_CRON_LOOP
)
{
moduledata
=
data
;
}
ModulesInHooks
++
;
...
...
@@ -6184,6 +6323,27 @@ void moduleUnsubscribeAllServerEvents(RedisModule *module) {
}
}
void
processModuleLoadingProgressEvent
(
int
is_aof
)
{
long
long
now
=
ustime
();
static
long
long
next_event
=
0
;
if
(
now
>=
next_event
)
{
/* Fire the loading progress modules end event. */
int
progress
=
-
1
;
if
(
server
.
loading_total_bytes
)
progress
=
(
server
.
loading_total_bytes
<<
10
)
/
server
.
loading_total_bytes
;
RedisModuleFlushInfoV1
fi
=
{
REDISMODULE_LOADING_PROGRESS_VERSION
,
server
.
hz
,
progress
};
moduleFireServerEvent
(
REDISMODULE_EVENT_LOADING_PROGRESS
,
is_aof
?
REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF:
REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB
,
&
fi
);
/* decide when the next event should fire. */
next_event
=
now
+
1000000
/
server
.
hz
;
}
}
/* --------------------------------------------------------------------------
* Modules API internals
* -------------------------------------------------------------------------- */
...
...
@@ -6352,6 +6512,11 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
ctx
.
module
->
blocked_clients
=
0
;
ctx
.
module
->
handle
=
handle
;
serverLog
(
LL_NOTICE
,
"Module '%s' loaded from %s"
,
ctx
.
module
->
name
,
path
);
/* Fire the loaded modules event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_MODULE_CHANGE
,
REDISMODULE_SUBEVENT_MODULE_LOADED
,
ctx
.
module
);
moduleFreeContext
(
&
ctx
);
return
C_OK
;
}
...
...
@@ -6414,6 +6579,11 @@ int moduleUnload(sds name) {
module
->
name
,
error
);
}
/* Fire the unloaded modules event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_MODULE_CHANGE
,
REDISMODULE_SUBEVENT_MODULE_UNLOADED
,
module
);
/* Remove from list of modules. */
serverLog
(
LL_NOTICE
,
"Module %s unloaded"
,
module
->
name
);
dictDelete
(
modules
,
module
->
name
);
...
...
@@ -6566,6 +6736,38 @@ size_t moduleCount(void) {
return
dictSize
(
modules
);
}
/* Set the key LRU/LFU depending on server.maxmemory_policy.
* The lru_idle arg is idle time in seconds, and is only relevant if the
* eviction policy is LRU based.
* The lfu_freq arg is a logarithmic counter that provides an indication of
* the access frequencyonly (must be <= 255) and is only relevant if the
* eviction policy is LFU based.
* Either or both of them may be <0, in that case, nothing is set. */
/* return value is an indication if the lru field was updated or not. */
int
RM_SetLRUOrLFU
(
RedisModuleKey
*
key
,
long
long
lfu_freq
,
long
long
lru_idle
)
{
if
(
!
key
->
value
)
return
REDISMODULE_ERR
;
if
(
objectSetLRUOrLFU
(
key
->
value
,
lfu_freq
,
lru_idle
,
lru_idle
>=
0
?
LRU_CLOCK
()
:
0
))
return
REDISMODULE_OK
;
return
REDISMODULE_ERR
;
}
/* Gets the key LRU or LFU (depending on the current eviction policy).
* One will be set to the appropiate return value, and the other will be set to -1.
* see RedisModule_SetLRUOrLFU for units and ranges.
* return value is an indication of success. */
int
RM_GetLRUOrLFU
(
RedisModuleKey
*
key
,
long
long
*
lfu_freq
,
long
long
*
lru_idle
)
{
*
lru_idle
=
*
lfu_freq
=
-
1
;
if
(
!
key
->
value
)
return
REDISMODULE_ERR
;
if
(
server
.
maxmemory_policy
&
MAXMEMORY_FLAG_LFU
)
{
*
lfu_freq
=
LFUDecrAndReturn
(
key
->
value
);
}
else
{
*
lru_idle
=
estimateObjectIdleTime
(
key
->
value
)
/
1000
;
}
return
REDISMODULE_OK
;
}
/* Register all the APIs we export. Keep this function at the end of the
* file so that's easy to seek it to add new entries. */
void
moduleRegisterCoreAPI
(
void
)
{
...
...
@@ -6630,6 +6832,9 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API
(
StringTruncate
);
REGISTER_API
(
SetExpire
);
REGISTER_API
(
GetExpire
);
REGISTER_API
(
ResetDataset
);
REGISTER_API
(
DbSize
);
REGISTER_API
(
RandomKey
);
REGISTER_API
(
ZsetAdd
);
REGISTER_API
(
ZsetIncrby
);
REGISTER_API
(
ZsetScore
);
...
...
@@ -6669,6 +6874,8 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API
(
LoadDouble
);
REGISTER_API
(
SaveFloat
);
REGISTER_API
(
LoadFloat
);
REGISTER_API
(
SaveLongDouble
);
REGISTER_API
(
LoadLongDouble
);
REGISTER_API
(
EmitAOF
);
REGISTER_API
(
Log
);
REGISTER_API
(
LogIOError
);
...
...
@@ -6757,7 +6964,10 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API
(
InfoAddFieldLongLong
);
REGISTER_API
(
InfoAddFieldULongLong
);
REGISTER_API
(
GetClientInfoById
);
REGISTER_API
(
PublishMessage
);
REGISTER_API
(
SubscribeToServerEvent
);
REGISTER_API
(
SetLRUOrLFU
);
REGISTER_API
(
GetLRUOrLFU
);
REGISTER_API
(
BlockClientOnKeys
);
REGISTER_API
(
SignalKeyAsReady
);
REGISTER_API
(
GetBlockedClientReadyKey
);
...
...
src/networking.c
View file @
0f026af1
...
...
@@ -530,7 +530,7 @@ void addReplyHumanLongDouble(client *c, long double d) {
decrRefCount
(
o
);
}
else
{
char
buf
[
MAX_LONG_DOUBLE_CHARS
];
int
len
=
ld2string
(
buf
,
sizeof
(
buf
),
d
,
1
);
int
len
=
ld2string
(
buf
,
sizeof
(
buf
),
d
,
LD_STR_HUMAN
);
addReplyProto
(
c
,
","
,
1
);
addReplyProto
(
c
,
buf
,
len
);
addReplyProto
(
c
,
"
\r\n
"
,
2
);
...
...
@@ -1118,6 +1118,11 @@ void freeClient(client *c) {
if
(
c
->
flags
&
CLIENT_SLAVE
&&
listLength
(
server
.
slaves
)
==
0
)
server
.
repl_no_slaves_since
=
server
.
unixtime
;
refreshGoodSlavesCount
();
/* Fire the replica change modules event. */
if
(
c
->
replstate
==
SLAVE_STATE_ONLINE
)
moduleFireServerEvent
(
REDISMODULE_EVENT_REPLICA_CHANGE
,
REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE
,
NULL
);
}
/* Master/slave cleanup Case 2:
...
...
src/object.c
View file @
0f026af1
...
...
@@ -178,7 +178,7 @@ robj *createStringObjectFromLongLongForValue(long long value) {
* The 'humanfriendly' option is used for INCRBYFLOAT and HINCRBYFLOAT. */
robj
*
createStringObjectFromLongDouble
(
long
double
value
,
int
humanfriendly
)
{
char
buf
[
MAX_LONG_DOUBLE_CHARS
];
int
len
=
ld2string
(
buf
,
sizeof
(
buf
),
value
,
humanfriendly
);
int
len
=
ld2string
(
buf
,
sizeof
(
buf
),
value
,
humanfriendly
?
LD_STR_HUMAN
:
LD_STR_AUTO
);
return
createStringObject
(
buf
,
len
);
}
...
...
@@ -1209,12 +1209,13 @@ sds getMemoryDoctorReport(void) {
* The lru_idle and lru_clock args are only relevant if policy
* is MAXMEMORY_FLAG_LRU.
* Either or both of them may be <0, in that case, nothing is set. */
void
objectSetLRUOrLFU
(
robj
*
val
,
long
long
lfu_freq
,
long
long
lru_idle
,
int
objectSetLRUOrLFU
(
robj
*
val
,
long
long
lfu_freq
,
long
long
lru_idle
,
long
long
lru_clock
)
{
if
(
server
.
maxmemory_policy
&
MAXMEMORY_FLAG_LFU
)
{
if
(
lfu_freq
>=
0
)
{
serverAssert
(
lfu_freq
<=
255
);
val
->
lru
=
(
LFUGetTimeInMinutes
()
<<
8
)
|
lfu_freq
;
return
1
;
}
}
else
if
(
lru_idle
>=
0
)
{
/* Provided LRU idle time is in seconds. Scale
...
...
@@ -1231,7 +1232,9 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
if
(
lru_abs
<
0
)
lru_abs
=
(
lru_clock
+
(
LRU_CLOCK_MAX
/
2
))
%
LRU_CLOCK_MAX
;
val
->
lru
=
lru_abs
;
return
1
;
}
return
0
;
}
/* ======================= The OBJECT and MEMORY commands =================== */
...
...
src/rdb.c
View file @
0f026af1
...
...
@@ -1080,9 +1080,9 @@ ssize_t rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {
}
/* Save a few default AUX fields with information about the RDB generated. */
int
rdbSaveInfoAuxFields
(
rio
*
rdb
,
int
flags
,
rdbSaveInfo
*
rsi
)
{
int
rdbSaveInfoAuxFields
(
rio
*
rdb
,
int
rdb
flags
,
rdbSaveInfo
*
rsi
)
{
int
redis_bits
=
(
sizeof
(
void
*
)
==
8
)
?
64
:
32
;
int
aof_preamble
=
(
flags
&
RDB
_SAVE
_AOF_PREAMBLE
)
!=
0
;
int
aof_preamble
=
(
rdb
flags
&
RDB
FLAGS
_AOF_PREAMBLE
)
!=
0
;
/* Add a few fields about the state when the RDB was created. */
if
(
rdbSaveAuxFieldStrStr
(
rdb
,
"redis-ver"
,
REDIS_VERSION
)
==
-
1
)
return
-
1
;
...
...
@@ -1150,7 +1150,7 @@ ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
* When the function returns C_ERR and if 'error' is not NULL, the
* integer pointed by 'error' is set to the value of errno just after the I/O
* error. */
int
rdbSaveRio
(
rio
*
rdb
,
int
*
error
,
int
flags
,
rdbSaveInfo
*
rsi
)
{
int
rdbSaveRio
(
rio
*
rdb
,
int
*
error
,
int
rdb
flags
,
rdbSaveInfo
*
rsi
)
{
dictIterator
*
di
=
NULL
;
dictEntry
*
de
;
char
magic
[
10
];
...
...
@@ -1162,7 +1162,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
rdb
->
update_cksum
=
rioGenericUpdateChecksum
;
snprintf
(
magic
,
sizeof
(
magic
),
"REDIS%04d"
,
RDB_VERSION
);
if
(
rdbWriteRaw
(
rdb
,
magic
,
9
)
==
-
1
)
goto
werr
;
if
(
rdbSaveInfoAuxFields
(
rdb
,
flags
,
rsi
)
==
-
1
)
goto
werr
;
if
(
rdbSaveInfoAuxFields
(
rdb
,
rdb
flags
,
rsi
)
==
-
1
)
goto
werr
;
if
(
rdbSaveModulesAux
(
rdb
,
REDISMODULE_AUX_BEFORE_RDB
)
==
-
1
)
goto
werr
;
for
(
j
=
0
;
j
<
server
.
dbnum
;
j
++
)
{
...
...
@@ -1199,7 +1199,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
/* When this RDB is produced as part of an AOF rewrite, move
* accumulated diff from parent to child while rewriting in
* order to have a smaller final write. */
if
(
flags
&
RDB
_SAVE
_AOF_PREAMBLE
&&
if
(
rdb
flags
&
RDB
FLAGS
_AOF_PREAMBLE
&&
rdb
->
processed_bytes
>
processed
+
AOF_READ_DIFF_INTERVAL_BYTES
)
{
processed
=
rdb
->
processed_bytes
;
...
...
@@ -1254,18 +1254,21 @@ werr:
int
rdbSaveRioWithEOFMark
(
rio
*
rdb
,
int
*
error
,
rdbSaveInfo
*
rsi
)
{
char
eofmark
[
RDB_EOF_MARK_SIZE
];
startSaving
(
RDBFLAGS_REPLICATION
);
getRandomHexChars
(
eofmark
,
RDB_EOF_MARK_SIZE
);
if
(
error
)
*
error
=
0
;
if
(
rioWrite
(
rdb
,
"$EOF:"
,
5
)
==
0
)
goto
werr
;
if
(
rioWrite
(
rdb
,
eofmark
,
RDB_EOF_MARK_SIZE
)
==
0
)
goto
werr
;
if
(
rioWrite
(
rdb
,
"
\r\n
"
,
2
)
==
0
)
goto
werr
;
if
(
rdbSaveRio
(
rdb
,
error
,
RDB
_SAVE
_NONE
,
rsi
)
==
C_ERR
)
goto
werr
;
if
(
rdbSaveRio
(
rdb
,
error
,
RDB
FLAGS
_NONE
,
rsi
)
==
C_ERR
)
goto
werr
;
if
(
rioWrite
(
rdb
,
eofmark
,
RDB_EOF_MARK_SIZE
)
==
0
)
goto
werr
;
stopSaving
(
1
);
return
C_OK
;
werr:
/* Write error. */
/* Set 'error' only if not already set by rdbSaveRio() call. */
if
(
error
&&
*
error
==
0
)
*
error
=
errno
;
stopSaving
(
0
);
return
C_ERR
;
}
...
...
@@ -1291,11 +1294,12 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
}
rioInitWithFile
(
&
rdb
,
fp
);
startSaving
(
RDBFLAGS_NONE
);
if
(
server
.
rdb_save_incremental_fsync
)
rioSetAutoSync
(
&
rdb
,
REDIS_AUTOSYNC_BYTES
);
if
(
rdbSaveRio
(
&
rdb
,
&
error
,
RDB
_SAVE
_NONE
,
rsi
)
==
C_ERR
)
{
if
(
rdbSaveRio
(
&
rdb
,
&
error
,
RDB
FLAGS
_NONE
,
rsi
)
==
C_ERR
)
{
errno
=
error
;
goto
werr
;
}
...
...
@@ -1317,6 +1321,7 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
cwdp
?
cwdp
:
"unknown"
,
strerror
(
errno
));
unlink
(
tmpfile
);
stopSaving
(
0
);
return
C_ERR
;
}
...
...
@@ -1324,12 +1329,14 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
server
.
dirty
=
0
;
server
.
lastsave
=
time
(
NULL
);
server
.
lastbgsave_status
=
C_OK
;
stopSaving
(
1
);
return
C_OK
;
werr:
serverLog
(
LL_WARNING
,
"Write error saving DB on disk: %s"
,
strerror
(
errno
));
fclose
(
fp
);
unlink
(
tmpfile
);
stopSaving
(
0
);
return
C_ERR
;
}
...
...
@@ -1918,23 +1925,33 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
/* Mark that we are loading in the global state and setup the fields
* needed to provide loading stats. */
void
startLoading
(
size_t
size
)
{
void
startLoading
(
size_t
size
,
int
rdbflags
)
{
/* Load the DB */
server
.
loading
=
1
;
server
.
loading_start_time
=
time
(
NULL
);
server
.
loading_loaded_bytes
=
0
;
server
.
loading_total_bytes
=
size
;
/* Fire the loading modules start event. */
int
subevent
;
if
(
rdbflags
&
RDBFLAGS_AOF_PREAMBLE
)
subevent
=
REDISMODULE_SUBEVENT_LOADING_AOF_START
;
else
if
(
rdbflags
&
RDBFLAGS_REPLICATION
)
subevent
=
REDISMODULE_SUBEVENT_LOADING_REPL_START
;
else
subevent
=
REDISMODULE_SUBEVENT_LOADING_RDB_START
;
moduleFireServerEvent
(
REDISMODULE_EVENT_LOADING
,
subevent
,
NULL
);
}
/* Mark that we are loading in the global state and setup the fields
* needed to provide loading stats.
* 'filename' is optional and used for rdb-check on error */
void
startLoadingFile
(
FILE
*
fp
,
char
*
filename
)
{
void
startLoadingFile
(
FILE
*
fp
,
char
*
filename
,
int
rdbflags
)
{
struct
stat
sb
;
if
(
fstat
(
fileno
(
fp
),
&
sb
)
==
-
1
)
sb
.
st_size
=
0
;
rdbFileBeingLoaded
=
filename
;
startLoading
(
sb
.
st_size
);
startLoading
(
sb
.
st_size
,
rdbflags
);
}
/* Refresh the loading progress info */
...
...
@@ -1945,9 +1962,37 @@ void loadingProgress(off_t pos) {
}
/* Loading finished */
void
stopLoading
(
void
)
{
void
stopLoading
(
int
success
)
{
server
.
loading
=
0
;
rdbFileBeingLoaded
=
NULL
;
/* Fire the loading modules end event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_LOADING
,
success
?
REDISMODULE_SUBEVENT_LOADING_ENDED:
REDISMODULE_SUBEVENT_LOADING_FAILED
,
NULL
);
}
void
startSaving
(
int
rdbflags
)
{
/* Fire the persistence modules end event. */
int
subevent
;
if
(
rdbflags
&
RDBFLAGS_AOF_PREAMBLE
)
subevent
=
REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START
;
else
if
(
getpid
()
!=
server
.
pid
)
subevent
=
REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START
;
else
subevent
=
REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START
;
moduleFireServerEvent
(
REDISMODULE_EVENT_PERSISTENCE
,
subevent
,
NULL
);
}
void
stopSaving
(
int
success
)
{
/* Fire the persistence modules end event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_PERSISTENCE
,
success
?
REDISMODULE_SUBEVENT_PERSISTENCE_ENDED:
REDISMODULE_SUBEVENT_PERSISTENCE_FAILED
,
NULL
);
}
/* Track loading progress in order to serve client's from time to time
...
...
@@ -1966,12 +2011,13 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
replicationSendNewlineToMaster
();
loadingProgress
(
r
->
processed_bytes
);
processEventsWhileBlocked
();
processModuleLoadingProgressEvent
(
0
);
}
}
/* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
* otherwise C_ERR is returned and 'errno' is set accordingly. */
int
rdbLoadRio
(
rio
*
rdb
,
rdbSaveInfo
*
rsi
,
int
loading_aof
)
{
int
rdbLoadRio
(
rio
*
rdb
,
int
rdbflags
,
rdbSaveInfo
*
rsi
)
{
uint64_t
dbid
;
int
type
,
rdbver
;
redisDb
*
db
=
server
.
db
+
0
;
...
...
@@ -2182,7 +2228,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
* received from the master. In the latter case, the master is
* responsible for key expiry. If we would expire keys here, the
* snapshot taken by the master may not be reflected on the slave. */
if
(
server
.
masterhost
==
NULL
&&
!
loading_aof
&&
expiretime
!=
-
1
&&
expiretime
<
now
)
{
if
(
server
.
masterhost
==
NULL
&&
!
(
rdbflags
&
RDBFLAGS_AOF_PREAMBLE
)
&&
expiretime
!=
-
1
&&
expiretime
<
now
)
{
decrRefCount
(
key
);
decrRefCount
(
val
);
}
else
{
...
...
@@ -2243,17 +2289,17 @@ eoferr:
*
* If you pass an 'rsi' structure initialied with RDB_SAVE_OPTION_INIT, the
* loading code will fiil the information fields in the structure. */
int
rdbLoad
(
char
*
filename
,
rdbSaveInfo
*
rsi
)
{
int
rdbLoad
(
char
*
filename
,
rdbSaveInfo
*
rsi
,
int
rdbflags
)
{
FILE
*
fp
;
rio
rdb
;
int
retval
;
if
((
fp
=
fopen
(
filename
,
"r"
))
==
NULL
)
return
C_ERR
;
startLoadingFile
(
fp
,
filename
);
startLoadingFile
(
fp
,
filename
,
rdbflags
);
rioInitWithFile
(
&
rdb
,
fp
);
retval
=
rdbLoadRio
(
&
rdb
,
rsi
,
0
);
retval
=
rdbLoadRio
(
&
rdb
,
r
dbflags
,
r
si
);
fclose
(
fp
);
stopLoading
();
stopLoading
(
retval
==
C_OK
);
return
retval
;
}
...
...
src/rdb.h
View file @
0f026af1
...
...
@@ -121,8 +121,10 @@
#define RDB_LOAD_PLAIN (1<<1)
#define RDB_LOAD_SDS (1<<2)
#define RDB_SAVE_NONE 0
#define RDB_SAVE_AOF_PREAMBLE (1<<0)
/* flags on the purpose of rdb save or load */
#define RDBFLAGS_NONE 0
#define RDBFLAGS_AOF_PREAMBLE (1<<0)
#define RDBFLAGS_REPLICATION (1<<1)
int
rdbSaveType
(
rio
*
rdb
,
unsigned
char
type
);
int
rdbLoadType
(
rio
*
rdb
);
...
...
@@ -135,7 +137,7 @@ uint64_t rdbLoadLen(rio *rdb, int *isencoded);
int
rdbLoadLenByRef
(
rio
*
rdb
,
int
*
isencoded
,
uint64_t
*
lenptr
);
int
rdbSaveObjectType
(
rio
*
rdb
,
robj
*
o
);
int
rdbLoadObjectType
(
rio
*
rdb
);
int
rdbLoad
(
char
*
filename
,
rdbSaveInfo
*
rsi
);
int
rdbLoad
(
char
*
filename
,
rdbSaveInfo
*
rsi
,
int
rdbflags
);
int
rdbSaveBackground
(
char
*
filename
,
rdbSaveInfo
*
rsi
);
int
rdbSaveToSlavesSockets
(
rdbSaveInfo
*
rsi
);
void
rdbRemoveTempFile
(
pid_t
childpid
);
...
...
@@ -154,7 +156,8 @@ int rdbSaveBinaryDoubleValue(rio *rdb, double val);
int
rdbLoadBinaryDoubleValue
(
rio
*
rdb
,
double
*
val
);
int
rdbSaveBinaryFloatValue
(
rio
*
rdb
,
float
val
);
int
rdbLoadBinaryFloatValue
(
rio
*
rdb
,
float
*
val
);
int
rdbLoadRio
(
rio
*
rdb
,
rdbSaveInfo
*
rsi
,
int
loading_aof
);
int
rdbLoadRio
(
rio
*
rdb
,
int
rdbflags
,
rdbSaveInfo
*
rsi
);
int
rdbSaveRio
(
rio
*
rdb
,
int
*
error
,
int
rdbflags
,
rdbSaveInfo
*
rsi
);
rdbSaveInfo
*
rdbPopulateSaveInfo
(
rdbSaveInfo
*
rsi
);
#endif
src/redis-check-rdb.c
View file @
0f026af1
...
...
@@ -202,7 +202,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
}
expiretime
=
-
1
;
startLoadingFile
(
fp
,
rdbfilename
);
startLoadingFile
(
fp
,
rdbfilename
,
RDBFLAGS_NONE
);
while
(
1
)
{
robj
*
key
,
*
val
;
...
...
@@ -316,7 +316,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
}
if
(
closefile
)
fclose
(
fp
);
stopLoading
();
stopLoading
(
1
);
return
0
;
eoferr:
/* unexpected end of file is handled here with a fatal exit */
...
...
@@ -327,7 +327,7 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
}
err:
if
(
closefile
)
fclose
(
fp
);
stopLoading
();
stopLoading
(
0
);
return
1
;
}
...
...
src/redismodule.h
View file @
0f026af1
...
...
@@ -18,6 +18,10 @@
#define REDISMODULE_READ (1<<0)
#define REDISMODULE_WRITE (1<<1)
/* RedisModule_OpenKey extra flags for the 'mode' argument.
* Avoid touching the LRU/LFU of the key when opened. */
#define REDISMODULE_OPEN_KEY_NOTOUCH (1<<16)
#define REDISMODULE_LIST_HEAD 0
#define REDISMODULE_LIST_TAIL 1
...
...
@@ -181,6 +185,8 @@ typedef uint64_t RedisModuleTimerID;
#define REDISMODULE_EVENT_REPLICA_CHANGE 6
#define REDISMODULE_EVENT_MASTER_LINK_CHANGE 7
#define REDISMODULE_EVENT_CRON_LOOP 8
#define REDISMODULE_EVENT_MODULE_CHANGE 9
#define REDISMODULE_EVENT_LOADING_PROGRESS 10
typedef
struct
RedisModuleEvent
{
uint64_t
id
;
/* REDISMODULE_EVENT_... defines. */
...
...
@@ -226,18 +232,28 @@ static const RedisModuleEvent
RedisModuleEvent_MasterLinkChange
=
{
REDISMODULE_EVENT_MASTER_LINK_CHANGE
,
1
},
RedisModuleEvent_ModuleChange
=
{
REDISMODULE_EVENT_MODULE_CHANGE
,
1
},
RedisModuleEvent_LoadingProgress
=
{
REDISMODULE_EVENT_LOADING_PROGRESS
,
1
};
/* Those are values that are used for the 'subevent' callback argument. */
#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START 0
#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_END 1
#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START 2
#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_END 3
#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START 1
#define REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START 2
#define REDISMODULE_SUBEVENT_PERSISTENCE_ENDED 3
#define REDISMODULE_SUBEVENT_PERSISTENCE_FAILED 4
#define REDISMODULE_SUBEVENT_LOADING_RDB_START 0
#define REDISMODULE_SUBEVENT_LOADING_RDB_END 1
#define REDISMODULE_SUBEVENT_LOADING_AOF_START 2
#define REDISMODULE_SUBEVENT_LOADING_AOF_END 3
#define REDISMODULE_SUBEVENT_LOADING_AOF_START 1
#define REDISMODULE_SUBEVENT_LOADING_REPL_START 2
#define REDISMODULE_SUBEVENT_LOADING_ENDED 3
#define REDISMODULE_SUBEVENT_LOADING_FAILED 4
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED 0
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED 1
...
...
@@ -245,12 +261,21 @@ static const RedisModuleEvent
#define REDISMODULE_SUBEVENT_MASTER_LINK_UP 0
#define REDISMODULE_SUBEVENT_MASTER_LINK_DOWN 1
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_CONNECTED 0
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_DISCONNECTED 1
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE 0
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE 1
#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER 0
#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA 1
#define REDISMODULE_SUBEVENT_FLUSHDB_START 0
#define REDISMODULE_SUBEVENT_FLUSHDB_END 1
#define REDISMODULE_SUBEVENT_MODULE_LOADED 0
#define REDISMODULE_SUBEVENT_MODULE_UNLOADED 1
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB 0
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF 1
/* RedisModuleClientInfo flags. */
#define REDISMODULE_CLIENTINFO_FLAG_SSL (1<<0)
#define REDISMODULE_CLIENTINFO_FLAG_PUBSUB (1<<1)
...
...
@@ -286,6 +311,22 @@ typedef struct RedisModuleClientInfo {
#define RedisModuleClientInfo RedisModuleClientInfoV1
#define REDISMODULE_REPLICATIONINFO_VERSION 1
typedef
struct
RedisModuleReplicationInfo
{
uint64_t
version
;
/* Not used since this structure is never passed
from the module to the core right now. Here
for future compatibility. */
int
master
;
/* true if master, false if replica */
char
*
masterhost
;
/* master instance hostname for NOW_REPLICA */
int
masterport
;
/* master instance port for NOW_REPLICA */
char
*
replid1
;
/* Main replication ID */
char
*
replid2
;
/* Secondary replication ID */
uint64_t
repl1_offset
;
/* Main replication offset */
uint64_t
repl2_offset
;
/* Offset of replid2 validity */
}
RedisModuleReplicationInfoV1
;
#define RedisModuleReplicationInfo RedisModuleReplicationInfoV1
#define REDISMODULE_FLUSHINFO_VERSION 1
typedef
struct
RedisModuleFlushInfo
{
uint64_t
version
;
/* Not used since this structure is never passed
...
...
@@ -297,6 +338,39 @@ typedef struct RedisModuleFlushInfo {
#define RedisModuleFlushInfo RedisModuleFlushInfoV1
#define REDISMODULE_MODULE_CHANGE_VERSION 1
typedef
struct
RedisModuleModuleChange
{
uint64_t
version
;
/* Not used since this structure is never passed
from the module to the core right now. Here
for future compatibility. */
const
char
*
module_name
;
/* Name of module loaded or unloaded. */
int32_t
module_version
;
/* Module version. */
}
RedisModuleModuleChangeV1
;
#define RedisModuleModuleChange RedisModuleModuleChangeV1
#define REDISMODULE_CRON_LOOP_VERSION 1
typedef
struct
RedisModuleCronLoopInfo
{
uint64_t
version
;
/* Not used since this structure is never passed
from the module to the core right now. Here
for future compatibility. */
int32_t
hz
;
/* Approximate number of events per second. */
}
RedisModuleCronLoopV1
;
#define RedisModuleCronLoop RedisModuleCronLoopV1
#define REDISMODULE_LOADING_PROGRESS_VERSION 1
typedef
struct
RedisModuleLoadingProgressInfo
{
uint64_t
version
;
/* Not used since this structure is never passed
from the module to the core right now. Here
for future compatibility. */
int32_t
hz
;
/* Approximate number of events per second. */
int32_t
progress
;
/* Approximate progress between 0 and 1024, or -1
* if unknown. */
}
RedisModuleLoadingProgressV1
;
#define RedisModuleLoadingProgress RedisModuleLoadingProgressV1
/* ------------------------- End of common defines ------------------------ */
#ifndef REDISMODULE_CORE
...
...
@@ -416,6 +490,9 @@ char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *l
int
REDISMODULE_API_FUNC
(
RedisModule_StringTruncate
)(
RedisModuleKey
*
key
,
size_t
newlen
);
mstime_t
REDISMODULE_API_FUNC
(
RedisModule_GetExpire
)(
RedisModuleKey
*
key
);
int
REDISMODULE_API_FUNC
(
RedisModule_SetExpire
)(
RedisModuleKey
*
key
,
mstime_t
expire
);
void
REDISMODULE_API_FUNC
(
RedisModule_ResetDataset
)(
int
restart_aof
,
int
async
);
unsigned
long
long
REDISMODULE_API_FUNC
(
RedisModule_DbSize
)(
RedisModuleCtx
*
ctx
);
RedisModuleString
*
REDISMODULE_API_FUNC
(
RedisModule_RandomKey
)(
RedisModuleCtx
*
ctx
);
int
REDISMODULE_API_FUNC
(
RedisModule_ZsetAdd
)(
RedisModuleKey
*
key
,
double
score
,
RedisModuleString
*
ele
,
int
*
flagsptr
);
int
REDISMODULE_API_FUNC
(
RedisModule_ZsetIncrby
)(
RedisModuleKey
*
key
,
double
score
,
RedisModuleString
*
ele
,
int
*
flagsptr
,
double
*
newscore
);
int
REDISMODULE_API_FUNC
(
RedisModule_ZsetScore
)(
RedisModuleKey
*
key
,
RedisModuleString
*
ele
,
double
*
score
);
...
...
@@ -435,6 +512,7 @@ int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx)
void
REDISMODULE_API_FUNC
(
RedisModule_KeyAtPos
)(
RedisModuleCtx
*
ctx
,
int
pos
);
unsigned
long
long
REDISMODULE_API_FUNC
(
RedisModule_GetClientId
)(
RedisModuleCtx
*
ctx
);
int
REDISMODULE_API_FUNC
(
RedisModule_GetClientInfoById
)(
void
*
ci
,
uint64_t
id
);
int
REDISMODULE_API_FUNC
(
RedisModule_PublishMessage
)(
RedisModuleCtx
*
ctx
,
RedisModuleString
*
channel
,
RedisModuleString
*
message
);
int
REDISMODULE_API_FUNC
(
RedisModule_GetContextFlags
)(
RedisModuleCtx
*
ctx
);
void
*
REDISMODULE_API_FUNC
(
RedisModule_PoolAlloc
)(
RedisModuleCtx
*
ctx
,
size_t
bytes
);
RedisModuleType
*
REDISMODULE_API_FUNC
(
RedisModule_CreateDataType
)(
RedisModuleCtx
*
ctx
,
const
char
*
name
,
int
encver
,
RedisModuleTypeMethods
*
typemethods
);
...
...
@@ -457,6 +535,8 @@ void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double valu
double
REDISMODULE_API_FUNC
(
RedisModule_LoadDouble
)(
RedisModuleIO
*
io
);
void
REDISMODULE_API_FUNC
(
RedisModule_SaveFloat
)(
RedisModuleIO
*
io
,
float
value
);
float
REDISMODULE_API_FUNC
(
RedisModule_LoadFloat
)(
RedisModuleIO
*
io
);
void
REDISMODULE_API_FUNC
(
RedisModule_SaveLongDouble
)(
RedisModuleIO
*
io
,
long
double
value
);
long
double
REDISMODULE_API_FUNC
(
RedisModule_LoadLongDouble
)(
RedisModuleIO
*
io
);
void
REDISMODULE_API_FUNC
(
RedisModule_Log
)(
RedisModuleCtx
*
ctx
,
const
char
*
level
,
const
char
*
fmt
,
...);
void
REDISMODULE_API_FUNC
(
RedisModule_LogIOError
)(
RedisModuleIO
*
io
,
const
char
*
levelstr
,
const
char
*
fmt
,
...);
void
REDISMODULE_API_FUNC
(
RedisModule__Assert
)(
const
char
*
estr
,
const
char
*
file
,
int
line
);
...
...
@@ -503,6 +583,8 @@ int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx
int
REDISMODULE_API_FUNC
(
RedisModule_InfoAddFieldLongLong
)(
RedisModuleInfoCtx
*
ctx
,
char
*
field
,
long
long
value
);
int
REDISMODULE_API_FUNC
(
RedisModule_InfoAddFieldULongLong
)(
RedisModuleInfoCtx
*
ctx
,
char
*
field
,
unsigned
long
long
value
);
int
REDISMODULE_API_FUNC
(
RedisModule_SubscribeToServerEvent
)(
RedisModuleCtx
*
ctx
,
RedisModuleEvent
event
,
RedisModuleEventCallback
callback
);
int
REDISMODULE_API_FUNC
(
RedisModule_SetLRUOrLFU
)(
RedisModuleKey
*
key
,
long
long
lfu_freq
,
long
long
lru_idle
);
int
REDISMODULE_API_FUNC
(
RedisModule_GetLRUOrLFU
)(
RedisModuleKey
*
key
,
long
long
*
lfu_freq
,
long
long
*
lru_idle
);
RedisModuleBlockedClient
*
REDISMODULE_API_FUNC
(
RedisModule_BlockClientOnKeys
)(
RedisModuleCtx
*
ctx
,
RedisModuleCmdFunc
reply_callback
,
RedisModuleCmdFunc
timeout_callback
,
void
(
*
free_privdata
)(
RedisModuleCtx
*
,
void
*
),
long
long
timeout_ms
,
RedisModuleString
**
keys
,
int
numkeys
,
void
*
privdata
);
void
REDISMODULE_API_FUNC
(
RedisModule_SignalKeyAsReady
)(
RedisModuleCtx
*
ctx
,
RedisModuleString
*
key
);
RedisModuleString
*
REDISMODULE_API_FUNC
(
RedisModule_GetBlockedClientReadyKey
)(
RedisModuleCtx
*
ctx
);
...
...
@@ -619,6 +701,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API
(
StringTruncate
);
REDISMODULE_GET_API
(
GetExpire
);
REDISMODULE_GET_API
(
SetExpire
);
REDISMODULE_GET_API
(
ResetDataset
);
REDISMODULE_GET_API
(
DbSize
);
REDISMODULE_GET_API
(
RandomKey
);
REDISMODULE_GET_API
(
ZsetAdd
);
REDISMODULE_GET_API
(
ZsetIncrby
);
REDISMODULE_GET_API
(
ZsetScore
);
...
...
@@ -658,6 +743,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API
(
LoadDouble
);
REDISMODULE_GET_API
(
SaveFloat
);
REDISMODULE_GET_API
(
LoadFloat
);
REDISMODULE_GET_API
(
SaveLongDouble
);
REDISMODULE_GET_API
(
LoadLongDouble
);
REDISMODULE_GET_API
(
EmitAOF
);
REDISMODULE_GET_API
(
Log
);
REDISMODULE_GET_API
(
LogIOError
);
...
...
@@ -705,7 +792,10 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API
(
InfoAddFieldLongLong
);
REDISMODULE_GET_API
(
InfoAddFieldULongLong
);
REDISMODULE_GET_API
(
GetClientInfoById
);
REDISMODULE_GET_API
(
PublishMessage
);
REDISMODULE_GET_API
(
SubscribeToServerEvent
);
REDISMODULE_GET_API
(
SetLRUOrLFU
);
REDISMODULE_GET_API
(
GetLRUOrLFU
);
REDISMODULE_GET_API
(
BlockClientOnKeys
);
REDISMODULE_GET_API
(
SignalKeyAsReady
);
REDISMODULE_GET_API
(
GetBlockedClientReadyKey
);
...
...
src/replication.c
View file @
0f026af1
...
...
@@ -533,6 +533,12 @@ int masterTryPartialResynchronization(client *c) {
* has this state from the previous connection with the master. */
refreshGoodSlavesCount
();
/* Fire the replica change modules event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_REPLICA_CHANGE
,
REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE
,
NULL
);
return
C_OK
;
/* The caller can return, no full resync needed. */
need_full_resync:
...
...
@@ -868,6 +874,10 @@ void putSlaveOnline(client *slave) {
return
;
}
refreshGoodSlavesCount
();
/* Fire the replica change modules event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_REPLICA_CHANGE
,
REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE
,
NULL
);
serverLog
(
LL_NOTICE
,
"Synchronization with replica %s succeeded"
,
replicationGetSlaveName
(
slave
));
}
...
...
@@ -1542,11 +1552,11 @@ void readSyncBulkPayload(connection *conn) {
* We'll restore it when the RDB is received. */
connBlock
(
conn
);
connRecvTimeout
(
conn
,
server
.
repl_timeout
*
1000
);
startLoading
(
server
.
repl_transfer_size
);
startLoading
(
server
.
repl_transfer_size
,
RDBFLAGS_REPLICATION
);
if
(
rdbLoadRio
(
&
rdb
,
&
rsi
,
0
)
!=
C_OK
)
{
if
(
rdbLoadRio
(
&
rdb
,
RDBFLAGS_REPLICATION
,
&
rsi
)
!=
C_OK
)
{
/* RDB loading failed. */
stopLoading
();
stopLoading
(
0
);
serverLog
(
LL_WARNING
,
"Failed trying to load the MASTER synchronization DB "
"from socket"
);
...
...
@@ -1567,7 +1577,7 @@ void readSyncBulkPayload(connection *conn) {
* gets promoted. */
return
;
}
stopLoading
();
stopLoading
(
1
);
/* RDB loading succeeded if we reach this point. */
if
(
server
.
repl_diskless_load
==
REPL_DISKLESS_LOAD_SWAPDB
)
{
...
...
@@ -1614,7 +1624,7 @@ void readSyncBulkPayload(connection *conn) {
cancelReplicationHandshake
();
return
;
}
if
(
rdbLoad
(
server
.
rdb_filename
,
&
rsi
)
!=
C_OK
)
{
if
(
rdbLoad
(
server
.
rdb_filename
,
&
rsi
,
RDBFLAGS_REPLICATION
)
!=
C_OK
)
{
serverLog
(
LL_WARNING
,
"Failed trying to load the MASTER synchronization "
"DB from disk"
);
...
...
@@ -1636,6 +1646,11 @@ void readSyncBulkPayload(connection *conn) {
server
.
repl_state
=
REPL_STATE_CONNECTED
;
server
.
repl_down_since
=
0
;
/* Fire the master link modules event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_MASTER_LINK_CHANGE
,
REDISMODULE_SUBEVENT_MASTER_LINK_UP
,
NULL
);
/* After a full resynchroniziation we use the replication ID and
* offset of the master. The secondary ID / offset are cleared since
* we are starting a new history. */
...
...
@@ -2314,12 +2329,31 @@ void replicationSetMaster(char *ip, int port) {
replicationDiscardCachedMaster
();
replicationCacheMasterUsingMyself
();
}
/* Fire the role change modules event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED
,
REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA
,
NULL
);
/* Fire the master link modules event. */
if
(
server
.
repl_state
==
REPL_STATE_CONNECTED
)
moduleFireServerEvent
(
REDISMODULE_EVENT_MASTER_LINK_CHANGE
,
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN
,
NULL
);
server
.
repl_state
=
REPL_STATE_CONNECT
;
}
/* Cancel replication, setting the instance as a master itself. */
void
replicationUnsetMaster
(
void
)
{
if
(
server
.
masterhost
==
NULL
)
return
;
/* Nothing to do. */
/* Fire the master link modules event. */
if
(
server
.
repl_state
==
REPL_STATE_CONNECTED
)
moduleFireServerEvent
(
REDISMODULE_EVENT_MASTER_LINK_CHANGE
,
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN
,
NULL
);
sdsfree
(
server
.
masterhost
);
server
.
masterhost
=
NULL
;
/* When a slave is turned into a master, the current replication ID
...
...
@@ -2348,11 +2382,22 @@ void replicationUnsetMaster(void) {
* starting from now. Otherwise the backlog will be freed after a
* failover if slaves do not connect immediately. */
server
.
repl_no_slaves_since
=
server
.
unixtime
;
/* Fire the role change modules event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED
,
REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER
,
NULL
);
}
/* This function is called when the slave lose the connection with the
* master into an unexpected way. */
void
replicationHandleMasterDisconnection
(
void
)
{
/* Fire the master link modules event. */
if
(
server
.
repl_state
==
REPL_STATE_CONNECTED
)
moduleFireServerEvent
(
REDISMODULE_EVENT_MASTER_LINK_CHANGE
,
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN
,
NULL
);
server
.
master
=
NULL
;
server
.
repl_state
=
REPL_STATE_CONNECT
;
server
.
repl_down_since
=
server
.
unixtime
;
...
...
src/server.c
View file @
0f026af1
...
...
@@ -2065,6 +2065,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
server
.
rdb_bgsave_scheduled
=
0
;
}
/* Fire the cron loop modules event. */
RedisModuleCronLoopV1
ei
=
{
REDISMODULE_CRON_LOOP_VERSION
,
server
.
hz
};
moduleFireServerEvent
(
REDISMODULE_EVENT_CRON_LOOP
,
0
,
&
ei
);
server
.
cronloops
++
;
return
1000
/
server
.
hz
;
}
...
...
@@ -3697,6 +3703,9 @@ int prepareForShutdown(int flags) {
}
}
/* Fire the shutdown modules event. */
moduleFireServerEvent
(
REDISMODULE_EVENT_SHUTDOWN
,
0
,
NULL
);
/* Remove the pid file if possible and needed. */
if
(
server
.
daemonize
||
server
.
pidfile
)
{
serverLog
(
LL_NOTICE
,
"Removing the pid file."
);
...
...
@@ -4782,7 +4791,7 @@ void loadDataFromDisk(void) {
serverLog
(
LL_NOTICE
,
"DB loaded from append only file: %.3f seconds"
,(
float
)(
ustime
()
-
start
)
/
1000000
);
}
else
{
rdbSaveInfo
rsi
=
RDB_SAVE_INFO_INIT
;
if
(
rdbLoad
(
server
.
rdb_filename
,
&
rsi
)
==
C_OK
)
{
if
(
rdbLoad
(
server
.
rdb_filename
,
&
rsi
,
RDBFLAGS_NONE
)
==
C_OK
)
{
serverLog
(
LL_NOTICE
,
"DB loaded from disk: %.3f seconds"
,
(
float
)(
ustime
()
-
start
)
/
1000000
);
...
...
src/server.h
View file @
0f026af1
...
...
@@ -1605,6 +1605,7 @@ ssize_t rdbSaveModulesAux(rio *rdb, int when);
int
moduleAllDatatypesHandleErrors
();
sds
modulesCollectInfo
(
sds
info
,
sds
section
,
int
for_crash_report
,
int
sections
);
void
moduleFireServerEvent
(
uint64_t
eid
,
int
subid
,
void
*
data
);
void
processModuleLoadingProgressEvent
(
int
is_aof
);
int
moduleTryServeClientBlockedOnKey
(
client
*
c
,
robj
*
key
);
void
moduleUnblockClient
(
client
*
c
);
int
moduleClientIsBlockedOnKeys
(
client
*
c
);
...
...
@@ -1837,10 +1838,12 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData,
void
rdbPipeWriteHandlerConnRemoved
(
struct
connection
*
conn
);
/* Generic persistence functions */
void
startLoadingFile
(
FILE
*
fp
,
char
*
filename
);
void
startLoading
(
size_t
size
);
void
startLoadingFile
(
FILE
*
fp
,
char
*
filename
,
int
rdbflags
);
void
startLoading
(
size_t
size
,
int
rdbflags
);
void
loadingProgress
(
off_t
pos
);
void
stopLoading
(
void
);
void
stopLoading
(
int
success
);
void
startSaving
(
int
rdbflags
);
void
stopSaving
(
int
success
);
#define DISK_ERROR_TYPE_AOF 1
/* Don't accept writes: AOF errors. */
#define DISK_ERROR_TYPE_RDB 2
/* Don't accept writes: RDB errors. */
...
...
@@ -1849,7 +1852,6 @@ int writeCommandsDeniedByDiskError(void);
/* RDB persistence */
#include "rdb.h"
int
rdbSaveRio
(
rio
*
rdb
,
int
*
error
,
int
flags
,
rdbSaveInfo
*
rsi
);
void
killRDBChild
(
void
);
/* AOF persistence */
...
...
@@ -1865,6 +1867,7 @@ void aofRewriteBufferReset(void);
unsigned
long
aofRewriteBufferSize
(
void
);
ssize_t
aofReadDiffFromParent
(
void
);
void
killAppendOnlyChild
(
void
);
void
restartAOFAfterSYNC
();
/* Child info */
void
openChildInfoPipe
(
void
);
...
...
@@ -2085,9 +2088,10 @@ robj *lookupKeyWrite(redisDb *db, robj *key);
robj
*
lookupKeyReadOrReply
(
client
*
c
,
robj
*
key
,
robj
*
reply
);
robj
*
lookupKeyWriteOrReply
(
client
*
c
,
robj
*
key
,
robj
*
reply
);
robj
*
lookupKeyReadWithFlags
(
redisDb
*
db
,
robj
*
key
,
int
flags
);
robj
*
lookupKeyWriteWithFlags
(
redisDb
*
db
,
robj
*
key
,
int
flags
);
robj
*
objectCommandLookup
(
client
*
c
,
robj
*
key
);
robj
*
objectCommandLookupOrReply
(
client
*
c
,
robj
*
key
,
robj
*
reply
);
void
objectSetLRUOrLFU
(
robj
*
val
,
long
long
lfu_freq
,
long
long
lru_idle
,
int
objectSetLRUOrLFU
(
robj
*
val
,
long
long
lfu_freq
,
long
long
lru_idle
,
long
long
lru_clock
);
#define LOOKUP_NONE 0
#define LOOKUP_NOTOUCH (1<<0)
...
...
@@ -2104,6 +2108,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
#define EMPTYDB_ASYNC (1<<0)
/* Reclaim memory in another thread. */
long
long
emptyDb
(
int
dbnum
,
int
flags
,
void
(
callback
)(
void
*
));
long
long
emptyDbGeneric
(
redisDb
*
dbarray
,
int
dbnum
,
int
flags
,
void
(
callback
)(
void
*
));
void
flushAllDataAndResetRDB
(
int
flags
);
long
long
dbTotalServerKeyCount
();
int
selectDb
(
client
*
c
,
int
id
);
...
...
src/t_hash.c
View file @
0f026af1
...
...
@@ -621,7 +621,7 @@ void hincrbyfloatCommand(client *c) {
}
char
buf
[
MAX_LONG_DOUBLE_CHARS
];
int
len
=
ld2string
(
buf
,
sizeof
(
buf
),
value
,
1
);
int
len
=
ld2string
(
buf
,
sizeof
(
buf
),
value
,
LD_STR_HUMAN
);
new
=
sdsnewlen
(
buf
,
len
);
hashTypeSet
(
o
,
c
->
argv
[
2
]
->
ptr
,
new
,
HASH_SET_TAKE_VALUE
);
addReplyBulkCBuffer
(
c
,
buf
,
len
);
...
...
src/t_stream.c
View file @
0f026af1
...
...
@@ -173,9 +173,19 @@ int streamCompareID(streamID *a, streamID *b) {
* C_ERR if an ID was given via 'use_id', but adding it failed since the
* current top ID is greater or equal. */
int
streamAppendItem
(
stream
*
s
,
robj
**
argv
,
int64_t
numfields
,
streamID
*
added_id
,
streamID
*
use_id
)
{
/* If an ID was given, check that it's greater than the last entry ID
* or return an error. */
if
(
use_id
&&
streamCompareID
(
use_id
,
&
s
->
last_id
)
<=
0
)
return
C_ERR
;
/* Generate the new entry ID. */
streamID
id
;
if
(
use_id
)
id
=
*
use_id
;
else
streamNextID
(
&
s
->
last_id
,
&
id
);
/* Check that the new ID is greater than the last entry ID
* or return an error. Automatically generated IDs might
* overflow (and wrap-around) when incrementing the sequence
part. */
if
(
streamCompareID
(
&
id
,
&
s
->
last_id
)
<=
0
)
return
C_ERR
;
/* Add the new entry. */
raxIterator
ri
;
...
...
@@ -192,13 +202,6 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
}
raxStop
(
&
ri
);
/* Generate the new entry ID. */
streamID
id
;
if
(
use_id
)
id
=
*
use_id
;
else
streamNextID
(
&
s
->
last_id
,
&
id
);
/* We have to add the key into the radix tree in lexicographic order,
* to do so we consider the ID as a single 128 bit number written in
* big endian, so that the most significant bytes are the first ones. */
...
...
src/util.c
View file @
0f026af1
...
...
@@ -510,15 +510,17 @@ int d2string(char *buf, size_t len, double value) {
return
len
;
}
/* Convert a long double into a string. If humanfriendly is non-zero
* it does not use exponential format and trims trailing zeroes at the end,
* however this results in loss of precision. Otherwise exp format is used
* and the output of snprintf() is not modified.
/* Create a string object from a long double.
* If mode is humanfriendly it does not use exponential format and trims trailing
* zeroes at the end (may result in loss of precision).
* If mode is default exp format is used and the output of snprintf()
* is not modified (may result in loss of precision).
* If mode is hex hexadecimal format is used (no loss of precision)
*
* The function returns the length of the string or zero if there was not
* enough buffer room to store it. */
int
ld2string
(
char
*
buf
,
size_t
len
,
long
double
value
,
int
humanfriendly
)
{
size_t
l
;
int
ld2string
(
char
*
buf
,
size_t
len
,
long
double
value
,
ld2string_mode
mode
)
{
size_t
l
=
0
;
if
(
isinf
(
value
))
{
/* Libc in odd systems (Hi Solaris!) will format infinite in a
...
...
@@ -531,26 +533,36 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
memcpy
(
buf
,
"-inf"
,
4
);
l
=
4
;
}
}
else
if
(
humanfriendly
)
{
/* We use 17 digits precision since with 128 bit floats that precision
* after rounding is able to represent most small decimal numbers in a
* way that is "non surprising" for the user (that is, most small
* decimal numbers will be represented in a way that when converted
* back into a string are exactly the same as what the user typed.) */
l
=
snprintf
(
buf
,
len
,
"%.17Lf"
,
value
);
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
/* Now remove trailing zeroes after the '.' */
if
(
strchr
(
buf
,
'.'
)
!=
NULL
)
{
char
*
p
=
buf
+
l
-
1
;
while
(
*
p
==
'0'
)
{
p
--
;
l
--
;
}
else
{
switch
(
mode
)
{
case
LD_STR_AUTO
:
l
=
snprintf
(
buf
,
len
,
"%.17Lg"
,
value
);
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
break
;
case
LD_STR_HEX
:
l
=
snprintf
(
buf
,
len
,
"%La"
,
value
);
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
break
;
case
LD_STR_HUMAN
:
/* We use 17 digits precision since with 128 bit floats that precision
* after rounding is able to represent most small decimal numbers in a
* way that is "non surprising" for the user (that is, most small
* decimal numbers will be represented in a way that when converted
* back into a string are exactly the same as what the user typed.) */
l
=
snprintf
(
buf
,
len
,
"%.17Lf"
,
value
);
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
/* Now remove trailing zeroes after the '.' */
if
(
strchr
(
buf
,
'.'
)
!=
NULL
)
{
char
*
p
=
buf
+
l
-
1
;
while
(
*
p
==
'0'
)
{
p
--
;
l
--
;
}
if
(
*
p
==
'.'
)
l
--
;
}
if
(
*
p
==
'.'
)
l
--
;
break
;
default:
return
0
;
/* Invalid mode. */
}
}
else
{
l
=
snprintf
(
buf
,
len
,
"%.17Lg"
,
value
);
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
}
buf
[
l
]
=
'\0'
;
return
l
;
...
...
src/util.h
View file @
0f026af1
...
...
@@ -38,6 +38,13 @@
* This should be the size of the buffer given to ld2string */
#define MAX_LONG_DOUBLE_CHARS 5*1024
/* long double to string convertion options */
typedef
enum
{
LD_STR_AUTO
,
/* %.17Lg */
LD_STR_HUMAN
,
/* %.17Lf + Trimming of trailing zeros */
LD_STR_HEX
/* %La */
}
ld2string_mode
;
int
stringmatchlen
(
const
char
*
p
,
int
plen
,
const
char
*
s
,
int
slen
,
int
nocase
);
int
stringmatch
(
const
char
*
p
,
const
char
*
s
,
int
nocase
);
int
stringmatchlen_fuzz_test
(
void
);
...
...
@@ -49,7 +56,7 @@ int string2ll(const char *s, size_t slen, long long *value);
int
string2l
(
const
char
*
s
,
size_t
slen
,
long
*
value
);
int
string2ld
(
const
char
*
s
,
size_t
slen
,
long
double
*
dp
);
int
d2string
(
char
*
buf
,
size_t
len
,
double
value
);
int
ld2string
(
char
*
buf
,
size_t
len
,
long
double
value
,
int
humanfriendly
);
int
ld2string
(
char
*
buf
,
size_t
len
,
long
double
value
,
ld2string_mode
mode
);
sds
getAbsolutePath
(
char
*
filename
);
unsigned
long
getTimeZone
(
void
);
int
pathIsBaseName
(
char
*
path
);
...
...
tests/modules/Makefile
View file @
0f026af1
...
...
@@ -18,7 +18,8 @@ TEST_MODULES = \
infotest.so
\
propagate.so
\
misc.so
\
hooks.so
hooks.so
\
blockonkeys.so
.PHONY
:
all
...
...
tests/modules/blockonkeys.c
0 → 100644
View file @
0f026af1
#define REDISMODULE_EXPERIMENTAL_API
#include "redismodule.h"
#include <string.h>
#include <assert.h>
#include <unistd.h>
#define LIST_SIZE 1024
typedef
struct
{
long
long
list
[
LIST_SIZE
];
long
long
length
;
}
fsl_t
;
/* Fixed-size list */
static
RedisModuleType
*
fsltype
=
NULL
;
fsl_t
*
fsl_type_create
()
{
fsl_t
*
o
;
o
=
RedisModule_Alloc
(
sizeof
(
*
o
));
o
->
length
=
0
;
return
o
;
}
void
fsl_type_free
(
fsl_t
*
o
)
{
RedisModule_Free
(
o
);
}
/* ========================== "fsltype" type methods ======================= */
void
*
fsl_rdb_load
(
RedisModuleIO
*
rdb
,
int
encver
)
{
if
(
encver
!=
0
)
{
return
NULL
;
}
fsl_t
*
fsl
=
fsl_type_create
();
fsl
->
length
=
RedisModule_LoadUnsigned
(
rdb
);
for
(
long
long
i
=
0
;
i
<
fsl
->
length
;
i
++
)
fsl
->
list
[
i
]
=
RedisModule_LoadSigned
(
rdb
);
return
fsl
;
}
void
fsl_rdb_save
(
RedisModuleIO
*
rdb
,
void
*
value
)
{
fsl_t
*
fsl
=
value
;
RedisModule_SaveUnsigned
(
rdb
,
fsl
->
length
);
for
(
long
long
i
=
0
;
i
<
fsl
->
length
;
i
++
)
RedisModule_SaveSigned
(
rdb
,
fsl
->
list
[
i
]);
}
void
fsl_aofrw
(
RedisModuleIO
*
aof
,
RedisModuleString
*
key
,
void
*
value
)
{
fsl_t
*
fsl
=
value
;
for
(
long
long
i
=
0
;
i
<
fsl
->
length
;
i
++
)
RedisModule_EmitAOF
(
aof
,
"FSL.PUSH"
,
"sl"
,
key
,
fsl
->
list
[
i
]);
}
void
fsl_free
(
void
*
value
)
{
fsl_type_free
(
value
);
}
/* ========================== helper methods ======================= */
int
get_fsl
(
RedisModuleCtx
*
ctx
,
RedisModuleString
*
keyname
,
int
mode
,
int
create
,
fsl_t
**
fsl
,
int
reply_on_failure
)
{
RedisModuleKey
*
key
=
RedisModule_OpenKey
(
ctx
,
keyname
,
mode
);
int
type
=
RedisModule_KeyType
(
key
);
if
(
type
!=
REDISMODULE_KEYTYPE_EMPTY
&&
RedisModule_ModuleTypeGetType
(
key
)
!=
fsltype
)
{
RedisModule_CloseKey
(
key
);
if
(
reply_on_failure
)
RedisModule_ReplyWithError
(
ctx
,
REDISMODULE_ERRORMSG_WRONGTYPE
);
return
0
;
}
/* Create an empty value object if the key is currently empty. */
if
(
type
==
REDISMODULE_KEYTYPE_EMPTY
)
{
if
(
!
create
)
{
/* Key is empty but we cannot create */
RedisModule_CloseKey
(
key
);
*
fsl
=
NULL
;
return
1
;
}
*
fsl
=
fsl_type_create
();
RedisModule_ModuleTypeSetValue
(
key
,
fsltype
,
*
fsl
);
}
else
{
*
fsl
=
RedisModule_ModuleTypeGetValue
(
key
);
}
RedisModule_CloseKey
(
key
);
return
1
;
}
/* ========================== commands ======================= */
/* FSL.PUSH <key> <int> - Push an integer to the fixed-size list (to the right).
* It must be greater than the element in the head of the list. */
int
fsl_push
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
if
(
argc
!=
3
)
return
RedisModule_WrongArity
(
ctx
);
long
long
ele
;
if
(
RedisModule_StringToLongLong
(
argv
[
2
],
&
ele
)
!=
REDISMODULE_OK
)
return
RedisModule_ReplyWithError
(
ctx
,
"ERR invalid integer"
);
fsl_t
*
fsl
;
if
(
!
get_fsl
(
ctx
,
argv
[
1
],
REDISMODULE_WRITE
,
1
,
&
fsl
,
1
))
return
REDISMODULE_OK
;
if
(
fsl
->
length
==
LIST_SIZE
)
return
RedisModule_ReplyWithError
(
ctx
,
"ERR list is full"
);
if
(
fsl
->
length
!=
0
&&
fsl
->
list
[
fsl
->
length
-
1
]
>=
ele
)
return
RedisModule_ReplyWithError
(
ctx
,
"ERR new element has to be greater than the head element"
);
fsl
->
list
[
fsl
->
length
++
]
=
ele
;
if
(
fsl
->
length
>=
2
)
RedisModule_SignalKeyAsReady
(
ctx
,
argv
[
1
]);
return
RedisModule_ReplyWithSimpleString
(
ctx
,
"OK"
);
}
int
bpop2_reply_callback
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
REDISMODULE_NOT_USED
(
argv
);
REDISMODULE_NOT_USED
(
argc
);
RedisModuleString
*
keyname
=
RedisModule_GetBlockedClientReadyKey
(
ctx
);
fsl_t
*
fsl
;
if
(
!
get_fsl
(
ctx
,
keyname
,
REDISMODULE_READ
,
0
,
&
fsl
,
0
))
return
REDISMODULE_ERR
;
if
(
!
fsl
||
fsl
->
length
<
2
)
return
REDISMODULE_ERR
;
RedisModule_ReplyWithArray
(
ctx
,
2
);
RedisModule_ReplyWithLongLong
(
ctx
,
fsl
->
list
[
--
fsl
->
length
]);
RedisModule_ReplyWithLongLong
(
ctx
,
fsl
->
list
[
--
fsl
->
length
]);
return
REDISMODULE_OK
;
}
int
bpop2_timeout_callback
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
REDISMODULE_NOT_USED
(
argv
);
REDISMODULE_NOT_USED
(
argc
);
return
RedisModule_ReplyWithSimpleString
(
ctx
,
"Request timedout"
);
}
/* FSL.BPOP2 <key> <timeout> - Block clients until list has two or more elements.
* When that happens, unblock client and pop the last two elements (from the right). */
int
fsl_bpop2
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
if
(
argc
!=
3
)
return
RedisModule_WrongArity
(
ctx
);
long
long
timeout
;
if
(
RedisModule_StringToLongLong
(
argv
[
2
],
&
timeout
)
!=
REDISMODULE_OK
||
timeout
<
0
)
return
RedisModule_ReplyWithError
(
ctx
,
"ERR invalid timeout"
);
fsl_t
*
fsl
;
if
(
!
get_fsl
(
ctx
,
argv
[
1
],
REDISMODULE_READ
,
0
,
&
fsl
,
1
))
return
REDISMODULE_OK
;
if
(
!
fsl
||
fsl
->
length
<
2
)
{
/* Key is empty or has <2 elements, we must block */
RedisModule_BlockClientOnKeys
(
ctx
,
bpop2_reply_callback
,
bpop2_timeout_callback
,
NULL
,
timeout
,
&
argv
[
1
],
1
,
NULL
);
}
else
{
RedisModule_ReplyWithArray
(
ctx
,
2
);
RedisModule_ReplyWithLongLong
(
ctx
,
fsl
->
list
[
--
fsl
->
length
]);
RedisModule_ReplyWithLongLong
(
ctx
,
fsl
->
list
[
--
fsl
->
length
]);
}
return
REDISMODULE_OK
;
}
int
bpopgt_reply_callback
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
REDISMODULE_NOT_USED
(
argv
);
REDISMODULE_NOT_USED
(
argc
);
RedisModuleString
*
keyname
=
RedisModule_GetBlockedClientReadyKey
(
ctx
);
long
long
gt
=
(
long
long
)
RedisModule_GetBlockedClientPrivateData
(
ctx
);
fsl_t
*
fsl
;
if
(
!
get_fsl
(
ctx
,
keyname
,
REDISMODULE_READ
,
0
,
&
fsl
,
0
))
return
REDISMODULE_ERR
;
if
(
!
fsl
||
fsl
->
list
[
fsl
->
length
-
1
]
<=
gt
)
return
REDISMODULE_ERR
;
RedisModule_ReplyWithLongLong
(
ctx
,
fsl
->
list
[
--
fsl
->
length
]);
return
REDISMODULE_OK
;
}
int
bpopgt_timeout_callback
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
REDISMODULE_NOT_USED
(
argv
);
REDISMODULE_NOT_USED
(
argc
);
return
RedisModule_ReplyWithSimpleString
(
ctx
,
"Request timedout"
);
}
void
bpopgt_free_privdata
(
RedisModuleCtx
*
ctx
,
void
*
privdata
)
{
/* Nothing to do because privdata is actually a 'long long',
* not a pointer to the heap */
REDISMODULE_NOT_USED
(
ctx
);
REDISMODULE_NOT_USED
(
privdata
);
}
/* FSL.BPOPGT <key> <gt> <timeout> - Block clients until list has an element greater than <gt>.
* When that happens, unblock client and pop the last element (from the right). */
int
fsl_bpopgt
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
if
(
argc
!=
4
)
return
RedisModule_WrongArity
(
ctx
);
long
long
gt
;
if
(
RedisModule_StringToLongLong
(
argv
[
2
],
&
gt
)
!=
REDISMODULE_OK
)
return
RedisModule_ReplyWithError
(
ctx
,
"ERR invalid integer"
);
long
long
timeout
;
if
(
RedisModule_StringToLongLong
(
argv
[
3
],
&
timeout
)
!=
REDISMODULE_OK
||
timeout
<
0
)
return
RedisModule_ReplyWithError
(
ctx
,
"ERR invalid timeout"
);
fsl_t
*
fsl
;
if
(
!
get_fsl
(
ctx
,
argv
[
1
],
REDISMODULE_READ
,
0
,
&
fsl
,
1
))
return
REDISMODULE_OK
;
if
(
!
fsl
||
fsl
->
list
[
fsl
->
length
-
1
]
<=
gt
)
{
/* Key is empty or has <2 elements, we must block */
RedisModule_BlockClientOnKeys
(
ctx
,
bpopgt_reply_callback
,
bpopgt_timeout_callback
,
bpopgt_free_privdata
,
timeout
,
&
argv
[
1
],
1
,
(
void
*
)
gt
);
}
else
{
RedisModule_ReplyWithLongLong
(
ctx
,
fsl
->
list
[
--
fsl
->
length
]);
}
return
REDISMODULE_OK
;
}
int
RedisModule_OnLoad
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
REDISMODULE_NOT_USED
(
argv
);
REDISMODULE_NOT_USED
(
argc
);
if
(
RedisModule_Init
(
ctx
,
"blockonkeys"
,
1
,
REDISMODULE_APIVER_1
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
RedisModuleTypeMethods
tm
=
{
.
version
=
REDISMODULE_TYPE_METHOD_VERSION
,
.
rdb_load
=
fsl_rdb_load
,
.
rdb_save
=
fsl_rdb_save
,
.
aof_rewrite
=
fsl_aofrw
,
.
mem_usage
=
NULL
,
.
free
=
fsl_free
,
.
digest
=
NULL
};
fsltype
=
RedisModule_CreateDataType
(
ctx
,
"fsltype_t"
,
0
,
&
tm
);
if
(
fsltype
==
NULL
)
return
REDISMODULE_ERR
;
if
(
RedisModule_CreateCommand
(
ctx
,
"fsl.push"
,
fsl_push
,
""
,
0
,
0
,
0
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
if
(
RedisModule_CreateCommand
(
ctx
,
"fsl.bpop2"
,
fsl_bpop2
,
""
,
0
,
0
,
0
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
if
(
RedisModule_CreateCommand
(
ctx
,
"fsl.bpopgt"
,
fsl_bpopgt
,
""
,
0
,
0
,
0
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
return
REDISMODULE_OK
;
}
Prev
1
2
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