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
a83e3663
Commit
a83e3663
authored
Feb 28, 2022
by
Oran Agra
Browse files
Merge remote-tracking branch 'origin/unstable' into 7.0
parents
d5915a16
5860fa3d
Changes
142
Hide whitespace changes
Inline
Side-by-side
src/commands/xsetid.json
View file @
a83e3663
...
@@ -4,8 +4,14 @@
...
@@ -4,8 +4,14 @@
"complexity"
:
"O(1)"
,
"complexity"
:
"O(1)"
,
"group"
:
"stream"
,
"group"
:
"stream"
,
"since"
:
"5.0.0"
,
"since"
:
"5.0.0"
,
"arity"
:
3
,
"arity"
:
-
3
,
"function"
:
"xsetidCommand"
,
"function"
:
"xsetidCommand"
,
"history"
:
[
[
"7.0.0"
,
"Added the `entries_added` and `max_deleted_entry_id` arguments."
]
],
"command_flags"
:
[
"command_flags"
:
[
"WRITE"
,
"WRITE"
,
"DENYOOM"
,
"DENYOOM"
,
...
@@ -43,6 +49,18 @@
...
@@ -43,6 +49,18 @@
{
{
"name"
:
"last-id"
,
"name"
:
"last-id"
,
"type"
:
"string"
"type"
:
"string"
},
{
"name"
:
"entries_added"
,
"token"
:
"ENTRIESADDED"
,
"type"
:
"integer"
,
"optional"
:
true
},
{
"name"
:
"max_deleted_entry_id"
,
"token"
:
"MAXDELETEDID"
,
"type"
:
"string"
,
"optional"
:
true
}
}
]
]
}
}
...
...
src/connection.c
View file @
a83e3663
...
@@ -178,6 +178,21 @@ static int connSocketWrite(connection *conn, const void *data, size_t data_len)
...
@@ -178,6 +178,21 @@ static int connSocketWrite(connection *conn, const void *data, size_t data_len)
return
ret
;
return
ret
;
}
}
static
int
connSocketWritev
(
connection
*
conn
,
const
struct
iovec
*
iov
,
int
iovcnt
)
{
int
ret
=
writev
(
conn
->
fd
,
iov
,
iovcnt
);
if
(
ret
<
0
&&
errno
!=
EAGAIN
)
{
conn
->
last_errno
=
errno
;
/* Don't overwrite the state of a connection that is not already
* connected, not to mess with handler callbacks.
*/
if
(
errno
!=
EINTR
&&
conn
->
state
==
CONN_STATE_CONNECTED
)
conn
->
state
=
CONN_STATE_ERROR
;
}
return
ret
;
}
static
int
connSocketRead
(
connection
*
conn
,
void
*
buf
,
size_t
buf_len
)
{
static
int
connSocketRead
(
connection
*
conn
,
void
*
buf
,
size_t
buf_len
)
{
int
ret
=
read
(
conn
->
fd
,
buf
,
buf_len
);
int
ret
=
read
(
conn
->
fd
,
buf
,
buf_len
);
if
(
!
ret
)
{
if
(
!
ret
)
{
...
@@ -349,6 +364,7 @@ ConnectionType CT_Socket = {
...
@@ -349,6 +364,7 @@ ConnectionType CT_Socket = {
.
ae_handler
=
connSocketEventHandler
,
.
ae_handler
=
connSocketEventHandler
,
.
close
=
connSocketClose
,
.
close
=
connSocketClose
,
.
write
=
connSocketWrite
,
.
write
=
connSocketWrite
,
.
writev
=
connSocketWritev
,
.
read
=
connSocketRead
,
.
read
=
connSocketRead
,
.
accept
=
connSocketAccept
,
.
accept
=
connSocketAccept
,
.
connect
=
connSocketConnect
,
.
connect
=
connSocketConnect
,
...
...
src/connection.h
View file @
a83e3663
...
@@ -32,6 +32,7 @@
...
@@ -32,6 +32,7 @@
#define __REDIS_CONNECTION_H
#define __REDIS_CONNECTION_H
#include <errno.h>
#include <errno.h>
#include <sys/uio.h>
#define CONN_INFO_LEN 32
#define CONN_INFO_LEN 32
...
@@ -59,6 +60,7 @@ typedef struct ConnectionType {
...
@@ -59,6 +60,7 @@ typedef struct ConnectionType {
void
(
*
ae_handler
)(
struct
aeEventLoop
*
el
,
int
fd
,
void
*
clientData
,
int
mask
);
void
(
*
ae_handler
)(
struct
aeEventLoop
*
el
,
int
fd
,
void
*
clientData
,
int
mask
);
int
(
*
connect
)(
struct
connection
*
conn
,
const
char
*
addr
,
int
port
,
const
char
*
source_addr
,
ConnectionCallbackFunc
connect_handler
);
int
(
*
connect
)(
struct
connection
*
conn
,
const
char
*
addr
,
int
port
,
const
char
*
source_addr
,
ConnectionCallbackFunc
connect_handler
);
int
(
*
write
)(
struct
connection
*
conn
,
const
void
*
data
,
size_t
data_len
);
int
(
*
write
)(
struct
connection
*
conn
,
const
void
*
data
,
size_t
data_len
);
int
(
*
writev
)(
struct
connection
*
conn
,
const
struct
iovec
*
iov
,
int
iovcnt
);
int
(
*
read
)(
struct
connection
*
conn
,
void
*
buf
,
size_t
buf_len
);
int
(
*
read
)(
struct
connection
*
conn
,
void
*
buf
,
size_t
buf_len
);
void
(
*
close
)(
struct
connection
*
conn
);
void
(
*
close
)(
struct
connection
*
conn
);
int
(
*
accept
)(
struct
connection
*
conn
,
ConnectionCallbackFunc
accept_handler
);
int
(
*
accept
)(
struct
connection
*
conn
,
ConnectionCallbackFunc
accept_handler
);
...
@@ -142,6 +144,18 @@ static inline int connWrite(connection *conn, const void *data, size_t data_len)
...
@@ -142,6 +144,18 @@ static inline int connWrite(connection *conn, const void *data, size_t data_len)
return
conn
->
type
->
write
(
conn
,
data
,
data_len
);
return
conn
->
type
->
write
(
conn
,
data
,
data_len
);
}
}
/* Gather output data from the iovcnt buffers specified by the members of the iov
* array: iov[0], iov[1], ..., iov[iovcnt-1] and write to connection, behaves the same as writev(3).
*
* Like writev(3), a short write is possible. A -1 return indicates an error.
*
* The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
* connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
*/
static
inline
int
connWritev
(
connection
*
conn
,
const
struct
iovec
*
iov
,
int
iovcnt
)
{
return
conn
->
type
->
writev
(
conn
,
iov
,
iovcnt
);
}
/* Read from the connection, behaves the same as read(2).
/* Read from the connection, behaves the same as read(2).
*
*
* Like read(2), a short read is possible. A return value of 0 will indicate the
* Like read(2), a short read is possible. A return value of 0 will indicate the
...
...
src/db.c
View file @
a83e3663
...
@@ -83,16 +83,16 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
...
@@ -83,16 +83,16 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
robj
*
val
=
NULL
;
robj
*
val
=
NULL
;
if
(
de
)
{
if
(
de
)
{
val
=
dictGetVal
(
de
);
val
=
dictGetVal
(
de
);
int
force_delete_expired
=
flags
&
LOOKUP_WRITE
;
/* Forcing deletion of expired keys on a replica makes the replica
if
(
force_delete_expired
)
{
* inconsistent with the master. We forbid it on readonly replicas, but
/* Forcing deletion of expired keys on a
replica make
s the replica
* we have to allow it on writable
replica
s to
make
write commands
* in
consistent
with the master. The reason it's allowed for write
* behave
consistent
ly.
* commands is to make writable replicas behave consistently. It
*
* shall not be used in readonly commands. Modules
are
accepted so
* It's possible that the WRITE flag is set even during
a
re
adonly
* that we don't break old
modules
. */
* command, since the command may trigger events that cause
modules
to
client
*
c
=
server
.
in_script
?
scriptGetClient
()
:
server
.
current_client
;
* perform additional writes. */
serverAssert
(
!
c
||
!
c
->
cmd
||
(
c
->
cmd
->
flags
&
(
CMD_WRITE
|
CMD_MODULE
)))
;
int
is_ro_replica
=
server
.
masterhost
&&
server
.
repl_slave_ro
;
}
int
force_delete_expired
=
flags
&
LOOKUP_WRITE
&&
!
is_ro_replica
;
if
(
expireIfNeeded
(
db
,
key
,
force_delete_expired
))
{
if
(
expireIfNeeded
(
db
,
key
,
force_delete_expired
))
{
/* The key is no longer valid. */
/* The key is no longer valid. */
val
=
NULL
;
val
=
NULL
;
...
@@ -1340,6 +1340,11 @@ int dbSwapDatabases(int id1, int id2) {
...
@@ -1340,6 +1340,11 @@ int dbSwapDatabases(int id1, int id2) {
redisDb
aux
=
server
.
db
[
id1
];
redisDb
aux
=
server
.
db
[
id1
];
redisDb
*
db1
=
&
server
.
db
[
id1
],
*
db2
=
&
server
.
db
[
id2
];
redisDb
*
db1
=
&
server
.
db
[
id1
],
*
db2
=
&
server
.
db
[
id2
];
/* Swapdb should make transaction fail if there is any
* client watching keys */
touchAllWatchedKeysInDb
(
db1
,
db2
);
touchAllWatchedKeysInDb
(
db2
,
db1
);
/* Swap hash tables. Note that we don't swap blocking_keys,
/* Swap hash tables. Note that we don't swap blocking_keys,
* ready_keys and watched_keys, since we want clients to
* ready_keys and watched_keys, since we want clients to
* remain in the same DB they were. */
* remain in the same DB they were. */
...
@@ -1361,14 +1366,9 @@ int dbSwapDatabases(int id1, int id2) {
...
@@ -1361,14 +1366,9 @@ int dbSwapDatabases(int id1, int id2) {
* However normally we only do this check for efficiency reasons
* However normally we only do this check for efficiency reasons
* in dbAdd() when a list is created. So here we need to rescan
* in dbAdd() when a list is created. So here we need to rescan
* the list of clients blocked on lists and signal lists as ready
* the list of clients blocked on lists and signal lists as ready
* if needed.
* if needed. */
*
* Also the swapdb should make transaction fail if there is any
* client watching keys */
scanDatabaseForReadyLists
(
db1
);
scanDatabaseForReadyLists
(
db1
);
touchAllWatchedKeysInDb
(
db1
,
db2
);
scanDatabaseForReadyLists
(
db2
);
scanDatabaseForReadyLists
(
db2
);
touchAllWatchedKeysInDb
(
db2
,
db1
);
return
C_OK
;
return
C_OK
;
}
}
...
@@ -1387,6 +1387,10 @@ void swapMainDbWithTempDb(redisDb *tempDb) {
...
@@ -1387,6 +1387,10 @@ void swapMainDbWithTempDb(redisDb *tempDb) {
redisDb
aux
=
server
.
db
[
i
];
redisDb
aux
=
server
.
db
[
i
];
redisDb
*
activedb
=
&
server
.
db
[
i
],
*
newdb
=
&
tempDb
[
i
];
redisDb
*
activedb
=
&
server
.
db
[
i
],
*
newdb
=
&
tempDb
[
i
];
/* Swapping databases should make transaction fail if there is any
* client watching keys. */
touchAllWatchedKeysInDb
(
activedb
,
newdb
);
/* Swap hash tables. Note that we don't swap blocking_keys,
/* Swap hash tables. Note that we don't swap blocking_keys,
* ready_keys and watched_keys, since clients
* ready_keys and watched_keys, since clients
* remain in the same DB they were. */
* remain in the same DB they were. */
...
@@ -1408,12 +1412,8 @@ void swapMainDbWithTempDb(redisDb *tempDb) {
...
@@ -1408,12 +1412,8 @@ void swapMainDbWithTempDb(redisDb *tempDb) {
* However normally we only do this check for efficiency reasons
* However normally we only do this check for efficiency reasons
* in dbAdd() when a list is created. So here we need to rescan
* in dbAdd() when a list is created. So here we need to rescan
* the list of clients blocked on lists and signal lists as ready
* the list of clients blocked on lists and signal lists as ready
* if needed.
* if needed. */
*
* Also the swapdb should make transaction fail if there is any
* client watching keys. */
scanDatabaseForReadyLists
(
activedb
);
scanDatabaseForReadyLists
(
activedb
);
touchAllWatchedKeysInDb
(
activedb
,
newdb
);
}
}
trackingInvalidateKeysOnFlush
(
1
);
trackingInvalidateKeysOnFlush
(
1
);
...
@@ -1692,7 +1692,7 @@ int64_t getAllKeySpecsFlags(struct redisCommand *cmd, int inv) {
...
@@ -1692,7 +1692,7 @@ int64_t getAllKeySpecsFlags(struct redisCommand *cmd, int inv) {
/* Fetch the keys based of the provided key specs. Returns the number of keys found, or -1 on error.
/* Fetch the keys based of the provided key specs. Returns the number of keys found, or -1 on error.
* There are several flags that can be used to modify how this function finds keys in a command.
* There are several flags that can be used to modify how this function finds keys in a command.
*
*
* GET_KEYSPEC_INCLUDE_
CHANNEL
S: Return
channel
s as if they were keys.
* GET_KEYSPEC_INCLUDE_
NOT_KEY
S: Return
'fake' key
s as if they were keys.
* GET_KEYSPEC_RETURN_PARTIAL: Skips invalid and incomplete keyspecs but returns the keys
* GET_KEYSPEC_RETURN_PARTIAL: Skips invalid and incomplete keyspecs but returns the keys
* found in other valid keyspecs.
* found in other valid keyspecs.
*/
*/
...
@@ -1703,8 +1703,8 @@ int getKeysUsingKeySpecs(struct redisCommand *cmd, robj **argv, int argc, int se
...
@@ -1703,8 +1703,8 @@ int getKeysUsingKeySpecs(struct redisCommand *cmd, robj **argv, int argc, int se
for
(
j
=
0
;
j
<
cmd
->
key_specs_num
;
j
++
)
{
for
(
j
=
0
;
j
<
cmd
->
key_specs_num
;
j
++
)
{
keySpec
*
spec
=
cmd
->
key_specs
+
j
;
keySpec
*
spec
=
cmd
->
key_specs
+
j
;
serverAssert
(
spec
->
begin_search_type
!=
KSPEC_BS_INVALID
);
serverAssert
(
spec
->
begin_search_type
!=
KSPEC_BS_INVALID
);
/* Skip specs that represent
channels instead of
keys */
/* Skip specs that represent
'fake'
keys */
if
((
spec
->
flags
&
CMD_KEY_
CHANNEL
)
&&
!
(
search_flags
&
GET_KEYSPEC_INCLUDE_
CHANNEL
S
))
{
if
((
spec
->
flags
&
CMD_KEY_
NOT_KEY
)
&&
!
(
search_flags
&
GET_KEYSPEC_INCLUDE_
NOT_KEY
S
))
{
continue
;
continue
;
}
}
...
@@ -1821,31 +1821,123 @@ invalid_spec:
...
@@ -1821,31 +1821,123 @@ invalid_spec:
* associated with how Redis will access the key.
* associated with how Redis will access the key.
*
*
* 'cmd' must be point to the corresponding entry into the redisCommand
* 'cmd' must be point to the corresponding entry into the redisCommand
* table, according to the command name in argv[0].
* table, according to the command name in argv[0]. */
*
* This function uses the command's key specs, which contain the key-spec flags,
* (e.g. RO / RW) and only resorts to the command-specific helper function if
* any of the keys-specs are marked as INCOMPLETE. */
int
getKeysFromCommandWithSpecs
(
struct
redisCommand
*
cmd
,
robj
**
argv
,
int
argc
,
int
search_flags
,
getKeysResult
*
result
)
{
int
getKeysFromCommandWithSpecs
(
struct
redisCommand
*
cmd
,
robj
**
argv
,
int
argc
,
int
search_flags
,
getKeysResult
*
result
)
{
if
(
cmd
->
flags
&
CMD_MODULE_GETKEYS
)
{
/* The command has at least one key-spec not marked as NOT_KEY */
int
has_keyspec
=
(
getAllKeySpecsFlags
(
cmd
,
1
)
&
CMD_KEY_NOT_KEY
);
/* The command has at least one key-spec marked as VARIABLE_FLAGS */
int
has_varflags
=
(
getAllKeySpecsFlags
(
cmd
,
0
)
&
CMD_KEY_VARIABLE_FLAGS
);
/* Flags indicating that we have a getkeys callback */
int
has_module_getkeys
=
cmd
->
flags
&
CMD_MODULE_GETKEYS
;
int
has_native_getkeys
=
!
(
cmd
->
flags
&
CMD_MODULE
)
&&
cmd
->
getkeys_proc
;
/* The key-spec that's auto generated by RM_CreateCommand sets VARIABLE_FLAGS since no flags are given.
* If the module provides getkeys callback, we'll prefer it, but if it didn't, we'll use key-spec anyway. */
if
((
cmd
->
flags
&
CMD_MODULE
)
&&
has_varflags
&&
!
has_module_getkeys
)
has_varflags
=
0
;
/* We prefer key-specs if there are any, and their flags are reliable. */
if
(
has_keyspec
&&
!
has_varflags
)
{
int
ret
=
getKeysUsingKeySpecs
(
cmd
,
argv
,
argc
,
search_flags
,
result
);
if
(
ret
>=
0
)
return
ret
;
/* If the specs returned with an error (probably an INVALID or INCOMPLETE spec),
* fallback to the callback method. */
}
/* Resort to getkeys callback methods. */
if
(
has_module_getkeys
)
return
moduleGetCommandKeysViaAPI
(
cmd
,
argv
,
argc
,
result
);
return
moduleGetCommandKeysViaAPI
(
cmd
,
argv
,
argc
,
result
);
}
else
{
if
(
!
(
getAllKeySpecsFlags
(
cmd
,
0
)
&
CMD_KEY_VARIABLE_FLAGS
))
{
/* We use native getkeys as a last resort, since not all these native getkeys provide
int
ret
=
getKeysUsingKeySpecs
(
cmd
,
argv
,
argc
,
search_flags
,
result
);
* flags properly (only the ones that correspond to INVALID, INCOMPLETE or VARIABLE_FLAGS do.*/
if
(
ret
>=
0
)
if
(
has_native_getkeys
)
return
ret
;
return
cmd
->
getkeys_proc
(
cmd
,
argv
,
argc
,
result
);
}
return
0
;
if
(
!
(
cmd
->
flags
&
CMD_MODULE
)
&&
cmd
->
getkeys_proc
)
return
cmd
->
getkeys_proc
(
cmd
,
argv
,
argc
,
result
);
return
0
;
}
}
}
/* This function returns a sanity check if the command may have keys. */
/* This function returns a sanity check if the command may have keys. */
int
doesCommandHaveKeys
(
struct
redisCommand
*
cmd
)
{
int
doesCommandHaveKeys
(
struct
redisCommand
*
cmd
)
{
return
(
!
(
cmd
->
flags
&
CMD_MODULE
)
&&
cmd
->
getkeys_proc
)
||
/* has getkeys_proc (non modules) */
return
(
!
(
cmd
->
flags
&
CMD_MODULE
)
&&
cmd
->
getkeys_proc
)
||
/* has getkeys_proc (non modules) */
(
cmd
->
flags
&
CMD_MODULE_GETKEYS
)
||
/* module with GETKEYS */
(
cmd
->
flags
&
CMD_MODULE_GETKEYS
)
||
/* module with GETKEYS */
(
getAllKeySpecsFlags
(
cmd
,
1
)
&
CMD_KEY_CHANNEL
);
/* has at least one key-spec not marked as CHANNEL */
(
getAllKeySpecsFlags
(
cmd
,
1
)
&
CMD_KEY_NOT_KEY
);
/* has at least one key-spec not marked as NOT_KEY */
}
/* A simplified channel spec table that contains all of the redis commands
* and which channels they have and how they are accessed. */
typedef
struct
ChannelSpecs
{
redisCommandProc
*
proc
;
/* Command procedure to match against */
uint64_t
flags
;
/* CMD_CHANNEL_* flags for this command */
int
start
;
/* The initial position of the first channel */
int
count
;
/* The number of channels, or -1 if all remaining
* arguments are channels. */
}
ChannelSpecs
;
ChannelSpecs
commands_with_channels
[]
=
{
{
subscribeCommand
,
CMD_CHANNEL_SUBSCRIBE
,
1
,
-
1
},
{
ssubscribeCommand
,
CMD_CHANNEL_SUBSCRIBE
,
1
,
-
1
},
{
unsubscribeCommand
,
CMD_CHANNEL_UNSUBSCRIBE
,
1
,
-
1
},
{
sunsubscribeCommand
,
CMD_CHANNEL_UNSUBSCRIBE
,
1
,
-
1
},
{
psubscribeCommand
,
CMD_CHANNEL_PATTERN
|
CMD_CHANNEL_SUBSCRIBE
,
1
,
-
1
},
{
punsubscribeCommand
,
CMD_CHANNEL_PATTERN
|
CMD_CHANNEL_UNSUBSCRIBE
,
1
,
-
1
},
{
publishCommand
,
CMD_CHANNEL_PUBLISH
,
1
,
1
},
{
spublishCommand
,
CMD_CHANNEL_PUBLISH
,
1
,
1
},
{
NULL
,
0
}
/* Terminator. */
};
/* Returns 1 if the command may access any channels matched by the flags
* argument. */
int
doesCommandHaveChannelsWithFlags
(
struct
redisCommand
*
cmd
,
int
flags
)
{
/* If a module declares get channels, we are just going to assume
* has channels. This API is allowed to return false positives. */
if
(
cmd
->
flags
&
CMD_MODULE_GETCHANNELS
)
{
return
1
;
}
for
(
ChannelSpecs
*
spec
=
commands_with_channels
;
spec
->
proc
!=
NULL
;
spec
+=
1
)
{
if
(
cmd
->
proc
==
spec
->
proc
)
{
return
!!
(
spec
->
flags
&
flags
);
}
}
return
0
;
}
/* Return all the arguments that are channels in the command passed via argc / argv.
* This function behaves similar to getKeysFromCommandWithSpecs, but with channels
* instead of keys.
*
* The command returns the positions of all the channel arguments inside the array,
* so the actual return value is a heap allocated array of integers. The
* length of the array is returned by reference into *numkeys.
*
* Along with the position, this command also returns the flags that are
* associated with how Redis will access the channel.
*
* 'cmd' must be point to the corresponding entry into the redisCommand
* table, according to the command name in argv[0]. */
int
getChannelsFromCommand
(
struct
redisCommand
*
cmd
,
robj
**
argv
,
int
argc
,
getKeysResult
*
result
)
{
keyReference
*
keys
;
/* If a module declares get channels, use that. */
if
(
cmd
->
flags
&
CMD_MODULE_GETCHANNELS
)
{
return
moduleGetCommandChannelsViaAPI
(
cmd
,
argv
,
argc
,
result
);
}
/* Otherwise check the channel spec table */
for
(
ChannelSpecs
*
spec
=
commands_with_channels
;
spec
!=
NULL
;
spec
+=
1
)
{
if
(
cmd
->
proc
==
spec
->
proc
)
{
int
start
=
spec
->
start
;
int
stop
=
(
spec
->
count
==
-
1
)
?
argc
:
start
+
spec
->
count
;
if
(
stop
>
argc
)
stop
=
argc
;
int
count
=
0
;
keys
=
getKeysPrepareResult
(
result
,
stop
-
start
);
for
(
int
i
=
start
;
i
<
stop
;
i
++
)
{
keys
[
count
].
pos
=
i
;
keys
[
count
++
].
flags
=
spec
->
flags
;
}
result
->
numkeys
=
count
;
return
count
;
}
}
return
0
;
}
}
/* The base case is to use the keys position as given in the command table
/* The base case is to use the keys position as given in the command table
...
...
src/debug.c
View file @
a83e3663
...
@@ -482,6 +482,10 @@ void debugCommand(client *c) {
...
@@ -482,6 +482,10 @@ void debugCommand(client *c) {
" Show low level client eviction pools info (maxmemory-clients)."
,
" Show low level client eviction pools info (maxmemory-clients)."
,
"PAUSE-CRON <0|1>"
,
"PAUSE-CRON <0|1>"
,
" Stop periodic cron job processing."
,
" Stop periodic cron job processing."
,
"REPLYBUFFER-PEAK-RESET-TIME <NEVER||RESET|time>"
,
" Sets the time (in milliseconds) to wait between client reply buffer peak resets."
,
" In case NEVER is provided the last observed peak will never be reset"
,
" In case RESET is provided the peak reset time will be restored to the default value"
,
NULL
NULL
};
};
addReplyHelp
(
c
,
help
);
addReplyHelp
(
c
,
help
);
...
@@ -825,7 +829,7 @@ NULL
...
@@ -825,7 +829,7 @@ NULL
int
memerr
;
int
memerr
;
unsigned
long
long
sz
=
memtoull
((
const
char
*
)
c
->
argv
[
2
]
->
ptr
,
&
memerr
);
unsigned
long
long
sz
=
memtoull
((
const
char
*
)
c
->
argv
[
2
]
->
ptr
,
&
memerr
);
if
(
memerr
||
!
quicklistisSetPackedThreshold
(
sz
))
{
if
(
memerr
||
!
quicklistisSetPackedThreshold
(
sz
))
{
addReplyError
(
c
,
"argument must be a memory value bigger th
e
n 1 and smaller than 4gb"
);
addReplyError
(
c
,
"argument must be a memory value bigger th
a
n 1 and smaller than 4gb"
);
}
else
{
}
else
{
addReply
(
c
,
shared
.
ok
);
addReply
(
c
,
shared
.
ok
);
}
}
...
@@ -921,12 +925,12 @@ NULL
...
@@ -921,12 +925,12 @@ NULL
addReplyStatus
(
c
,
"Apparently Redis did not crash: test passed"
);
addReplyStatus
(
c
,
"Apparently Redis did not crash: test passed"
);
}
else
if
(
!
strcasecmp
(
c
->
argv
[
1
]
->
ptr
,
"set-disable-deny-scripts"
)
&&
c
->
argc
==
3
)
}
else
if
(
!
strcasecmp
(
c
->
argv
[
1
]
->
ptr
,
"set-disable-deny-scripts"
)
&&
c
->
argc
==
3
)
{
{
server
.
script_disable_deny_script
=
atoi
(
c
->
argv
[
2
]
->
ptr
);
;
server
.
script_disable_deny_script
=
atoi
(
c
->
argv
[
2
]
->
ptr
);
addReply
(
c
,
shared
.
ok
);
addReply
(
c
,
shared
.
ok
);
}
else
if
(
!
strcasecmp
(
c
->
argv
[
1
]
->
ptr
,
"config-rewrite-force-all"
)
&&
c
->
argc
==
2
)
}
else
if
(
!
strcasecmp
(
c
->
argv
[
1
]
->
ptr
,
"config-rewrite-force-all"
)
&&
c
->
argc
==
2
)
{
{
if
(
rewriteConfig
(
server
.
configfile
,
1
)
==
-
1
)
if
(
rewriteConfig
(
server
.
configfile
,
1
)
==
-
1
)
addReplyError
(
c
,
"CONFIG-REWRITE-FORCE-ALL failed
"
);
addReplyError
Format
(
c
,
"CONFIG-REWRITE-FORCE-ALL failed
: %s"
,
strerror
(
errno
)
);
else
else
addReply
(
c
,
shared
.
ok
);
addReply
(
c
,
shared
.
ok
);
}
else
if
(
!
strcasecmp
(
c
->
argv
[
1
]
->
ptr
,
"client-eviction"
)
&&
c
->
argc
==
2
)
{
}
else
if
(
!
strcasecmp
(
c
->
argv
[
1
]
->
ptr
,
"client-eviction"
)
&&
c
->
argc
==
2
)
{
...
@@ -958,6 +962,16 @@ NULL
...
@@ -958,6 +962,16 @@ NULL
{
{
server
.
pause_cron
=
atoi
(
c
->
argv
[
2
]
->
ptr
);
server
.
pause_cron
=
atoi
(
c
->
argv
[
2
]
->
ptr
);
addReply
(
c
,
shared
.
ok
);
addReply
(
c
,
shared
.
ok
);
}
else
if
(
!
strcasecmp
(
c
->
argv
[
1
]
->
ptr
,
"replybuffer-peak-reset-time"
)
&&
c
->
argc
==
3
)
{
if
(
!
strcasecmp
(
c
->
argv
[
2
]
->
ptr
,
"never"
))
{
server
.
reply_buffer_peak_reset_time
=
-
1
;
}
else
if
(
!
strcasecmp
(
c
->
argv
[
2
]
->
ptr
,
"reset"
))
{
server
.
reply_buffer_peak_reset_time
=
REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME
;
}
else
{
if
(
getLongFromObjectOrReply
(
c
,
c
->
argv
[
2
],
&
server
.
reply_buffer_peak_reset_time
,
NULL
)
!=
C_OK
)
return
;
}
addReply
(
c
,
shared
.
ok
);
}
else
{
}
else
{
addReplySubcommandSyntaxError
(
c
);
addReplySubcommandSyntaxError
(
c
);
return
;
return
;
...
@@ -1681,13 +1695,19 @@ void logStackTrace(void *eip, int uplevel) {
...
@@ -1681,13 +1695,19 @@ void logStackTrace(void *eip, int uplevel) {
void
logServerInfo
(
void
)
{
void
logServerInfo
(
void
)
{
sds
infostring
,
clients
;
sds
infostring
,
clients
;
serverLogRaw
(
LL_WARNING
|
LL_RAW
,
"
\n
------ INFO OUTPUT ------
\n
"
);
serverLogRaw
(
LL_WARNING
|
LL_RAW
,
"
\n
------ INFO OUTPUT ------
\n
"
);
infostring
=
genRedisInfoString
(
"all"
);
int
all
=
0
,
everything
=
0
;
robj
*
argv
[
1
];
argv
[
0
]
=
createStringObject
(
"all"
,
strlen
(
"all"
));
dict
*
section_dict
=
genInfoSectionDict
(
argv
,
1
,
NULL
,
&
all
,
&
everything
);
infostring
=
genRedisInfoString
(
section_dict
,
all
,
everything
);
serverLogRaw
(
LL_WARNING
|
LL_RAW
,
infostring
);
serverLogRaw
(
LL_WARNING
|
LL_RAW
,
infostring
);
serverLogRaw
(
LL_WARNING
|
LL_RAW
,
"
\n
------ CLIENT LIST OUTPUT ------
\n
"
);
serverLogRaw
(
LL_WARNING
|
LL_RAW
,
"
\n
------ CLIENT LIST OUTPUT ------
\n
"
);
clients
=
getAllClientsInfoString
(
-
1
);
clients
=
getAllClientsInfoString
(
-
1
);
serverLogRaw
(
LL_WARNING
|
LL_RAW
,
clients
);
serverLogRaw
(
LL_WARNING
|
LL_RAW
,
clients
);
sdsfree
(
infostring
);
sdsfree
(
infostring
);
sdsfree
(
clients
);
sdsfree
(
clients
);
releaseInfoSectionDict
(
section_dict
);
decrRefCount
(
argv
[
0
]);
}
}
/* Log certain config values, which can be used for debuggin */
/* Log certain config values, which can be used for debuggin */
...
@@ -1723,10 +1743,10 @@ void logCurrentClient(void) {
...
@@ -1723,10 +1743,10 @@ void logCurrentClient(void) {
sdsfree
(
client
);
sdsfree
(
client
);
for
(
j
=
0
;
j
<
cc
->
argc
;
j
++
)
{
for
(
j
=
0
;
j
<
cc
->
argc
;
j
++
)
{
robj
*
decoded
;
robj
*
decoded
;
decoded
=
getDecodedObject
(
cc
->
argv
[
j
]);
decoded
=
getDecodedObject
(
cc
->
argv
[
j
]);
serverLog
(
LL_WARNING
|
LL_RAW
,
"argv[%d]: '%s'
\n
"
,
j
,
sds
repr
=
sdscatrepr
(
sdsempty
(),
decoded
->
ptr
,
min
(
sdslen
(
decoded
->
ptr
),
128
));
(
char
*
)
decoded
->
ptr
);
serverLog
(
LL_WARNING
|
LL_RAW
,
"argv[%d]: '%s'
\n
"
,
j
,
(
char
*
)
repr
);
sdsfree
(
repr
);
decrRefCount
(
decoded
);
decrRefCount
(
decoded
);
}
}
/* Check if the first argument, usually a key, is found inside the
/* Check if the first argument, usually a key, is found inside the
...
@@ -1764,7 +1784,10 @@ int memtest_test_linux_anonymous_maps(void) {
...
@@ -1764,7 +1784,10 @@ int memtest_test_linux_anonymous_maps(void) {
if
(
!
fd
)
return
0
;
if
(
!
fd
)
return
0
;
fp
=
fopen
(
"/proc/self/maps"
,
"r"
);
fp
=
fopen
(
"/proc/self/maps"
,
"r"
);
if
(
!
fp
)
return
0
;
if
(
!
fp
)
{
closeDirectLogFiledes
(
fd
);
return
0
;
}
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
while
(
fgets
(
line
,
sizeof
(
line
),
fp
)
!=
NULL
)
{
char
*
start
,
*
end
,
*
p
=
line
;
char
*
start
,
*
end
,
*
p
=
line
;
...
...
src/debugmacro.h
View file @
a83e3663
...
@@ -30,6 +30,9 @@
...
@@ -30,6 +30,9 @@
* POSSIBILITY OF SUCH DAMAGE.
* POSSIBILITY OF SUCH DAMAGE.
*/
*/
#ifndef _REDIS_DEBUGMACRO_H_
#define _REDIS_DEBUGMACRO_H_
#include <stdio.h>
#include <stdio.h>
#define D(...) \
#define D(...) \
do { \
do { \
...
@@ -39,3 +42,5 @@
...
@@ -39,3 +42,5 @@
fprintf(fp,"\n"); \
fprintf(fp,"\n"); \
fclose(fp); \
fclose(fp); \
} while (0)
} while (0)
#endif
/* _REDIS_DEBUGMACRO_H_ */
src/defrag.c
View file @
a83e3663
...
@@ -128,6 +128,27 @@ robj *activeDefragStringOb(robj* ob, long *defragged) {
...
@@ -128,6 +128,27 @@ robj *activeDefragStringOb(robj* ob, long *defragged) {
return
ret
;
return
ret
;
}
}
/* Defrag helper for lua scripts
*
* returns NULL in case the allocation wasn't moved.
* when it returns a non-null value, the old pointer was already released
* and should NOT be accessed. */
luaScript
*
activeDefragLuaScript
(
luaScript
*
script
,
long
*
defragged
)
{
luaScript
*
ret
=
NULL
;
/* try to defrag script struct */
if
((
ret
=
activeDefragAlloc
(
script
)))
{
script
=
ret
;
(
*
defragged
)
++
;
}
/* try to defrag actual script object */
robj
*
ob
=
activeDefragStringOb
(
script
->
body
,
defragged
);
if
(
ob
)
script
->
body
=
ob
;
return
ret
;
}
/* Defrag helper for dictEntries to be used during dict iteration (called on
/* Defrag helper for dictEntries to be used during dict iteration (called on
* each step). Returns a stat of how many pointers were moved. */
* each step). Returns a stat of how many pointers were moved. */
long
dictIterDefragEntry
(
dictIterator
*
iter
)
{
long
dictIterDefragEntry
(
dictIterator
*
iter
)
{
...
@@ -256,6 +277,7 @@ long activeDefragZsetEntry(zset *zs, dictEntry *de) {
...
@@ -256,6 +277,7 @@ long activeDefragZsetEntry(zset *zs, dictEntry *de) {
#define DEFRAG_SDS_DICT_VAL_IS_SDS 1
#define DEFRAG_SDS_DICT_VAL_IS_SDS 1
#define DEFRAG_SDS_DICT_VAL_IS_STROB 2
#define DEFRAG_SDS_DICT_VAL_IS_STROB 2
#define DEFRAG_SDS_DICT_VAL_VOID_PTR 3
#define DEFRAG_SDS_DICT_VAL_VOID_PTR 3
#define DEFRAG_SDS_DICT_VAL_LUA_SCRIPT 4
/* Defrag a dict with sds key and optional value (either ptr, sds or robj string) */
/* Defrag a dict with sds key and optional value (either ptr, sds or robj string) */
long
activeDefragSdsDict
(
dict
*
d
,
int
val_type
)
{
long
activeDefragSdsDict
(
dict
*
d
,
int
val_type
)
{
...
@@ -280,6 +302,10 @@ long activeDefragSdsDict(dict* d, int val_type) {
...
@@ -280,6 +302,10 @@ long activeDefragSdsDict(dict* d, int val_type) {
void
*
newptr
,
*
ptr
=
dictGetVal
(
de
);
void
*
newptr
,
*
ptr
=
dictGetVal
(
de
);
if
((
newptr
=
activeDefragAlloc
(
ptr
)))
if
((
newptr
=
activeDefragAlloc
(
ptr
)))
de
->
v
.
val
=
newptr
,
defragged
++
;
de
->
v
.
val
=
newptr
,
defragged
++
;
}
else
if
(
val_type
==
DEFRAG_SDS_DICT_VAL_LUA_SCRIPT
)
{
void
*
newptr
,
*
ptr
=
dictGetVal
(
de
);
if
((
newptr
=
activeDefragLuaScript
(
ptr
,
&
defragged
)))
de
->
v
.
val
=
newptr
;
}
}
defragged
+=
dictIterDefragEntry
(
di
);
defragged
+=
dictIterDefragEntry
(
di
);
}
}
...
@@ -939,7 +965,7 @@ long defragOtherGlobals() {
...
@@ -939,7 +965,7 @@ long defragOtherGlobals() {
/* there are many more pointers to defrag (e.g. client argv, output / aof buffers, etc.
/* there are many more pointers to defrag (e.g. client argv, output / aof buffers, etc.
* but we assume most of these are short lived, we only need to defrag allocations
* but we assume most of these are short lived, we only need to defrag allocations
* that remain static for a long time */
* that remain static for a long time */
defragged
+=
activeDefragSdsDict
(
evalScriptsDict
(),
DEFRAG_SDS_DICT_VAL_
IS_STROB
);
defragged
+=
activeDefragSdsDict
(
evalScriptsDict
(),
DEFRAG_SDS_DICT_VAL_
LUA_SCRIPT
);
defragged
+=
moduleDefragGlobals
();
defragged
+=
moduleDefragGlobals
();
return
defragged
;
return
defragged
;
}
}
...
@@ -1130,7 +1156,7 @@ void activeDefragCycle(void) {
...
@@ -1130,7 +1156,7 @@ void activeDefragCycle(void) {
/* Move on to next database, and stop if we reached the last one. */
/* Move on to next database, and stop if we reached the last one. */
if
(
++
current_db
>=
server
.
dbnum
)
{
if
(
++
current_db
>=
server
.
dbnum
)
{
/* defrag other items not part of the db / keys */
/* defrag other items not part of the db / keys */
defragOtherGlobals
();
server
.
stat_active_defrag_hits
+=
defragOtherGlobals
();
long
long
now
=
ustime
();
long
long
now
=
ustime
();
size_t
frag_bytes
;
size_t
frag_bytes
;
...
...
src/eval.c
View file @
a83e3663
...
@@ -47,11 +47,6 @@ void ldbEnable(client *c);
...
@@ -47,11 +47,6 @@ void ldbEnable(client *c);
void
evalGenericCommandWithDebugging
(
client
*
c
,
int
evalsha
);
void
evalGenericCommandWithDebugging
(
client
*
c
,
int
evalsha
);
sds
ldbCatStackValue
(
sds
s
,
lua_State
*
lua
,
int
idx
);
sds
ldbCatStackValue
(
sds
s
,
lua_State
*
lua
,
int
idx
);
typedef
struct
luaScript
{
uint64_t
flags
;
robj
*
body
;
}
luaScript
;
static
void
dictLuaScriptDestructor
(
dict
*
d
,
void
*
val
)
{
static
void
dictLuaScriptDestructor
(
dict
*
d
,
void
*
val
)
{
UNUSED
(
d
);
UNUSED
(
d
);
if
(
val
==
NULL
)
return
;
/* Lazy freeing will set value to NULL. */
if
(
val
==
NULL
)
return
;
/* Lazy freeing will set value to NULL. */
...
@@ -63,7 +58,7 @@ static uint64_t dictStrCaseHash(const void *key) {
...
@@ -63,7 +58,7 @@ static uint64_t dictStrCaseHash(const void *key) {
return
dictGenCaseHashFunction
((
unsigned
char
*
)
key
,
strlen
((
char
*
)
key
));
return
dictGenCaseHashFunction
((
unsigned
char
*
)
key
,
strlen
((
char
*
)
key
));
}
}
/* server.lua_scripts sha (as sds string) -> scripts (as
robj
) cache. */
/* server.lua_scripts sha (as sds string) -> scripts (as
luaScript
) cache. */
dictType
shaScriptObjectDictType
=
{
dictType
shaScriptObjectDictType
=
{
dictStrCaseHash
,
/* hash function */
dictStrCaseHash
,
/* hash function */
NULL
,
/* key dup */
NULL
,
/* key dup */
...
@@ -246,11 +241,14 @@ void scriptingInit(int setup) {
...
@@ -246,11 +241,14 @@ void scriptingInit(int setup) {
" if i and i.what == 'C' then
\n
"
" if i and i.what == 'C' then
\n
"
" i = dbg.getinfo(3,'nSl')
\n
"
" i = dbg.getinfo(3,'nSl')
\n
"
" end
\n
"
" end
\n
"
" if type(err) ~= 'table' then
\n
"
" err = {err='ERR' .. tostring(err)}"
" end"
" if i then
\n
"
" if i then
\n
"
"
return i.source .. ':' .. i.currentline .. ': ' .. err
\n
"
"
err['source'] = i.source
\n
"
"
els
e
\n
"
"
err['line'] = i.currentlin
e
\n
"
"
return err
\n
"
"
end
"
"
end
\n
"
"
return err
\n
"
"end
\n
"
;
"end
\n
"
;
luaL_loadbuffer
(
lua
,
errh_func
,
strlen
(
errh_func
),
"@err_handler_def"
);
luaL_loadbuffer
(
lua
,
errh_func
,
strlen
(
errh_func
),
"@err_handler_def"
);
lua_pcall
(
lua
,
0
,
0
,
0
);
lua_pcall
(
lua
,
0
,
0
,
0
);
...
@@ -392,7 +390,7 @@ sds luaCreateFunction(client *c, robj *body) {
...
@@ -392,7 +390,7 @@ sds luaCreateFunction(client *c, robj *body) {
if
(
luaL_loadbuffer
(
lctx
.
lua
,
funcdef
,
sdslen
(
funcdef
),
"@user_script"
))
{
if
(
luaL_loadbuffer
(
lctx
.
lua
,
funcdef
,
sdslen
(
funcdef
),
"@user_script"
))
{
if
(
c
!=
NULL
)
{
if
(
c
!=
NULL
)
{
addReplyErrorFormat
(
c
,
addReplyErrorFormat
(
c
,
"Error compiling script (new function): %s
\n
"
,
"Error compiling script (new function): %s"
,
lua_tostring
(
lctx
.
lua
,
-
1
));
lua_tostring
(
lctx
.
lua
,
-
1
));
}
}
lua_pop
(
lctx
.
lua
,
1
);
lua_pop
(
lctx
.
lua
,
1
);
...
@@ -403,7 +401,7 @@ sds luaCreateFunction(client *c, robj *body) {
...
@@ -403,7 +401,7 @@ sds luaCreateFunction(client *c, robj *body) {
if
(
lua_pcall
(
lctx
.
lua
,
0
,
0
,
0
))
{
if
(
lua_pcall
(
lctx
.
lua
,
0
,
0
,
0
))
{
if
(
c
!=
NULL
)
{
if
(
c
!=
NULL
)
{
addReplyErrorFormat
(
c
,
"Error running script (new function): %s
\n
"
,
addReplyErrorFormat
(
c
,
"Error running script (new function): %s"
,
lua_tostring
(
lctx
.
lua
,
-
1
));
lua_tostring
(
lctx
.
lua
,
-
1
));
}
}
lua_pop
(
lctx
.
lua
,
1
);
lua_pop
(
lctx
.
lua
,
1
);
...
@@ -1479,8 +1477,8 @@ int ldbRepl(lua_State *lua) {
...
@@ -1479,8 +1477,8 @@ int ldbRepl(lua_State *lua) {
while
((
argv
=
ldbReplParseCommand
(
&
argc
,
&
err
))
==
NULL
)
{
while
((
argv
=
ldbReplParseCommand
(
&
argc
,
&
err
))
==
NULL
)
{
char
buf
[
1024
];
char
buf
[
1024
];
if
(
err
)
{
if
(
err
)
{
lua
_p
ush
string
(
lua
,
err
);
lua
P
ush
Error
(
lua
,
err
);
lua
_e
rror
(
lua
);
lua
E
rror
(
lua
);
}
}
int
nread
=
connRead
(
ldb
.
conn
,
buf
,
sizeof
(
buf
));
int
nread
=
connRead
(
ldb
.
conn
,
buf
,
sizeof
(
buf
));
if
(
nread
<=
0
)
{
if
(
nread
<=
0
)
{
...
@@ -1497,8 +1495,8 @@ int ldbRepl(lua_State *lua) {
...
@@ -1497,8 +1495,8 @@ int ldbRepl(lua_State *lua) {
if
(
sdslen
(
ldb
.
cbuf
)
>
1
<<
20
)
{
if
(
sdslen
(
ldb
.
cbuf
)
>
1
<<
20
)
{
sdsfree
(
ldb
.
cbuf
);
sdsfree
(
ldb
.
cbuf
);
ldb
.
cbuf
=
sdsempty
();
ldb
.
cbuf
=
sdsempty
();
lua
_p
ush
string
(
lua
,
"max client buffer reached"
);
lua
P
ush
Error
(
lua
,
"max client buffer reached"
);
lua
_e
rror
(
lua
);
lua
E
rror
(
lua
);
}
}
}
}
...
@@ -1558,8 +1556,8 @@ ldbLog(sdsnew(" next line of code."));
...
@@ -1558,8 +1556,8 @@ ldbLog(sdsnew(" next line of code."));
ldbEval
(
lua
,
argv
,
argc
);
ldbEval
(
lua
,
argv
,
argc
);
ldbSendLogs
();
ldbSendLogs
();
}
else
if
(
!
strcasecmp
(
argv
[
0
],
"a"
)
||
!
strcasecmp
(
argv
[
0
],
"abort"
))
{
}
else
if
(
!
strcasecmp
(
argv
[
0
],
"a"
)
||
!
strcasecmp
(
argv
[
0
],
"abort"
))
{
lua
_p
ush
string
(
lua
,
"script aborted for user request"
);
lua
P
ush
Error
(
lua
,
"script aborted for user request"
);
lua
_e
rror
(
lua
);
lua
E
rror
(
lua
);
}
else
if
(
argc
>
1
&&
}
else
if
(
argc
>
1
&&
(
!
strcasecmp
(
argv
[
0
],
"r"
)
||
!
strcasecmp
(
argv
[
0
],
"redis"
)))
{
(
!
strcasecmp
(
argv
[
0
],
"r"
)
||
!
strcasecmp
(
argv
[
0
],
"redis"
)))
{
ldbRedis
(
lua
,
argv
,
argc
);
ldbRedis
(
lua
,
argv
,
argc
);
...
@@ -1640,8 +1638,8 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
...
@@ -1640,8 +1638,8 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
/* If the client closed the connection and we have a timeout
/* If the client closed the connection and we have a timeout
* connection, let's kill the script otherwise the process
* connection, let's kill the script otherwise the process
* will remain blocked indefinitely. */
* will remain blocked indefinitely. */
lua
_p
ush
string
(
lua
,
"timeout during Lua debugging with client closing connection"
);
lua
P
ush
Error
(
lua
,
"timeout during Lua debugging with client closing connection"
);
lua
_e
rror
(
lua
);
lua
E
rror
(
lua
);
}
}
rctx
->
start_time
=
getMonotonicUs
();
rctx
->
start_time
=
getMonotonicUs
();
rctx
->
snapshot_time
=
mstime
();
rctx
->
snapshot_time
=
mstime
();
...
...
src/function_lua.c
View file @
a83e3663
...
@@ -86,8 +86,8 @@ static void luaEngineLoadHook(lua_State *lua, lua_Debug *ar) {
...
@@ -86,8 +86,8 @@ static void luaEngineLoadHook(lua_State *lua, lua_Debug *ar) {
if
(
duration
>
LOAD_TIMEOUT_MS
)
{
if
(
duration
>
LOAD_TIMEOUT_MS
)
{
lua_sethook
(
lua
,
luaEngineLoadHook
,
LUA_MASKLINE
,
0
);
lua_sethook
(
lua
,
luaEngineLoadHook
,
LUA_MASKLINE
,
0
);
lua
_p
ush
string
(
lua
,
"FUNCTION LOAD timeout"
);
lua
P
ush
Error
(
lua
,
"FUNCTION LOAD timeout"
);
lua
_e
rror
(
lua
);
lua
E
rror
(
lua
);
}
}
}
}
...
@@ -151,10 +151,13 @@ static int luaEngineCreate(void *engine_ctx, functionLibInfo *li, sds blob, sds
...
@@ -151,10 +151,13 @@ static int luaEngineCreate(void *engine_ctx, functionLibInfo *li, sds blob, sds
lua_sethook
(
lua
,
luaEngineLoadHook
,
LUA_MASKCOUNT
,
100000
);
lua_sethook
(
lua
,
luaEngineLoadHook
,
LUA_MASKCOUNT
,
100000
);
/* Run the compiled code to allow it to register functions */
/* Run the compiled code to allow it to register functions */
if
(
lua_pcall
(
lua
,
0
,
0
,
0
))
{
if
(
lua_pcall
(
lua
,
0
,
0
,
0
))
{
*
err
=
sdscatprintf
(
sdsempty
(),
"Error registering functions: %s"
,
lua_tostring
(
lua
,
-
1
));
errorInfo
err_info
=
{
0
};
luaExtractErrorInformation
(
lua
,
&
err_info
);
*
err
=
sdscatprintf
(
sdsempty
(),
"Error registering functions: %s"
,
err_info
.
msg
);
lua_pop
(
lua
,
2
);
/* pops the error and globals table */
lua_pop
(
lua
,
2
);
/* pops the error and globals table */
lua_sethook
(
lua
,
NULL
,
0
,
0
);
/* Disable hook */
lua_sethook
(
lua
,
NULL
,
0
,
0
);
/* Disable hook */
luaSaveOnRegistry
(
lua
,
REGISTRY_LOAD_CTX_NAME
,
NULL
);
luaSaveOnRegistry
(
lua
,
REGISTRY_LOAD_CTX_NAME
,
NULL
);
luaErrorInformationDiscard
(
&
err_info
);
return
C_ERR
;
return
C_ERR
;
}
}
lua_sethook
(
lua
,
NULL
,
0
,
0
);
/* Disable hook */
lua_sethook
(
lua
,
NULL
,
0
,
0
);
/* Disable hook */
...
@@ -429,11 +432,11 @@ static int luaRegisterFunction(lua_State *lua) {
...
@@ -429,11 +432,11 @@ static int luaRegisterFunction(lua_State *lua) {
loadCtx
*
load_ctx
=
luaGetFromRegistry
(
lua
,
REGISTRY_LOAD_CTX_NAME
);
loadCtx
*
load_ctx
=
luaGetFromRegistry
(
lua
,
REGISTRY_LOAD_CTX_NAME
);
if
(
!
load_ctx
)
{
if
(
!
load_ctx
)
{
luaPushError
(
lua
,
"redis.register_function can only be called on FUNCTION LOAD command"
);
luaPushError
(
lua
,
"redis.register_function can only be called on FUNCTION LOAD command"
);
return
lua
Raise
Error
(
lua
);
return
luaError
(
lua
);
}
}
if
(
luaRegisterFunctionReadArgs
(
lua
,
&
register_f_args
)
!=
C_OK
)
{
if
(
luaRegisterFunctionReadArgs
(
lua
,
&
register_f_args
)
!=
C_OK
)
{
return
lua
Raise
Error
(
lua
);
return
luaError
(
lua
);
}
}
sds
err
=
NULL
;
sds
err
=
NULL
;
...
@@ -441,7 +444,7 @@ static int luaRegisterFunction(lua_State *lua) {
...
@@ -441,7 +444,7 @@ static int luaRegisterFunction(lua_State *lua) {
luaRegisterFunctionArgsDispose
(
lua
,
&
register_f_args
);
luaRegisterFunctionArgsDispose
(
lua
,
&
register_f_args
);
luaPushError
(
lua
,
err
);
luaPushError
(
lua
,
err
);
sdsfree
(
err
);
sdsfree
(
err
);
return
lua
Raise
Error
(
lua
);
return
luaError
(
lua
);
}
}
return
0
;
return
0
;
...
@@ -475,11 +478,14 @@ int luaEngineInitEngine() {
...
@@ -475,11 +478,14 @@ int luaEngineInitEngine() {
" if i and i.what == 'C' then
\n
"
" if i and i.what == 'C' then
\n
"
" i = dbg.getinfo(3,'nSl')
\n
"
" i = dbg.getinfo(3,'nSl')
\n
"
" end
\n
"
" end
\n
"
" if type(err) ~= 'table' then
\n
"
" err = {err='ERR' .. tostring(err)}"
" end"
" if i then
\n
"
" if i then
\n
"
"
return i.source .. ':' .. i.currentline .. ': ' .. err
\n
"
"
err['source'] = i.source
\n
"
"
els
e
\n
"
"
err['line'] = i.currentlin
e
\n
"
"
return err
\n
"
"
end
"
"
end
\n
"
"
return err
\n
"
"end
\n
"
"end
\n
"
"return error_handler"
;
"return error_handler"
;
luaL_loadbuffer
(
lua_engine_ctx
->
lua
,
errh_func
,
strlen
(
errh_func
),
"@err_handler_def"
);
luaL_loadbuffer
(
lua_engine_ctx
->
lua
,
errh_func
,
strlen
(
errh_func
),
"@err_handler_def"
);
...
...
src/functions.c
View file @
a83e3663
...
@@ -808,7 +808,7 @@ void functionFlushCommand(client *c) {
...
@@ -808,7 +808,7 @@ void functionFlushCommand(client *c) {
void
functionHelpCommand
(
client
*
c
)
{
void
functionHelpCommand
(
client
*
c
)
{
const
char
*
help
[]
=
{
const
char
*
help
[]
=
{
"LOAD <ENGINE NAME> <LIBRARY NAME> [REPLACE] [DESC <LIBRARY DESCRIPTION>] <LIBRARY CODE>"
,
"LOAD <ENGINE NAME> <LIBRARY NAME> [REPLACE] [DESC
RIPTION
<LIBRARY DESCRIPTION>] <LIBRARY CODE>"
,
" Create a new library with the given library name and code."
,
" Create a new library with the given library name and code."
,
"DELETE <LIBRARY NAME>"
,
"DELETE <LIBRARY NAME>"
,
" Delete the given library."
,
" Delete the given library."
,
...
...
src/geohash.h
View file @
a83e3663
...
@@ -34,7 +34,6 @@
...
@@ -34,7 +34,6 @@
#include <stddef.h>
#include <stddef.h>
#include <stdint.h>
#include <stdint.h>
#include <stdint.h>
#if defined(__cplusplus)
#if defined(__cplusplus)
extern
"C"
{
extern
"C"
{
...
...
src/geohash_helper.c
View file @
a83e3663
...
@@ -91,8 +91,8 @@ uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
...
@@ -91,8 +91,8 @@ uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
* \-----------------/ -------- \-----------------/
* \-----------------/ -------- \-----------------/
* \ / / \ \ /
* \ / / \ \ /
* \ (long,lat) / / (long,lat) \ \ (long,lat) /
* \ (long,lat) / / (long,lat) \ \ (long,lat) /
* \ / / \
/ \
* \ / / \ /
\
* --------- /----------------\
/--------------\
* --------- /----------------\ /
-
--------------\
* Northern Hemisphere Southern Hemisphere Around the equator
* Northern Hemisphere Southern Hemisphere Around the equator
*/
*/
int
geohashBoundingBox
(
GeoShape
*
shape
,
double
*
bounds
)
{
int
geohashBoundingBox
(
GeoShape
*
shape
,
double
*
bounds
)
{
...
@@ -164,14 +164,14 @@ GeoHashRadius geohashCalculateAreasByShapeWGS84(GeoShape *shape) {
...
@@ -164,14 +164,14 @@ GeoHashRadius geohashCalculateAreasByShapeWGS84(GeoShape *shape) {
geohashDecode
(
long_range
,
lat_range
,
neighbors
.
east
,
&
east
);
geohashDecode
(
long_range
,
lat_range
,
neighbors
.
east
,
&
east
);
geohashDecode
(
long_range
,
lat_range
,
neighbors
.
west
,
&
west
);
geohashDecode
(
long_range
,
lat_range
,
neighbors
.
west
,
&
west
);
if
(
geohashGetDistance
(
longitude
,
latitude
,
longitude
,
north
.
latitude
.
max
)
if
(
north
.
latitude
.
max
<
max_lat
)
<
radius_meters
)
decrease_step
=
1
;
decrease_step
=
1
;
if
(
geohashGetDistance
(
longitude
,
latitude
,
longitude
,
south
.
latitude
.
min
)
if
(
south
.
latitude
.
min
>
min_lat
)
<
radius_meters
)
decrease_step
=
1
;
decrease_step
=
1
;
if
(
geohashGetDistance
(
longitude
,
latitude
,
east
.
longitude
.
max
,
latitude
)
if
(
east
.
longitude
.
max
<
max_lon
)
<
radius_meters
)
decrease_step
=
1
;
decrease_step
=
1
;
if
(
geohashGetDistance
(
longitude
,
latitude
,
west
.
longitude
.
min
,
latitude
)
if
(
west
.
longitude
.
min
>
min_lon
)
<
radius_meters
)
decrease_step
=
1
;
decrease_step
=
1
;
}
}
if
(
steps
>
1
&&
decrease_step
)
{
if
(
steps
>
1
&&
decrease_step
)
{
...
...
src/help.h
View file @
a83e3663
/* Automatically generated by
./
utils/generate-command-help.rb, do not edit. */
/* Automatically generated by utils/generate-command-help.rb, do not edit. */
#ifndef __REDIS_HELP_H
#ifndef __REDIS_HELP_H
#define __REDIS_HELP_H
#define __REDIS_HELP_H
...
@@ -429,6 +429,11 @@ struct commandHelp {
...
@@ -429,6 +429,11 @@ struct commandHelp {
"Extract keys given a full Redis command"
,
"Extract keys given a full Redis command"
,
9
,
9
,
"2.8.13"
},
"2.8.13"
},
{
"COMMAND GETKEYSANDFLAGS"
,
""
,
"Extract keys given a full Redis command"
,
9
,
"7.0.0"
},
{
"COMMAND HELP"
,
{
"COMMAND HELP"
,
""
,
""
,
"Show helpful text about the different subcommands"
,
"Show helpful text about the different subcommands"
,
...
@@ -571,12 +576,12 @@ struct commandHelp {
...
@@ -571,12 +576,12 @@ struct commandHelp {
"6.2.0"
},
"6.2.0"
},
{
"FCALL"
,
{
"FCALL"
,
"function numkeys key [key ...] arg [arg ...]"
,
"function numkeys key [key ...] arg [arg ...]"
,
"
PATCH__TBD__38__
"
,
"
Invoke a function
"
,
10
,
10
,
"7.0.0"
},
"7.0.0"
},
{
"FCALL_RO"
,
{
"FCALL_RO"
,
"function numkeys key [key ...] arg [arg ...]"
,
"function numkeys key [key ...] arg [arg ...]"
,
"
PATCH__TBD__7__
"
,
"
Invoke a read-only function
"
,
10
,
10
,
"7.0.0"
},
"7.0.0"
},
{
"FLUSHALL"
,
{
"FLUSHALL"
,
...
@@ -595,7 +600,7 @@ struct commandHelp {
...
@@ -595,7 +600,7 @@ struct commandHelp {
10
,
10
,
"7.0.0"
},
"7.0.0"
},
{
"FUNCTION DELETE"
,
{
"FUNCTION DELETE"
,
"
function
-name"
,
"
library
-name"
,
"Delete a function by name"
,
"Delete a function by name"
,
10
,
10
,
"7.0.0"
},
"7.0.0"
},
...
@@ -625,7 +630,7 @@ struct commandHelp {
...
@@ -625,7 +630,7 @@ struct commandHelp {
10
,
10
,
"7.0.0"
},
"7.0.0"
},
{
"FUNCTION LOAD"
,
{
"FUNCTION LOAD"
,
"engine-name library-name [REPLACE] [DESC library-description] function-code"
,
"engine-name library-name [REPLACE] [DESC
RIPTION
library-description] function-code"
,
"Create a function with the given arguments (name, code, description)"
,
"Create a function with the given arguments (name, code, description)"
,
10
,
10
,
"7.0.0"
},
"7.0.0"
},
...
@@ -820,7 +825,7 @@ struct commandHelp {
...
@@ -820,7 +825,7 @@ struct commandHelp {
1
,
1
,
"2.6.0"
},
"2.6.0"
},
{
"INFO"
,
{
"INFO"
,
"[section]"
,
"[section
[section ...]
]"
,
"Get information and statistics about the server"
,
"Get information and statistics about the server"
,
9
,
9
,
"1.0.0"
},
"1.0.0"
},
...
@@ -1180,7 +1185,7 @@ struct commandHelp {
...
@@ -1180,7 +1185,7 @@ struct commandHelp {
6
,
6
,
"7.0.0"
},
"7.0.0"
},
{
"PUBSUB SHARDNUMSUB"
,
{
"PUBSUB SHARDNUMSUB"
,
""
,
"
[channel [channel ...]]
"
,
"Get the count of subscribers for shard channels"
,
"Get the count of subscribers for shard channels"
,
6
,
6
,
"7.0.0"
},
"7.0.0"
},
...
@@ -1391,7 +1396,7 @@ struct commandHelp {
...
@@ -1391,7 +1396,7 @@ struct commandHelp {
"1.0.0"
},
"1.0.0"
},
{
"SLAVEOF"
,
{
"SLAVEOF"
,
"host port"
,
"host port"
,
"Make the server a replica of another instance, or promote it as master.
Deprecated starting with Redis 5. Use REPLICAOF instead.
"
,
"Make the server a replica of another instance, or promote it as master."
,
9
,
9
,
"1.0.0"
},
"1.0.0"
},
{
"SLOWLOG"
,
{
"SLOWLOG"
,
...
@@ -1590,7 +1595,7 @@ struct commandHelp {
...
@@ -1590,7 +1595,7 @@ struct commandHelp {
14
,
14
,
"5.0.0"
},
"5.0.0"
},
{
"XGROUP CREATE"
,
{
"XGROUP CREATE"
,
"key groupname id|$ [MKSTREAM]"
,
"key groupname id|$ [MKSTREAM]
[ENTRIESREAD entries_read]
"
,
"Create a consumer group."
,
"Create a consumer group."
,
14
,
14
,
"5.0.0"
},
"5.0.0"
},
...
@@ -1615,7 +1620,7 @@ struct commandHelp {
...
@@ -1615,7 +1620,7 @@ struct commandHelp {
14
,
14
,
"5.0.0"
},
"5.0.0"
},
{
"XGROUP SETID"
,
{
"XGROUP SETID"
,
"key groupname id|$"
,
"key groupname id|$
[ENTRIESREAD entries_read]
"
,
"Set a consumer group to an arbitrary last delivered ID value."
,
"Set a consumer group to an arbitrary last delivered ID value."
,
14
,
14
,
"5.0.0"
},
"5.0.0"
},
...
@@ -1675,7 +1680,7 @@ struct commandHelp {
...
@@ -1675,7 +1680,7 @@ struct commandHelp {
14
,
14
,
"5.0.0"
},
"5.0.0"
},
{
"XSETID"
,
{
"XSETID"
,
"key last-id"
,
"key last-id
[ENTRIESADDED entries_added] [MAXDELETEDID max_deleted_entry_id]
"
,
"An internal command for replicating stream values"
,
"An internal command for replicating stream values"
,
14
,
14
,
"5.0.0"
},
"5.0.0"
},
...
...
src/module.c
View file @
a83e3663
...
@@ -70,7 +70,7 @@
...
@@ -70,7 +70,7 @@
typedef
struct
RedisModuleInfoCtx
{
typedef
struct
RedisModuleInfoCtx
{
struct
RedisModule
*
module
;
struct
RedisModule
*
module
;
const
char
*
requested_section
;
dict
*
requested_section
s
;
sds
info
;
/* info string we collected so far */
sds
info
;
/* info string we collected so far */
int
sections
;
/* number of sections we collected so far */
int
sections
;
/* number of sections we collected so far */
int
in_section
;
/* indication if we're in an active section or not */
int
in_section
;
/* indication if we're in an active section or not */
...
@@ -154,7 +154,8 @@ struct RedisModuleCtx {
...
@@ -154,7 +154,8 @@ struct RedisModuleCtx {
gets called for clients blocked
gets called for clients blocked
on keys. */
on keys. */
/* Used if there is the REDISMODULE_CTX_KEYS_POS_REQUEST flag set. */
/* Used if there is the REDISMODULE_CTX_KEYS_POS_REQUEST or
* REDISMODULE_CTX_CHANNEL_POS_REQUEST flag set. */
getKeysResult
*
keys_result
;
getKeysResult
*
keys_result
;
struct
RedisModulePoolAllocBlock
*
pa_head
;
struct
RedisModulePoolAllocBlock
*
pa_head
;
...
@@ -173,6 +174,7 @@ typedef struct RedisModuleCtx RedisModuleCtx;
...
@@ -173,6 +174,7 @@ typedef struct RedisModuleCtx RedisModuleCtx;
when the context is destroyed */
when the context is destroyed */
#define REDISMODULE_CTX_NEW_CLIENT (1<<7)
/* Free client object when the
#define REDISMODULE_CTX_NEW_CLIENT (1<<7)
/* Free client object when the
context is destroyed */
context is destroyed */
#define REDISMODULE_CTX_CHANNELS_POS_REQUEST (1<<8)
/* This represents a Redis key opened with RM_OpenKey(). */
/* This represents a Redis key opened with RM_OpenKey(). */
struct
RedisModuleKey
{
struct
RedisModuleKey
{
...
@@ -390,6 +392,7 @@ typedef struct RedisModuleKeyOptCtx {
...
@@ -390,6 +392,7 @@ typedef struct RedisModuleKeyOptCtx {
In most cases, only 'from_dbid' is valid, but in callbacks such
In most cases, only 'from_dbid' is valid, but in callbacks such
as `copy2`, 'from_dbid' and 'to_dbid' are both valid. */
as `copy2`, 'from_dbid' and 'to_dbid' are both valid. */
}
RedisModuleKeyOptCtx
;
}
RedisModuleKeyOptCtx
;
/* --------------------------------------------------------------------------
/* --------------------------------------------------------------------------
* Prototypes
* Prototypes
* -------------------------------------------------------------------------- */
* -------------------------------------------------------------------------- */
...
@@ -404,6 +407,16 @@ static void moduleInitKeyTypeSpecific(RedisModuleKey *key);
...
@@ -404,6 +407,16 @@ static void moduleInitKeyTypeSpecific(RedisModuleKey *key);
void
RM_FreeDict
(
RedisModuleCtx
*
ctx
,
RedisModuleDict
*
d
);
void
RM_FreeDict
(
RedisModuleCtx
*
ctx
,
RedisModuleDict
*
d
);
void
RM_FreeServerInfo
(
RedisModuleCtx
*
ctx
,
RedisModuleServerInfoData
*
data
);
void
RM_FreeServerInfo
(
RedisModuleCtx
*
ctx
,
RedisModuleServerInfoData
*
data
);
/* Helpers for RM_SetCommandInfo. */
static
int
moduleValidateCommandInfo
(
const
RedisModuleCommandInfo
*
info
);
static
int64_t
moduleConvertKeySpecsFlags
(
int64_t
flags
,
int
from_api
);
static
int
moduleValidateCommandArgs
(
RedisModuleCommandArg
*
args
,
const
RedisModuleCommandInfoVersion
*
version
);
static
struct
redisCommandArg
*
moduleCopyCommandArgs
(
RedisModuleCommandArg
*
args
,
const
RedisModuleCommandInfoVersion
*
version
);
static
redisCommandArgType
moduleConvertArgType
(
RedisModuleCommandArgType
type
,
int
*
error
);
static
int
moduleConvertArgFlags
(
int
flags
);
/* --------------------------------------------------------------------------
/* --------------------------------------------------------------------------
* ## Heap allocation raw functions
* ## Heap allocation raw functions
*
*
...
@@ -770,6 +783,25 @@ int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc,
...
@@ -770,6 +783,25 @@ int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc,
return
result
->
numkeys
;
return
result
->
numkeys
;
}
}
/* This function returns the list of channels, with the same interface as
* moduleGetCommandKeysViaAPI, for modules that declare "getchannels-api"
* during registration. Unlike keys, this is the only way to declare channels. */
int
moduleGetCommandChannelsViaAPI
(
struct
redisCommand
*
cmd
,
robj
**
argv
,
int
argc
,
getKeysResult
*
result
)
{
RedisModuleCommand
*
cp
=
(
void
*
)(
unsigned
long
)
cmd
->
getkeys_proc
;
RedisModuleCtx
ctx
;
moduleCreateContext
(
&
ctx
,
cp
->
module
,
REDISMODULE_CTX_CHANNELS_POS_REQUEST
);
/* Initialize getKeysResult */
getKeysPrepareResult
(
result
,
MAX_KEYS_BUFFER
);
ctx
.
keys_result
=
result
;
cp
->
func
(
&
ctx
,(
void
**
)
argv
,
argc
);
/* We currently always use the array allocated by RM_RM_ChannelAtPosWithFlags() and don't try
* to optimize for the pre-allocated buffer. */
moduleFreeContext
(
&
ctx
);
return
result
->
numkeys
;
}
/* --------------------------------------------------------------------------
/* --------------------------------------------------------------------------
* ## Commands API
* ## Commands API
*
*
...
@@ -789,17 +821,23 @@ int RM_IsKeysPositionRequest(RedisModuleCtx *ctx) {
...
@@ -789,17 +821,23 @@ int RM_IsKeysPositionRequest(RedisModuleCtx *ctx) {
* keys, since it was flagged as "getkeys-api" during the registration,
* keys, since it was flagged as "getkeys-api" during the registration,
* the command implementation checks for this special call using the
* the command implementation checks for this special call using the
* RedisModule_IsKeysPositionRequest() API and uses this function in
* RedisModule_IsKeysPositionRequest() API and uses this function in
* order to report keys, like in the following example:
* order to report keys.
*
* The supported flags are the ones used by RM_SetCommandInfo, see REDISMODULE_CMD_KEY_*.
*
*
* The following is an example of how it could be used:
*
*
* if (RedisModule_IsKeysPositionRequest(ctx)) {
* if (RedisModule_IsKeysPositionRequest(ctx)) {
* RedisModule_KeyAtPos
(ctx,1
);
* RedisModule_KeyAtPos
WithFlags(ctx, 2, REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS
);
* RedisModule_KeyAtPos
(ctx,2
);
* RedisModule_KeyAtPos
WithFlags(ctx, 1, REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE | REDISMODULE_CMD_KEY_ACCESS
);
* }
* }
*
*
* Note: in the example below the get keys API would not be needed since
* Note: in the example above the get keys API could have been handled by key-specs (preferred).
* keys are at fixed positions. This interface is only used for commands
* Implementing the getkeys-api is required only when is it not possible to declare key-specs that cover all keys.
* with a more complex structure. */
*
void
RM_KeyAtPos
(
RedisModuleCtx
*
ctx
,
int
pos
)
{
*/
void
RM_KeyAtPosWithFlags
(
RedisModuleCtx
*
ctx
,
int
pos
,
int
flags
)
{
if
(
!
(
ctx
->
flags
&
REDISMODULE_CTX_KEYS_POS_REQUEST
)
||
!
ctx
->
keys_result
)
return
;
if
(
!
(
ctx
->
flags
&
REDISMODULE_CTX_KEYS_POS_REQUEST
)
||
!
ctx
->
keys_result
)
return
;
if
(
pos
<=
0
)
return
;
if
(
pos
<=
0
)
return
;
...
@@ -811,7 +849,74 @@ void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) {
...
@@ -811,7 +849,74 @@ void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) {
getKeysPrepareResult
(
res
,
newsize
);
getKeysPrepareResult
(
res
,
newsize
);
}
}
res
->
keys
[
res
->
numkeys
++
].
pos
=
pos
;
res
->
keys
[
res
->
numkeys
].
pos
=
pos
;
res
->
keys
[
res
->
numkeys
].
flags
=
moduleConvertKeySpecsFlags
(
flags
,
1
);
res
->
numkeys
++
;
}
/* This API existed before RM_KeyAtPosWithFlags was added, now deprecated and
* can be used for compatibility with older versions, before key-specs and flags
* were introduced. */
void
RM_KeyAtPos
(
RedisModuleCtx
*
ctx
,
int
pos
)
{
/* Default flags require full access */
int
flags
=
moduleConvertKeySpecsFlags
(
CMD_KEY_FULL_ACCESS
,
0
);
RM_KeyAtPosWithFlags
(
ctx
,
pos
,
flags
);
}
/* Return non-zero if a module command, that was declared with the
* flag "getchannels-api", is called in a special way to get the channel positions
* and not to get executed. Otherwise zero is returned. */
int
RM_IsChannelsPositionRequest
(
RedisModuleCtx
*
ctx
)
{
return
(
ctx
->
flags
&
REDISMODULE_CTX_CHANNELS_POS_REQUEST
)
!=
0
;
}
/* When a module command is called in order to obtain the position of
* channels, since it was flagged as "getchannels-api" during the
* registration, the command implementation checks for this special call
* using the RedisModule_IsChannelsPositionRequest() API and uses this
* function in order to report the channels.
*
* The supported flags are:
* * REDISMODULE_CMD_CHANNEL_SUBSCRIBE: This command will subscribe to the channel.
* * REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE: This command will unsubscribe from this channel.
* * REDISMODULE_CMD_CHANNEL_PUBLISH: This command will publish to this channel.
* * REDISMODULE_CMD_CHANNEL_PATTERN: Instead of acting on a specific channel, will act on any
* channel specified by the pattern. This is the same access
* used by the PSUBSCRIBE and PUNSUBSCRIBE commands available
* in Redis. Not intended to be used with PUBLISH permissions.
*
* The following is an example of how it could be used:
*
* if (RedisModule_IsChannelsPositionRequest(ctx)) {
* RedisModule_ChannelAtPosWithFlags(ctx, 1, REDISMODULE_CMD_CHANNEL_SUBSCRIBE | REDISMODULE_CMD_CHANNEL_PATTERN);
* RedisModule_ChannelAtPosWithFlags(ctx, 1, REDISMODULE_CMD_CHANNEL_PUBLISH);
* }
*
* Note: One usage of declaring channels is for evaluating ACL permissions. In this context,
* unsubscribing is always allowed, so commands will only be checked against subscribe and
* publish permissions. This is preferred over using RM_ACLCheckChannelPermissions, since
* it allows the ACLs to be checked before the command is executed. */
void
RM_ChannelAtPosWithFlags
(
RedisModuleCtx
*
ctx
,
int
pos
,
int
flags
)
{
if
(
!
(
ctx
->
flags
&
REDISMODULE_CTX_CHANNELS_POS_REQUEST
)
||
!
ctx
->
keys_result
)
return
;
if
(
pos
<=
0
)
return
;
getKeysResult
*
res
=
ctx
->
keys_result
;
/* Check overflow */
if
(
res
->
numkeys
==
res
->
size
)
{
int
newsize
=
res
->
size
+
(
res
->
size
>
8192
?
8192
:
res
->
size
);
getKeysPrepareResult
(
res
,
newsize
);
}
int
new_flags
=
0
;
if
(
flags
&
REDISMODULE_CMD_CHANNEL_SUBSCRIBE
)
new_flags
|=
CMD_CHANNEL_SUBSCRIBE
;
if
(
flags
&
REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE
)
new_flags
|=
CMD_CHANNEL_UNSUBSCRIBE
;
if
(
flags
&
REDISMODULE_CMD_CHANNEL_PUBLISH
)
new_flags
|=
CMD_CHANNEL_PUBLISH
;
if
(
flags
&
REDISMODULE_CMD_CHANNEL_PATTERN
)
new_flags
|=
CMD_CHANNEL_PATTERN
;
res
->
keys
[
res
->
numkeys
].
pos
=
pos
;
res
->
keys
[
res
->
numkeys
].
flags
=
new_flags
;
res
->
numkeys
++
;
}
}
/* Helper for RM_CreateCommand(). Turns a string representing command
/* Helper for RM_CreateCommand(). Turns a string representing command
...
@@ -840,6 +945,7 @@ int64_t commandFlagsFromString(char *s) {
...
@@ -840,6 +945,7 @@ int64_t commandFlagsFromString(char *s) {
else
if
(
!
strcasecmp
(
t
,
"no-auth"
))
flags
|=
CMD_NO_AUTH
;
else
if
(
!
strcasecmp
(
t
,
"no-auth"
))
flags
|=
CMD_NO_AUTH
;
else
if
(
!
strcasecmp
(
t
,
"may-replicate"
))
flags
|=
CMD_MAY_REPLICATE
;
else
if
(
!
strcasecmp
(
t
,
"may-replicate"
))
flags
|=
CMD_MAY_REPLICATE
;
else
if
(
!
strcasecmp
(
t
,
"getkeys-api"
))
flags
|=
CMD_MODULE_GETKEYS
;
else
if
(
!
strcasecmp
(
t
,
"getkeys-api"
))
flags
|=
CMD_MODULE_GETKEYS
;
else
if
(
!
strcasecmp
(
t
,
"getchannels-api"
))
flags
|=
CMD_MODULE_GETCHANNELS
;
else
if
(
!
strcasecmp
(
t
,
"no-cluster"
))
flags
|=
CMD_MODULE_NO_CLUSTER
;
else
if
(
!
strcasecmp
(
t
,
"no-cluster"
))
flags
|=
CMD_MODULE_NO_CLUSTER
;
else
if
(
!
strcasecmp
(
t
,
"no-mandatory-keys"
))
flags
|=
CMD_NO_MANDATORY_KEYS
;
else
if
(
!
strcasecmp
(
t
,
"no-mandatory-keys"
))
flags
|=
CMD_NO_MANDATORY_KEYS
;
else
if
(
!
strcasecmp
(
t
,
"allow-busy"
))
flags
|=
CMD_ALLOW_BUSY
;
else
if
(
!
strcasecmp
(
t
,
"allow-busy"
))
flags
|=
CMD_ALLOW_BUSY
;
...
@@ -850,33 +956,6 @@ int64_t commandFlagsFromString(char *s) {
...
@@ -850,33 +956,6 @@ int64_t commandFlagsFromString(char *s) {
return
flags
;
return
flags
;
}
}
/* Helper for RM_CreateCommand(). Turns a string representing keys spec
* flags into the keys spec flags used by the Redis core.
*
* It returns the set of flags, or -1 if unknown flags are found. */
int64_t
commandKeySpecsFlagsFromString
(
const
char
*
s
)
{
int
count
,
j
;
int64_t
flags
=
0
;
sds
*
tokens
=
sdssplitlen
(
s
,
strlen
(
s
),
" "
,
1
,
&
count
);
for
(
j
=
0
;
j
<
count
;
j
++
)
{
char
*
t
=
tokens
[
j
];
if
(
!
strcasecmp
(
t
,
"RO"
))
flags
|=
CMD_KEY_RO
;
else
if
(
!
strcasecmp
(
t
,
"RW"
))
flags
|=
CMD_KEY_RW
;
else
if
(
!
strcasecmp
(
t
,
"OW"
))
flags
|=
CMD_KEY_OW
;
else
if
(
!
strcasecmp
(
t
,
"RM"
))
flags
|=
CMD_KEY_RM
;
else
if
(
!
strcasecmp
(
t
,
"access"
))
flags
|=
CMD_KEY_ACCESS
;
else
if
(
!
strcasecmp
(
t
,
"insert"
))
flags
|=
CMD_KEY_INSERT
;
else
if
(
!
strcasecmp
(
t
,
"update"
))
flags
|=
CMD_KEY_UPDATE
;
else
if
(
!
strcasecmp
(
t
,
"delete"
))
flags
|=
CMD_KEY_DELETE
;
else
if
(
!
strcasecmp
(
t
,
"channel"
))
flags
|=
CMD_KEY_CHANNEL
;
else
if
(
!
strcasecmp
(
t
,
"incomplete"
))
flags
|=
CMD_KEY_INCOMPLETE
;
else
break
;
}
sdsfreesplitres
(
tokens
,
count
);
if
(
j
!=
count
)
return
-
1
;
/* Some token not processed correctly. */
return
flags
;
}
RedisModuleCommand
*
moduleCreateCommandProxy
(
struct
RedisModule
*
module
,
sds
declared_name
,
sds
fullname
,
RedisModuleCmdFunc
cmdfunc
,
int64_t
flags
,
int
firstkey
,
int
lastkey
,
int
keystep
);
RedisModuleCommand
*
moduleCreateCommandProxy
(
struct
RedisModule
*
module
,
sds
declared_name
,
sds
fullname
,
RedisModuleCmdFunc
cmdfunc
,
int64_t
flags
,
int
firstkey
,
int
lastkey
,
int
keystep
);
/* Register a new command in the Redis server, that will be handled by
/* Register a new command in the Redis server, that will be handled by
...
@@ -946,6 +1025,8 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds dec
...
@@ -946,6 +1025,8 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds dec
* * **"allow-busy"**: Permit the command while the server is blocked either by
* * **"allow-busy"**: Permit the command while the server is blocked either by
* a script or by a slow module command, see
* a script or by a slow module command, see
* RM_Yield.
* RM_Yield.
* * **"getchannels-api"**: The command implements the interface to return
* the arguments that are channels.
*
*
* The last three parameters specify which arguments of the new command are
* The last three parameters specify which arguments of the new command are
* Redis keys. See https://redis.io/commands/command for more information.
* Redis keys. See https://redis.io/commands/command for more information.
...
@@ -965,9 +1046,7 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds dec
...
@@ -965,9 +1046,7 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds dec
* NOTE: The scheme described above serves a limited purpose and can
* NOTE: The scheme described above serves a limited purpose and can
* only be used to find keys that exist at constant indices.
* only be used to find keys that exist at constant indices.
* For non-trivial key arguments, you may pass 0,0,0 and use
* For non-trivial key arguments, you may pass 0,0,0 and use
* RedisModule_AddCommandKeySpec (see documentation).
* RedisModule_SetCommandInfo to set key specs using a more advanced scheme. */
*
*/
int
RM_CreateCommand
(
RedisModuleCtx
*
ctx
,
const
char
*
name
,
RedisModuleCmdFunc
cmdfunc
,
const
char
*
strflags
,
int
firstkey
,
int
lastkey
,
int
keystep
)
{
int
RM_CreateCommand
(
RedisModuleCtx
*
ctx
,
const
char
*
name
,
RedisModuleCmdFunc
cmdfunc
,
const
char
*
strflags
,
int
firstkey
,
int
lastkey
,
int
keystep
)
{
int64_t
flags
=
strflags
?
commandFlagsFromString
((
char
*
)
strflags
)
:
0
;
int64_t
flags
=
strflags
?
commandFlagsFromString
((
char
*
)
strflags
)
:
0
;
if
(
flags
==
-
1
)
return
REDISMODULE_ERR
;
if
(
flags
==
-
1
)
return
REDISMODULE_ERR
;
...
@@ -1020,7 +1099,7 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds dec
...
@@ -1020,7 +1099,7 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds dec
cp
->
rediscmd
->
key_specs
=
cp
->
rediscmd
->
key_specs_static
;
cp
->
rediscmd
->
key_specs
=
cp
->
rediscmd
->
key_specs_static
;
if
(
firstkey
!=
0
)
{
if
(
firstkey
!=
0
)
{
cp
->
rediscmd
->
key_specs_num
=
1
;
cp
->
rediscmd
->
key_specs_num
=
1
;
cp
->
rediscmd
->
key_specs
[
0
].
flags
=
0
;
cp
->
rediscmd
->
key_specs
[
0
].
flags
=
CMD_KEY_FULL_ACCESS
|
CMD_KEY_VARIABLE_FLAGS
;
cp
->
rediscmd
->
key_specs
[
0
].
begin_search_type
=
KSPEC_BS_INDEX
;
cp
->
rediscmd
->
key_specs
[
0
].
begin_search_type
=
KSPEC_BS_INDEX
;
cp
->
rediscmd
->
key_specs
[
0
].
bs
.
index
.
pos
=
firstkey
;
cp
->
rediscmd
->
key_specs
[
0
].
bs
.
index
.
pos
=
firstkey
;
cp
->
rediscmd
->
key_specs
[
0
].
find_keys_type
=
KSPEC_FK_RANGE
;
cp
->
rediscmd
->
key_specs
[
0
].
find_keys_type
=
KSPEC_FK_RANGE
;
...
@@ -1121,216 +1200,735 @@ int RM_CreateSubcommand(RedisModuleCommand *parent, const char *name, RedisModul
...
@@ -1121,216 +1200,735 @@ int RM_CreateSubcommand(RedisModuleCommand *parent, const char *name, RedisModul
return
REDISMODULE_OK
;
return
REDISMODULE_OK
;
}
}
/* Return `struct RedisModule *` as `void *` to avoid exposing it outside of module.c. */
/* Accessors of array elements of structs where the element size is stored
void
*
moduleGetHandleByName
(
char
*
modulename
)
{
* separately in the version struct. */
return
dictFetchValue
(
modules
,
modulename
);
static
RedisModuleCommandHistoryEntry
*
}
moduleCmdHistoryEntryAt
(
const
RedisModuleCommandInfoVersion
*
version
,
RedisModuleCommandHistoryEntry
*
entries
,
int
index
)
{
off_t
offset
=
index
*
version
->
sizeof_historyentry
;
return
(
RedisModuleCommandHistoryEntry
*
)((
char
*
)(
entries
)
+
offset
);
}
static
RedisModuleCommandKeySpec
*
moduleCmdKeySpecAt
(
const
RedisModuleCommandInfoVersion
*
version
,
RedisModuleCommandKeySpec
*
keyspecs
,
int
index
)
{
off_t
offset
=
index
*
version
->
sizeof_keyspec
;
return
(
RedisModuleCommandKeySpec
*
)((
char
*
)(
keyspecs
)
+
offset
);
}
static
RedisModuleCommandArg
*
moduleCmdArgAt
(
const
RedisModuleCommandInfoVersion
*
version
,
const
RedisModuleCommandArg
*
args
,
int
index
)
{
off_t
offset
=
index
*
version
->
sizeof_arg
;
return
(
RedisModuleCommandArg
*
)((
char
*
)(
args
)
+
offset
);
}
/* Set additional command information.
*
* Affects the output of `COMMAND`, `COMMAND INFO` and `COMMAND DOCS`, Cluster,
* ACL and is used to filter commands with the wrong number of arguments before
* the call reaches the module code.
*
* This function can be called after creating a command using RM_CreateCommand
* and fetching the command pointer using RM_GetCommand. The information can
* only be set once for each command and has the following structure:
*
* typedef struct RedisModuleCommandInfo {
* const RedisModuleCommandInfoVersion *version;
* const char *summary;
* const char *complexity;
* const char *since;
* RedisModuleCommandHistoryEntry *history;
* const char *tips;
* int arity;
* RedisModuleCommandKeySpec *key_specs;
* RedisModuleCommandArg *args;
* } RedisModuleCommandInfo;
*
* All fields except `version` are optional. Explanation of the fields:
*
* - `version`: This field enables compatibility with different Redis versions.
* Always set this field to REDISMODULE_COMMAND_INFO_VERSION.
*
* - `summary`: A short description of the command (optional).
*
* - `complexity`: Complexity description (optional).
*
* - `since`: The version where the command was introduced (optional).
* Note: The version specified should be the module's, not Redis version.
*
* - `history`: An array of RedisModuleCommandHistoryEntry (optional), which is
* a struct with the following fields:
*
* const char *since;
* const char *changes;
*
* `since` is a version string and `changes` is a string describing the
* changes. The array is terminated by a zeroed entry, i.e. an entry with
* both strings set to NULL.
*
* - `tips`: A string of space-separated tips regarding this command, meant for
* clients and proxies. See https://redis.io/topics/command-tips.
*
* - `arity`: Number of arguments, including the command name itself. A positive
* number specifies an exact number of arguments and a negative number
* specifies a minimum number of arguments, so use -N to say >= N. Redis
* validates a call before passing it to a module, so this can replace an
* arity check inside the module command implementation. A value of 0 (or an
* omitted arity field) is equivalent to -2 if the command has sub commands
* and -1 otherwise.
*
* - `key_specs`: An array of RedisModuleCommandKeySpec, terminated by an
* element memset to zero. This is a scheme that tries to describe the
* positions of key arguments better than the old RM_CreateCommand arguments
* `firstkey`, `lastkey`, `keystep` and is needed if those three are not
* enough to describe the key positions. There are two steps to retrieve key
* positions: *begin search* (BS) in which index should find the first key and
* *find keys* (FK) which, relative to the output of BS, describes how can we
* will which arguments are keys. Additionally, there are key specific flags.
*
* Key-specs cause the triplet (firstkey, lastkey, keystep) given in
* RM_CreateCommand to be recomputed, but it is still useful to provide
* these three parameters in RM_CreateCommand, to better support old Redis
* versions where RM_SetCommandInfo is not available.
*
* Note that key-specs don't fully replace the "getkeys-api" (see
* RM_CreateCommand, RM_IsKeysPositionRequest and RM_KeyAtPosWithFlags) so
* it may be a good idea to supply both key-specs and implement the
* getkeys-api.
*
* A key-spec has the following structure:
*
* typedef struct RedisModuleCommandKeySpec {
* const char *notes;
* uint64_t flags;
* RedisModuleKeySpecBeginSearchType begin_search_type;
* union {
* struct {
* int pos;
* } index;
* struct {
* const char *keyword;
* int startfrom;
* } keyword;
* } bs;
* RedisModuleKeySpecFindKeysType find_keys_type;
* union {
* struct {
* int lastkey;
* int keystep;
* int limit;
* } range;
* struct {
* int keynumidx;
* int firstkey;
* int keystep;
* } keynum;
* } fk;
* } RedisModuleCommandKeySpec;
*
* Explanation of the fields of RedisModuleCommandKeySpec:
*
* * `notes`: Optional notes or clarifications about this key spec.
*
* * `flags`: A bitwise or of key-spec flags described below.
*
* * `begin_search_type`: This describes how the first key is discovered.
* There are two ways to determine the first key:
*
* * `REDISMODULE_KSPEC_BS_UNKNOWN`: There is no way to tell where the
* key args start.
* * `REDISMODULE_KSPEC_BS_INDEX`: Key args start at a constant index.
* * `REDISMODULE_KSPEC_BS_KEYWORD`: Key args start just after a
* specific keyword.
*
* * `bs`: This is a union in which the `index` or `keyword` branch is used
* depending on the value of the `begin_search_type` field.
*
* * `bs.index.pos`: The index from which we start the search for keys.
* (`REDISMODULE_KSPEC_BS_INDEX` only.)
*
* * `bs.keyword.keyword`: The keyword (string) that indicates the
* beginning of key arguments. (`REDISMODULE_KSPEC_BS_KEYWORD` only.)
*
* * `bs.keyword.startfrom`: An index in argv from which to start
* searching. Can be negative, which means start search from the end,
* in reverse. Example: -2 means to start in reverse from the
* penultimate argument. (`REDISMODULE_KSPEC_BS_KEYWORD` only.)
*
* * `find_keys_type`: After the "begin search", this describes which
* arguments are keys. The strategies are:
*
* * `REDISMODULE_KSPEC_BS_UNKNOWN`: There is no way to tell where the
* key args are located.
* * `REDISMODULE_KSPEC_FK_RANGE`: Keys end at a specific index (or
* relative to the last argument).
* * `REDISMODULE_KSPEC_FK_KEYNUM`: There's an argument that contains
* the number of key args somewhere before the keys themselves.
*
* `find_keys_type` and `fk` can be omitted if this keyspec describes
* exactly one key.
*
* * `fk`: This is a union in which the `range` or `keynum` branch is used
* depending on the value of the `find_keys_type` field.
*
* * `fk.range` (for `REDISMODULE_KSPEC_FK_RANGE`): A struct with the
* following fields:
*
* * `lastkey`: Index of the last key relative to the result of the
* begin search step. Can be negative, in which case it's not
* relative. -1 indicates the last argument, -2 one before the
* last and so on.
*
* * `keystep`: How many arguments should we skip after finding a
* key, in order to find the next one?
*
* * `limit`: If `lastkey` is -1, we use `limit` to stop the search
* by a factor. 0 and 1 mean no limit. 2 means 1/2 of the
* remaining args, 3 means 1/3, and so on.
*
* * `fk.keynum` (for `REDISMODULE_KSPEC_FK_KEYNUM`): A struct with the
* following fields:
*
* * `keynumidx`: Index of the argument containing the number of
* keys to come, relative to the result of the begin search step.
*
* * `firstkey`: Index of the fist key relative to the result of the
* begin search step. (Usually it's just after `keynumidx`, in
* which case it should be set to `keynumidx + 1`.)
*
* * `keystep`: How many argumentss should we skip after finding a
* key, in order to find the next one?
*
* Key-spec flags:
*
* The first four refer to what the command actually does with the *value or
* metadata of the key*, and not necessarily the user data or how it affects
* it. Each key-spec may must have exactly one of these. Any operation
* that's not distinctly deletion, overwrite or read-only would be marked as
* RW.
*
* * `REDISMODULE_CMD_KEY_RO`: Read-Only. Reads the value of the key, but
* doesn't necessarily return it.
*
* * `REDISMODULE_CMD_KEY_RW`: Read-Write. Modifies the data stored in the
* value of the key or its metadata.
*
* * `REDISMODULE_CMD_KEY_OW`: Overwrite. Overwrites the data stored in the
* value of the key.
*
* * `REDISMODULE_CMD_KEY_RM`: Deletes the key.
*
* The next four refer to *user data inside the value of the key*, not the
* metadata like LRU, type, cardinality. It refers to the logical operation
* on the user's data (actual input strings or TTL), being
* used/returned/copied/changed. It doesn't refer to modification or
* returning of metadata (like type, count, presence of data). ACCESS can be
* combined with one of the write operations INSERT, DELETE or UPDATE. Any
* write that's not an INSERT or a DELETE would be UPDATE.
*
* * `REDISMODULE_CMD_KEY_ACCESS`: Returns, copies or uses the user data
* from the value of the key.
*
* * `REDISMODULE_CMD_KEY_UPDATE`: Updates data to the value, new value may
* depend on the old value.
*
* * `REDISMODULE_CMD_KEY_INSERT`: Adds data to the value with no chance of
* modification or deletion of existing data.
*
* * `REDISMODULE_CMD_KEY_DELETE`: Explicitly deletes some content from the
* value of the key.
*
* Other flags:
*
* * `REDISMODULE_CMD_KEY_NOT_KEY`: The key is not actually a key, but
* should be routed in cluster mode as if it was a key.
*
* * `REDISMODULE_CMD_KEY_INCOMPLETE`: The keyspec might not point out all
* the keys it should cover.
*
* * `REDISMODULE_CMD_KEY_VARIABLE_FLAGS`: Some keys might have different
* flags depending on arguments.
*
* - `args`: An array of RedisModuleCommandArg, terminated by an element memset
* to zero. RedisModuleCommandArg is a structure with at the fields described
* below.
*
* typedef struct RedisModuleCommandArg {
* const char *name;
* RedisModuleCommandArgType type;
* int key_spec_index;
* const char *token;
* const char *summary;
* const char *since;
* int flags;
* struct RedisModuleCommandArg *subargs;
* } RedisModuleCommandArg;
*
* Explanation of the fields:
*
* * `name`: Name of the argument.
*
* * `type`: The type of the argument. See below for details. The types
* `REDISMODULE_ARG_TYPE_ONEOF` and `REDISMODULE_ARG_TYPE_BLOCK` require
* an argument to have sub-arguments, i.e. `subargs`.
*
* * `key_spec_index`: If the `type` is `REDISMODULE_ARG_TYPE_KEY` you must
* provide the index of the key-spec associated with this argument. See
* `key_specs` above. If the argument is not a key, you may specify -1.
*
* * `token`: The token preceding the argument (optional). Example: the
* argument `seconds` in `SET` has a token `EX`. If the argument consists
* of only a token (for example `NX` in `SET`) the type should be
* `REDISMODULE_ARG_TYPE_PURE_TOKEN` and `value` should be NULL.
*
* * `summary`: A short description of the argument (optional).
*
* * `since`: The first version which included this argument (optional).
*
* * `flags`: A bitwise or of the macros `REDISMODULE_CMD_ARG_*`. See below.
*
* * `value`: The display-value of the argument. This string is what should
* be displayed when creating the command syntax from the output of
* `COMMAND`. If `token` is not NULL, it should also be displayed.
*
* Explanation of `RedisModuleCommandArgType`:
*
* * `REDISMODULE_ARG_TYPE_STRING`: String argument.
* * `REDISMODULE_ARG_TYPE_INTEGER`: Integer argument.
* * `REDISMODULE_ARG_TYPE_DOUBLE`: Double-precision float argument.
* * `REDISMODULE_ARG_TYPE_KEY`: String argument representing a keyname.
* * `REDISMODULE_ARG_TYPE_PATTERN`: String, but regex pattern.
* * `REDISMODULE_ARG_TYPE_UNIX_TIME`: Integer, but Unix timestamp.
* * `REDISMODULE_ARG_TYPE_PURE_TOKEN`: Argument doesn't have a placeholder.
* It's just a token without a value. Example: the `KEEPTTL` option of the
* `SET` command.
* * `REDISMODULE_ARG_TYPE_ONEOF`: Used when the user can choose only one of
* a few sub-arguments. Requires `subargs`. Example: the `NX` and `XX`
* options of `SET`.
* * `REDISMODULE_ARG_TYPE_BLOCK`: Used when one wants to group together
* several sub-arguments, usually to apply something on all of them, like
* making the entire group "optional". Requires `subargs`. Example: the
* `LIMIT offset count` parameters in `ZRANGE`.
*
* Explanation of the command argument flags:
*
* * `REDISMODULE_CMD_ARG_OPTIONAL`: The argument is optional (like GET in
* the SET command).
* * `REDISMODULE_CMD_ARG_MULTIPLE`: The argument may repeat itself (like
* key in DEL).
* * `REDISMODULE_CMD_ARG_MULTIPLE_TOKEN`: The argument may repeat itself,
* and so does its token (like `GET pattern` in SORT).
*
* On success REDISMODULE_OK is returned. On error REDISMODULE_ERR is returned
* and `errno` is set to EINVAL if invalid info was provided or EEXIST if info
* has already been set. If the info is invalid, a warning is logged explaining
* which part of the info is invalid and why. */
int
RM_SetCommandInfo
(
RedisModuleCommand
*
command
,
const
RedisModuleCommandInfo
*
info
)
{
if
(
!
moduleValidateCommandInfo
(
info
))
{
errno
=
EINVAL
;
return
REDISMODULE_ERR
;
}
/* Returns 1 if `cmd` is a command of the module `modulename`. 0 otherwise. */
struct
redisCommand
*
cmd
=
command
->
rediscmd
;
int
moduleIsModuleCommand
(
void
*
module_handle
,
struct
redisCommand
*
cmd
)
{
if
(
cmd
->
proc
!=
RedisModuleCommandDispatcher
)
return
0
;
if
(
module_handle
==
NULL
)
return
0
;
RedisModuleCommand
*
cp
=
(
void
*
)(
unsigned
long
)
cmd
->
getkeys_proc
;
return
(
cp
->
module
==
module_handle
);
}
void
extendKeySpecsIfNeeded
(
struct
redisCommand
*
cmd
)
{
/* Check if any info has already been set. Overwriting info involves freeing
/* We extend even if key_specs_num == key_specs_max because
* the old info, which is not implemented. */
* this function is called prior to adding a new spec */
if
(
cmd
->
summary
||
cmd
->
complexity
||
cmd
->
since
||
cmd
->
history
||
if
(
cmd
->
key_specs_num
<
cmd
->
key_specs_max
)
cmd
->
tips
||
cmd
->
args
||
return
;
!
(
cmd
->
key_specs_num
==
0
||
/* Allow key spec populated from legacy (first,last,step) to exist. */
(
cmd
->
key_specs_num
==
1
&&
cmd
->
key_specs
==
cmd
->
key_specs_static
&&
cmd
->
key_specs
[
0
].
begin_search_type
==
KSPEC_BS_INDEX
&&
cmd
->
key_specs
[
0
].
find_keys_type
==
KSPEC_FK_RANGE
)))
{
errno
=
EEXIST
;
return
REDISMODULE_ERR
;
}
cmd
->
key_specs_max
++
;
if
(
info
->
summary
)
cmd
->
summary
=
zstrdup
(
info
->
summary
);
if
(
info
->
complexity
)
cmd
->
complexity
=
zstrdup
(
info
->
complexity
);
if
(
info
->
since
)
cmd
->
since
=
zstrdup
(
info
->
since
);
if
(
cmd
->
key_specs
==
cmd
->
key_specs_static
)
{
const
RedisModuleCommandInfoVersion
*
version
=
info
->
version
;
cmd
->
key_specs
=
zmalloc
(
sizeof
(
keySpec
)
*
cmd
->
key_specs_max
);
if
(
info
->
history
)
{
memcpy
(
cmd
->
key_specs
,
cmd
->
key_specs_static
,
sizeof
(
keySpec
)
*
cmd
->
key_specs_num
);
size_t
count
=
0
;
}
else
{
while
(
moduleCmdHistoryEntryAt
(
version
,
info
->
history
,
count
)
->
since
)
cmd
->
key_specs
=
zrealloc
(
cmd
->
key_specs
,
sizeof
(
keySpec
)
*
cmd
->
key_specs_max
);
count
++
;
serverAssert
(
count
<
SIZE_MAX
/
sizeof
(
commandHistory
));
cmd
->
history
=
zmalloc
(
sizeof
(
commandHistory
)
*
(
count
+
1
));
for
(
size_t
j
=
0
;
j
<
count
;
j
++
)
{
RedisModuleCommandHistoryEntry
*
entry
=
moduleCmdHistoryEntryAt
(
version
,
info
->
history
,
j
);
cmd
->
history
[
j
].
since
=
zstrdup
(
entry
->
since
);
cmd
->
history
[
j
].
changes
=
zstrdup
(
entry
->
changes
);
}
cmd
->
history
[
count
].
since
=
NULL
;
cmd
->
history
[
count
].
changes
=
NULL
;
cmd
->
num_history
=
count
;
}
if
(
info
->
tips
)
{
int
count
;
sds
*
tokens
=
sdssplitlen
(
info
->
tips
,
strlen
(
info
->
tips
),
" "
,
1
,
&
count
);
if
(
tokens
)
{
cmd
->
tips
=
zmalloc
(
sizeof
(
char
*
)
*
(
count
+
1
));
for
(
int
j
=
0
;
j
<
count
;
j
++
)
{
cmd
->
tips
[
j
]
=
zstrdup
(
tokens
[
j
]);
}
cmd
->
tips
[
count
]
=
NULL
;
cmd
->
num_tips
=
count
;
sdsfreesplitres
(
tokens
,
count
);
}
}
}
}
int
moduleAddCommandKeySpec
(
RedisModuleCommand
*
command
,
const
char
*
specflags
,
int
*
index
)
{
if
(
info
->
arity
)
cmd
->
arity
=
info
->
arity
;
int64_t
flags
=
specflags
?
commandKeySpecsFlagsFromString
(
specflags
)
:
0
;
if
(
flags
==
-
1
)
return
REDISMODULE_ERR
;
struct
redisCommand
*
cmd
=
command
->
rediscmd
;
if
(
info
->
key_specs
)
{
/* Count and allocate the key specs. */
size_t
count
=
0
;
while
(
moduleCmdKeySpecAt
(
version
,
info
->
key_specs
,
count
)
->
begin_search_type
)
count
++
;
serverAssert
(
count
<
INT_MAX
);
if
(
count
<=
STATIC_KEY_SPECS_NUM
)
{
cmd
->
key_specs_max
=
STATIC_KEY_SPECS_NUM
;
cmd
->
key_specs
=
cmd
->
key_specs_static
;
}
else
{
cmd
->
key_specs_max
=
count
;
cmd
->
key_specs
=
zmalloc
(
sizeof
(
keySpec
)
*
count
);
}
extendKeySpecsIfNeeded
(
cmd
);
/* Copy the contents of the RedisModuleCommandKeySpec array. */
cmd
->
key_specs_num
=
count
;
for
(
size_t
j
=
0
;
j
<
count
;
j
++
)
{
RedisModuleCommandKeySpec
*
spec
=
moduleCmdKeySpecAt
(
version
,
info
->
key_specs
,
j
);
cmd
->
key_specs
[
j
].
notes
=
spec
->
notes
?
zstrdup
(
spec
->
notes
)
:
NULL
;
cmd
->
key_specs
[
j
].
flags
=
moduleConvertKeySpecsFlags
(
spec
->
flags
,
1
);
switch
(
spec
->
begin_search_type
)
{
case
REDISMODULE_KSPEC_BS_UNKNOWN
:
cmd
->
key_specs
[
j
].
begin_search_type
=
KSPEC_BS_UNKNOWN
;
break
;
case
REDISMODULE_KSPEC_BS_INDEX
:
cmd
->
key_specs
[
j
].
begin_search_type
=
KSPEC_BS_INDEX
;
cmd
->
key_specs
[
j
].
bs
.
index
.
pos
=
spec
->
bs
.
index
.
pos
;
break
;
case
REDISMODULE_KSPEC_BS_KEYWORD
:
cmd
->
key_specs
[
j
].
begin_search_type
=
KSPEC_BS_KEYWORD
;
cmd
->
key_specs
[
j
].
bs
.
keyword
.
keyword
=
zstrdup
(
spec
->
bs
.
keyword
.
keyword
);
cmd
->
key_specs
[
j
].
bs
.
keyword
.
startfrom
=
spec
->
bs
.
keyword
.
startfrom
;
break
;
default:
/* Can't happen; stopped in moduleValidateCommandInfo(). */
serverPanic
(
"Unknown begin_search_type"
);
}
*
index
=
cmd
->
key_specs_num
;
switch
(
spec
->
find_keys_type
)
{
cmd
->
key_specs
[
cmd
->
key_specs_num
].
begin_search_type
=
KSPEC_BS_INVALID
;
case
REDISMODULE_KSPEC_FK_OMITTED
:
cmd
->
key_specs
[
cmd
->
key_specs_num
].
find_keys_type
=
KSPEC_FK_INVALID
;
/* Omitted field is shorthand to say that it's a single key. */
cmd
->
key_specs
[
cmd
->
key_specs_num
].
flags
=
flags
;
cmd
->
key_specs
[
j
].
find_keys_type
=
KSPEC_FK_RANGE
;
cmd
->
key_specs_num
++
;
cmd
->
key_specs
[
j
].
fk
.
range
.
lastkey
=
0
;
return
REDISMODULE_OK
;
cmd
->
key_specs
[
j
].
fk
.
range
.
keystep
=
1
;
}
cmd
->
key_specs
[
j
].
fk
.
range
.
limit
=
0
;
break
;
case
REDISMODULE_KSPEC_FK_UNKNOWN
:
cmd
->
key_specs
[
j
].
find_keys_type
=
KSPEC_FK_UNKNOWN
;
break
;
case
REDISMODULE_KSPEC_FK_RANGE
:
cmd
->
key_specs
[
j
].
find_keys_type
=
KSPEC_FK_RANGE
;
cmd
->
key_specs
[
j
].
fk
.
range
.
lastkey
=
spec
->
fk
.
range
.
lastkey
;
cmd
->
key_specs
[
j
].
fk
.
range
.
keystep
=
spec
->
fk
.
range
.
keystep
;
cmd
->
key_specs
[
j
].
fk
.
range
.
limit
=
spec
->
fk
.
range
.
limit
;
break
;
case
REDISMODULE_KSPEC_FK_KEYNUM
:
cmd
->
key_specs
[
j
].
find_keys_type
=
KSPEC_FK_KEYNUM
;
cmd
->
key_specs
[
j
].
fk
.
keynum
.
keynumidx
=
spec
->
fk
.
keynum
.
keynumidx
;
cmd
->
key_specs
[
j
].
fk
.
keynum
.
firstkey
=
spec
->
fk
.
keynum
.
firstkey
;
cmd
->
key_specs
[
j
].
fk
.
keynum
.
keystep
=
spec
->
fk
.
keynum
.
keystep
;
break
;
default:
/* Can't happen; stopped in moduleValidateCommandInfo(). */
serverPanic
(
"Unknown find_keys_type"
);
}
}
int
moduleSetCommandKeySpecBeginSearch
(
RedisModuleCommand
*
command
,
int
index
,
keySpec
*
spec
)
{
/* Update the legacy (first,last,step) spec used by the COMMAND command,
struct
redisCommand
*
cmd
=
command
->
rediscmd
;
* by trying to "glue" consecutive range key specs. */
populateCommandLegacyRangeSpec
(
cmd
);
populateCommandMovableKeys
(
cmd
);
}
if
(
index
>=
cmd
->
key_specs_num
)
if
(
info
->
args
)
{
return
REDISMODULE_ERR
;
cmd
->
args
=
moduleCopyCommandArgs
(
info
->
args
,
version
);
/* Populate arg.num_args with the number of subargs, recursively */
cmd
->
num_args
=
populateArgsStructure
(
cmd
->
args
);
}
cmd
->
key_specs
[
index
].
begin_search_type
=
spec
->
begin_search_type
;
/* Fields added in future versions to be added here, under conditions like
cmd
->
key_specs
[
index
].
bs
=
spec
->
bs
;
* `if (info->version >= 2) { access version 2 fields here }` */
return
REDISMODULE_OK
;
return
REDISMODULE_OK
;
}
}
int
moduleSetCommandKeySpecFindKeys
(
RedisModuleCommand
*
command
,
int
index
,
keySpec
*
spec
)
{
/* Returns 1 if v is a power of two, 0 otherwise. */
struct
redisCommand
*
cmd
=
command
->
rediscmd
;
static
inline
int
isPowerOfTwo
(
uint64_t
v
)
{
return
v
&&
!
(
v
&
(
v
-
1
));
}
if
(
index
>=
cmd
->
key_specs_num
)
/* Returns 1 if the command info is valid and 0 otherwise. */
return
REDISMODULE_ERR
;
static
int
moduleValidateCommandInfo
(
const
RedisModuleCommandInfo
*
info
)
{
const
RedisModuleCommandInfoVersion
*
version
=
info
->
version
;
if
(
!
version
)
{
serverLog
(
LL_WARNING
,
"Invalid command info: version missing"
);
return
0
;
}
/* No validation for the fields summary, complexity, since, tips (strings or
* NULL) and arity (any integer). */
/* History: If since is set, changes must also be set. */
if
(
info
->
history
)
{
for
(
size_t
j
=
0
;
moduleCmdHistoryEntryAt
(
version
,
info
->
history
,
j
)
->
since
;
j
++
)
{
if
(
!
moduleCmdHistoryEntryAt
(
version
,
info
->
history
,
j
)
->
changes
)
{
serverLog
(
LL_WARNING
,
"Invalid command info: history[%zd].changes missing"
,
j
);
return
0
;
}
}
}
cmd
->
key_specs
[
index
].
find_keys_type
=
spec
->
find_keys_type
;
/* Key specs. */
cmd
->
key_specs
[
index
].
fk
=
spec
->
fk
;
if
(
info
->
key_specs
)
{
for
(
size_t
j
=
0
;
moduleCmdKeySpecAt
(
version
,
info
->
key_specs
,
j
)
->
begin_search_type
;
j
++
)
{
RedisModuleCommandKeySpec
*
spec
=
moduleCmdKeySpecAt
(
version
,
info
->
key_specs
,
j
);
if
(
j
>=
INT_MAX
)
{
serverLog
(
LL_WARNING
,
"Invalid command info: Too many key specs"
);
return
0
;
/* redisCommand.key_specs_num is an int. */
}
/* Refresh legacy range */
/* Flags. Exactly one flag in a group is set if and only if the
populateCommandLegacyRangeSpec
(
cmd
);
* masked bits is a power of two. */
/* Refresh movablekeys flag */
uint64_t
key_flags
=
populateCommandMovableKeys
(
cmd
);
REDISMODULE_CMD_KEY_RO
|
REDISMODULE_CMD_KEY_RW
|
REDISMODULE_CMD_KEY_OW
|
REDISMODULE_CMD_KEY_RM
;
uint64_t
write_flags
=
REDISMODULE_CMD_KEY_INSERT
|
REDISMODULE_CMD_KEY_DELETE
|
REDISMODULE_CMD_KEY_UPDATE
;
if
(
!
isPowerOfTwo
(
spec
->
flags
&
key_flags
))
{
serverLog
(
LL_WARNING
,
"Invalid command info: key_specs[%zd].flags: "
"Exactly one of the flags RO, RW, OW, RM reqired"
,
j
);
return
0
;
}
if
((
spec
->
flags
&
write_flags
)
!=
0
&&
!
isPowerOfTwo
(
spec
->
flags
&
write_flags
))
{
serverLog
(
LL_WARNING
,
"Invalid command info: key_specs[%zd].flags: "
"INSERT, DELETE and UPDATE are mutually exclusive"
,
j
);
return
0
;
}
return
REDISMODULE_OK
;
switch
(
spec
->
begin_search_type
)
{
}
case
REDISMODULE_KSPEC_BS_UNKNOWN
:
break
;
case
REDISMODULE_KSPEC_BS_INDEX
:
break
;
case
REDISMODULE_KSPEC_BS_KEYWORD
:
if
(
spec
->
bs
.
keyword
.
keyword
==
NULL
)
{
serverLog
(
LL_WARNING
,
"Invalid command info: key_specs[%zd].bs.keyword.keyword "
"required when begin_search_type is KEYWORD"
,
j
);
return
0
;
}
break
;
default:
serverLog
(
LL_WARNING
,
"Invalid command info: key_specs[%zd].begin_search_type: "
"Invalid value %d"
,
j
,
spec
->
begin_search_type
);
return
0
;
}
/* **The key spec API is not officially released and it is going to be changed
/* Validate find_keys_type. */
* in Redis 7.0. It has been disabled temporarily.**
switch
(
spec
->
find_keys_type
)
{
*
case
REDISMODULE_KSPEC_FK_OMITTED
:
break
;
/* short for RANGE {0,1,0} */
* Key specs is a scheme that tries to describe the location
case
REDISMODULE_KSPEC_FK_UNKNOWN
:
break
;
* of key arguments better than the old [first,last,step] scheme
case
REDISMODULE_KSPEC_FK_RANGE
:
break
;
* which is limited and doesn't fit many commands.
case
REDISMODULE_KSPEC_FK_KEYNUM
:
break
;
*
default:
* This information is used by ACL, Cluster and the `COMMAND` command.
serverLog
(
LL_WARNING
,
*
"Invalid command info: key_specs[%zd].find_keys_type: "
* There are two steps to retrieve the key arguments:
"Invalid value %d"
,
j
,
spec
->
find_keys_type
);
*
return
0
;
* - `begin_search` (BS): in which index should we start seacrhing for keys?
}
* - `find_keys` (FK): relative to the output of BS, how can we will which args are keys?
}
*
}
* There are two types of BS:
*
* - `index`: key args start at a constant index
* - `keyword`: key args start just after a specific keyword
*
* There are two kinds of FK:
*
* - `range`: keys end at a specific index (or relative to the last argument)
* - `keynum`: there's an arg that contains the number of key args somewhere before the keys themselves
*
* This function adds a new key spec to a command, returning a unique id in `spec_id`.
* The caller must then call one of the RedisModule_SetCommandKeySpecBeginSearch* APIs
* followed by one of the RedisModule_SetCommandKeySpecFindKeys* APIs.
*
* It should be called just after RedisModule_CreateCommand.
*
* Example:
*
* if (RedisModule_CreateCommand(ctx,"kspec.smove",kspec_legacy,"",0,0,0) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
*
* if (RedisModule_AddCommandKeySpec(ctx,"kspec.smove","RW access delete",&spec_id) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
* if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"kspec.smove",spec_id,1) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
* if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.smove",spec_id,0,1,0) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
*
* if (RedisModule_AddCommandKeySpec(ctx,"kspec.smove","RW insert",&spec_id) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
* if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"kspec.smove",spec_id,2) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
* if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.smove",spec_id,0,1,0) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
*
* It is also possible to use this API on subcommands (See RedisModule_CreateSubcommand).
* The name of the subcommand should be the name of the parent command + "|" + name of subcommand.
*
* Example:
*
* RedisModule_AddCommandKeySpec(ctx,"module.object|encoding","RO",&spec_id)
*
* Returns REDISMODULE_OK on success
*/
int
RM_AddCommandKeySpec
(
RedisModuleCommand
*
command
,
const
char
*
specflags
,
int
*
spec_id
)
{
return
moduleAddCommandKeySpec
(
command
,
specflags
,
spec_id
);
}
/* Set a "index" key arguments spec to a command (begin_search step).
/* Args, subargs (recursive) */
* See RedisModule_AddCommandKeySpec's doc.
return
moduleValidateCommandArgs
(
info
->
args
,
version
);
*
}
* - `index`: The index from which we start the search for keys
*
/* When from_api is true, converts from REDISMODULE_CMD_KEY_* flags to CMD_KEY_* flags.
* Returns REDISMODULE_OK */
* When from_api is false, converts from CMD_KEY_* flags to REDISMODULE_CMD_KEY_* flags. */
int
RM_SetCommandKeySpecBeginSearchIndex
(
RedisModuleCommand
*
command
,
int
spec_id
,
int
index
)
{
static
int64_t
moduleConvertKeySpecsFlags
(
int64_t
flags
,
int
from_api
)
{
keySpec
spec
;
int64_t
out
=
0
;
spec
.
begin_search_type
=
KSPEC_BS_INDEX
;
int64_t
map
[][
2
]
=
{
spec
.
bs
.
index
.
pos
=
index
;
{
REDISMODULE_CMD_KEY_RO
,
CMD_KEY_RO
},
{
REDISMODULE_CMD_KEY_RW
,
CMD_KEY_RW
},
{
REDISMODULE_CMD_KEY_OW
,
CMD_KEY_OW
},
{
REDISMODULE_CMD_KEY_RM
,
CMD_KEY_RM
},
{
REDISMODULE_CMD_KEY_ACCESS
,
CMD_KEY_ACCESS
},
{
REDISMODULE_CMD_KEY_INSERT
,
CMD_KEY_INSERT
},
{
REDISMODULE_CMD_KEY_UPDATE
,
CMD_KEY_UPDATE
},
{
REDISMODULE_CMD_KEY_DELETE
,
CMD_KEY_DELETE
},
{
REDISMODULE_CMD_KEY_NOT_KEY
,
CMD_KEY_NOT_KEY
},
{
REDISMODULE_CMD_KEY_INCOMPLETE
,
CMD_KEY_INCOMPLETE
},
{
REDISMODULE_CMD_KEY_VARIABLE_FLAGS
,
CMD_KEY_VARIABLE_FLAGS
},
{
0
,
0
}};
int
from_idx
=
from_api
?
0
:
1
,
to_idx
=
!
from_idx
;
for
(
int
i
=
0
;
map
[
i
][
0
];
i
++
)
if
(
flags
&
map
[
i
][
from_idx
])
out
|=
map
[
i
][
to_idx
];
return
out
;
}
/* Validates an array of RedisModuleCommandArg. Returns 1 if it's valid and 0 if
* it's invalid. */
static
int
moduleValidateCommandArgs
(
RedisModuleCommandArg
*
args
,
const
RedisModuleCommandInfoVersion
*
version
)
{
if
(
args
==
NULL
)
return
1
;
/* Missing args is OK. */
for
(
size_t
j
=
0
;
moduleCmdArgAt
(
version
,
args
,
j
)
->
name
!=
NULL
;
j
++
)
{
RedisModuleCommandArg
*
arg
=
moduleCmdArgAt
(
version
,
args
,
j
);
int
arg_type_error
=
0
;
moduleConvertArgType
(
arg
->
type
,
&
arg_type_error
);
if
(
arg_type_error
)
{
serverLog
(
LL_WARNING
,
"Invalid command info: Argument
\"
%s
\"
: Undefined type %d"
,
arg
->
name
,
arg
->
type
);
return
0
;
}
if
(
arg
->
type
==
REDISMODULE_ARG_TYPE_PURE_TOKEN
&&
!
arg
->
token
)
{
serverLog
(
LL_WARNING
,
"Invalid command info: Argument
\"
%s
\"
: "
"token required when type is PURE_TOKEN"
,
args
[
j
].
name
);
return
0
;
}
return
moduleSetCommandKeySpecBeginSearch
(
command
,
spec_id
,
&
spec
);
if
(
arg
->
type
==
REDISMODULE_ARG_TYPE_KEY
)
{
}
if
(
arg
->
key_spec_index
<
0
)
{
serverLog
(
LL_WARNING
,
"Invalid command info: Argument
\"
%s
\"
: "
"key_spec_index required when type is KEY"
,
arg
->
name
);
return
0
;
}
}
else
if
(
arg
->
key_spec_index
!=
-
1
&&
arg
->
key_spec_index
!=
0
)
{
/* 0 is allowed for convenience, to allow it to be omitted in
* compound struct literals on the form `.field = value`. */
serverLog
(
LL_WARNING
,
"Invalid command info: Argument
\"
%s
\"
: "
"key_spec_index specified but type isn't KEY"
,
arg
->
name
);
return
0
;
}
/* Set a "keyword" key arguments spec to a command (begin_search step).
if
(
arg
->
flags
&
~
(
_REDISMODULE_CMD_ARG_NEXT
-
1
))
{
* See RedisModule_AddCommandKeySpec's doc.
serverLog
(
LL_WARNING
,
*
"Invalid command info: Argument
\"
%s
\"
: Invalid flags"
,
* - `keyword`: The keyword that indicates the beginning of key args
arg
->
name
);
* - `startfrom`: An index in argv from which to start searching.
return
0
;
* Can be negative, which means start search from the end, in reverse
}
* (Example: -2 means to start in reverse from the panultimate arg)
*
* Returns REDISMODULE_OK */
int
RM_SetCommandKeySpecBeginSearchKeyword
(
RedisModuleCommand
*
command
,
int
spec_id
,
const
char
*
keyword
,
int
startfrom
)
{
keySpec
spec
;
spec
.
begin_search_type
=
KSPEC_BS_KEYWORD
;
spec
.
bs
.
keyword
.
keyword
=
keyword
;
spec
.
bs
.
keyword
.
startfrom
=
startfrom
;
return
moduleSetCommandKeySpecBeginSearch
(
command
,
spec_id
,
&
spec
);
if
(
arg
->
type
==
REDISMODULE_ARG_TYPE_ONEOF
||
arg
->
type
==
REDISMODULE_ARG_TYPE_BLOCK
)
{
if
(
arg
->
subargs
==
NULL
)
{
serverLog
(
LL_WARNING
,
"Invalid command info: Argument
\"
%s
\"
: "
"subargs required when type is ONEOF or BLOCK"
,
arg
->
name
);
return
0
;
}
if
(
!
moduleValidateCommandArgs
(
arg
->
subargs
,
version
))
return
0
;
}
else
{
if
(
arg
->
subargs
!=
NULL
)
{
serverLog
(
LL_WARNING
,
"Invalid command info: Argument
\"
%s
\"
: "
"subargs specified but type isn't ONEOF nor BLOCK"
,
arg
->
name
);
return
0
;
}
}
}
return
1
;
}
}
/* Set a "range" key arguments spec to a command (find_keys step).
/* Converts an array of RedisModuleCommandArg into a freshly allocated array of
* See RedisModule_AddCommandKeySpec's doc.
* struct redisCommandArg. */
*
static
struct
redisCommandArg
*
moduleCopyCommandArgs
(
RedisModuleCommandArg
*
args
,
* - `lastkey`: Relative index (to the result of the begin_search step) where the last key is.
const
RedisModuleCommandInfoVersion
*
version
)
{
* Can be negative, in which case it's not relative. -1 indicating till the last argument,
size_t
count
=
0
;
* -2 one before the last and so on.
while
(
moduleCmdArgAt
(
version
,
args
,
count
)
->
name
)
count
++
;
* - `keystep`: How many args should we skip after finding a key, in order to find the next one.
serverAssert
(
count
<
SIZE_MAX
/
sizeof
(
struct
redisCommandArg
));
* - `limit`: If lastkey is -1, we use limit to stop the search by a factor. 0 and 1 mean no limit.
struct
redisCommandArg
*
realargs
=
zcalloc
((
count
+
1
)
*
sizeof
(
redisCommandArg
));
* 2 means 1/2 of the remaining args, 3 means 1/3, and so on.
*
for
(
size_t
j
=
0
;
j
<
count
;
j
++
)
{
* Returns REDISMODULE_OK */
RedisModuleCommandArg
*
arg
=
moduleCmdArgAt
(
version
,
args
,
j
);
int
RM_SetCommandKeySpecFindKeysRange
(
RedisModuleCommand
*
command
,
int
spec_id
,
int
lastkey
,
int
keystep
,
int
limit
)
{
realargs
[
j
].
name
=
zstrdup
(
arg
->
name
);
keySpec
spec
;
realargs
[
j
].
type
=
moduleConvertArgType
(
arg
->
type
,
NULL
);
spec
.
find_keys_type
=
KSPEC_FK_RANGE
;
if
(
arg
->
type
==
REDISMODULE_ARG_TYPE_KEY
)
spec
.
fk
.
range
.
lastkey
=
lastkey
;
realargs
[
j
].
key_spec_index
=
arg
->
key_spec_index
;
spec
.
fk
.
range
.
keystep
=
keystep
;
else
spec
.
fk
.
range
.
limit
=
limit
;
realargs
[
j
].
key_spec_index
=
-
1
;
if
(
arg
->
token
)
realargs
[
j
].
token
=
zstrdup
(
arg
->
token
);
if
(
arg
->
summary
)
realargs
[
j
].
summary
=
zstrdup
(
arg
->
summary
);
if
(
arg
->
since
)
realargs
[
j
].
since
=
zstrdup
(
arg
->
since
);
realargs
[
j
].
flags
=
moduleConvertArgFlags
(
arg
->
flags
);
if
(
arg
->
subargs
)
realargs
[
j
].
subargs
=
moduleCopyCommandArgs
(
arg
->
subargs
,
version
);
}
return
realargs
;
}
static
redisCommandArgType
moduleConvertArgType
(
RedisModuleCommandArgType
type
,
int
*
error
)
{
if
(
error
)
*
error
=
0
;
switch
(
type
)
{
case
REDISMODULE_ARG_TYPE_STRING
:
return
ARG_TYPE_STRING
;
case
REDISMODULE_ARG_TYPE_INTEGER
:
return
ARG_TYPE_INTEGER
;
case
REDISMODULE_ARG_TYPE_DOUBLE
:
return
ARG_TYPE_DOUBLE
;
case
REDISMODULE_ARG_TYPE_KEY
:
return
ARG_TYPE_KEY
;
case
REDISMODULE_ARG_TYPE_PATTERN
:
return
ARG_TYPE_PATTERN
;
case
REDISMODULE_ARG_TYPE_UNIX_TIME
:
return
ARG_TYPE_UNIX_TIME
;
case
REDISMODULE_ARG_TYPE_PURE_TOKEN
:
return
ARG_TYPE_PURE_TOKEN
;
case
REDISMODULE_ARG_TYPE_ONEOF
:
return
ARG_TYPE_ONEOF
;
case
REDISMODULE_ARG_TYPE_BLOCK
:
return
ARG_TYPE_BLOCK
;
default:
if
(
error
)
*
error
=
1
;
return
-
1
;
}
}
return
moduleSetCommandKeySpecFindKeys
(
command
,
spec_id
,
&
spec
);
static
int
moduleConvertArgFlags
(
int
flags
)
{
int
realflags
=
0
;
if
(
flags
&
REDISMODULE_CMD_ARG_OPTIONAL
)
realflags
|=
CMD_ARG_OPTIONAL
;
if
(
flags
&
REDISMODULE_CMD_ARG_MULTIPLE
)
realflags
|=
CMD_ARG_MULTIPLE
;
if
(
flags
&
REDISMODULE_CMD_ARG_MULTIPLE_TOKEN
)
realflags
|=
CMD_ARG_MULTIPLE_TOKEN
;
return
realflags
;
}
}
/* Set a "keynum" key arguments spec to a command (find_keys step).
/* Return `struct RedisModule *` as `void *` to avoid exposing it outside of module.c. */
* See RedisModule_AddCommandKeySpec's doc.
void
*
moduleGetHandleByName
(
char
*
modulename
)
{
*
return
dictFetchValue
(
modules
,
modulename
);
* - `keynumidx`: Relative index (to the result of the begin_search step) where the arguments that
}
* contains the number of keys is.
* - `firstkey`: Relative index (to the result of the begin_search step) where the first key is
* found (Usually it's just after keynumidx, so it should be keynumidx+1)
* - `keystep`: How many args should we skip after finding a key, in order to find the next one.
*
* Returns REDISMODULE_OK */
int
RM_SetCommandKeySpecFindKeysKeynum
(
RedisModuleCommand
*
command
,
int
spec_id
,
int
keynumidx
,
int
firstkey
,
int
keystep
)
{
keySpec
spec
;
spec
.
find_keys_type
=
KSPEC_FK_KEYNUM
;
spec
.
fk
.
keynum
.
keynumidx
=
keynumidx
;
spec
.
fk
.
keynum
.
firstkey
=
firstkey
;
spec
.
fk
.
keynum
.
keystep
=
keystep
;
return
moduleSetCommandKeySpecFindKeys
(
command
,
spec_id
,
&
spec
);
/* Returns 1 if `cmd` is a command of the module `modulename`. 0 otherwise. */
int
moduleIsModuleCommand
(
void
*
module_handle
,
struct
redisCommand
*
cmd
)
{
if
(
cmd
->
proc
!=
RedisModuleCommandDispatcher
)
return
0
;
if
(
module_handle
==
NULL
)
return
0
;
RedisModuleCommand
*
cp
=
(
void
*
)(
unsigned
long
)
cmd
->
getkeys_proc
;
return
(
cp
->
module
==
module_handle
);
}
}
/* --------------------------------------------------------------------------
/* --------------------------------------------------------------------------
...
@@ -2399,6 +2997,15 @@ int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) {
...
@@ -2399,6 +2997,15 @@ int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) {
size_t
proto_len
;
size_t
proto_len
;
const
char
*
proto
=
callReplyGetProto
(
reply
,
&
proto_len
);
const
char
*
proto
=
callReplyGetProto
(
reply
,
&
proto_len
);
addReplyProto
(
c
,
proto
,
proto_len
);
addReplyProto
(
c
,
proto
,
proto_len
);
/* Propagate the error list from that reply to the other client, to do some
* post error reply handling, like statistics.
* Note that if the original reply had an array with errors, and the module
* replied with just a portion of the original reply, and not the entire
* reply, the errors are currently not propagated and the errors stats
* will not get propagated. */
list
*
errors
=
callReplyDeferredErrorList
(
reply
);
if
(
errors
)
deferredAfterErrorReply
(
c
,
errors
);
return
REDISMODULE_OK
;
return
REDISMODULE_OK
;
}
}
...
@@ -5051,7 +5658,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
...
@@ -5051,7 +5658,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
errno
=
ENOENT
;
errno
=
ENOENT
;
goto
cleanup
;
goto
cleanup
;
}
}
c
->
cmd
=
c
->
lastcmd
=
cmd
;
c
->
cmd
=
c
->
lastcmd
=
c
->
realcmd
=
cmd
;
/* Basic arity checks. */
/* Basic arity checks. */
if
((
cmd
->
arity
>
0
&&
cmd
->
arity
!=
argc
)
||
(
argc
<
-
cmd
->
arity
))
{
if
((
cmd
->
arity
>
0
&&
cmd
->
arity
!=
argc
)
||
(
argc
<
-
cmd
->
arity
))
{
...
@@ -5135,7 +5742,8 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
...
@@ -5135,7 +5742,8 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
proto
=
sdscatlen
(
proto
,
o
->
buf
,
o
->
used
);
proto
=
sdscatlen
(
proto
,
o
->
buf
,
o
->
used
);
listDelNode
(
c
->
reply
,
listFirst
(
c
->
reply
));
listDelNode
(
c
->
reply
,
listFirst
(
c
->
reply
));
}
}
reply
=
callReplyCreate
(
proto
,
ctx
);
reply
=
callReplyCreate
(
proto
,
c
->
deferred_reply_errors
,
ctx
);
c
->
deferred_reply_errors
=
NULL
;
/* now the responsibility of the reply object. */
autoMemoryAdd
(
ctx
,
REDISMODULE_AM_REPLY
,
reply
);
autoMemoryAdd
(
ctx
,
REDISMODULE_AM_REPLY
,
reply
);
cleanup:
cleanup:
...
@@ -6572,6 +7180,7 @@ void moduleHandleBlockedClients(void) {
...
@@ -6572,6 +7180,7 @@ void moduleHandleBlockedClients(void) {
* was blocked on keys (RM_BlockClientOnKeys()), because we already
* was blocked on keys (RM_BlockClientOnKeys()), because we already
* called such callback in moduleTryServeClientBlockedOnKey() when
* called such callback in moduleTryServeClientBlockedOnKey() when
* the key was signaled as ready. */
* the key was signaled as ready. */
long
long
prev_error_replies
=
server
.
stat_total_error_replies
;
uint64_t
reply_us
=
0
;
uint64_t
reply_us
=
0
;
if
(
c
&&
!
bc
->
blocked_on_keys
&&
bc
->
reply_callback
)
{
if
(
c
&&
!
bc
->
blocked_on_keys
&&
bc
->
reply_callback
)
{
RedisModuleCtx
ctx
;
RedisModuleCtx
ctx
;
...
@@ -6586,13 +7195,6 @@ void moduleHandleBlockedClients(void) {
...
@@ -6586,13 +7195,6 @@ void moduleHandleBlockedClients(void) {
reply_us
=
elapsedUs
(
replyTimer
);
reply_us
=
elapsedUs
(
replyTimer
);
moduleFreeContext
(
&
ctx
);
moduleFreeContext
(
&
ctx
);
}
}
/* Update stats now that we've finished the blocking operation.
* This needs to be out of the reply callback above given that a
* module might not define any callback and still do blocking ops.
*/
if
(
c
&&
!
bc
->
blocked_on_keys
)
{
updateStatsOnUnblock
(
c
,
bc
->
background_duration
,
reply_us
);
}
/* Free privdata if any. */
/* Free privdata if any. */
if
(
bc
->
privdata
&&
bc
->
free_privdata
)
{
if
(
bc
->
privdata
&&
bc
->
free_privdata
)
{
...
@@ -6613,6 +7215,14 @@ void moduleHandleBlockedClients(void) {
...
@@ -6613,6 +7215,14 @@ void moduleHandleBlockedClients(void) {
moduleReleaseTempClient
(
bc
->
reply_client
);
moduleReleaseTempClient
(
bc
->
reply_client
);
moduleReleaseTempClient
(
bc
->
thread_safe_ctx_client
);
moduleReleaseTempClient
(
bc
->
thread_safe_ctx_client
);
/* Update stats now that we've finished the blocking operation.
* This needs to be out of the reply callback above given that a
* module might not define any callback and still do blocking ops.
*/
if
(
c
&&
!
bc
->
blocked_on_keys
)
{
updateStatsOnUnblock
(
c
,
bc
->
background_duration
,
reply_us
,
server
.
stat_total_error_replies
!=
prev_error_replies
);
}
if
(
c
!=
NULL
)
{
if
(
c
!=
NULL
)
{
/* Before unblocking the client, set the disconnect callback
/* Before unblocking the client, set the disconnect callback
* to NULL, because if we reached this point, the client was
* to NULL, because if we reached this point, the client was
...
@@ -6670,10 +7280,11 @@ void moduleBlockedClientTimedOut(client *c) {
...
@@ -6670,10 +7280,11 @@ void moduleBlockedClientTimedOut(client *c) {
ctx
.
client
=
bc
->
client
;
ctx
.
client
=
bc
->
client
;
ctx
.
blocked_client
=
bc
;
ctx
.
blocked_client
=
bc
;
ctx
.
blocked_privdata
=
bc
->
privdata
;
ctx
.
blocked_privdata
=
bc
->
privdata
;
long
long
prev_error_replies
=
server
.
stat_total_error_replies
;
bc
->
timeout_callback
(
&
ctx
,(
void
**
)
c
->
argv
,
c
->
argc
);
bc
->
timeout_callback
(
&
ctx
,(
void
**
)
c
->
argv
,
c
->
argc
);
moduleFreeContext
(
&
ctx
);
moduleFreeContext
(
&
ctx
);
if
(
!
bc
->
blocked_on_keys
)
{
if
(
!
bc
->
blocked_on_keys
)
{
updateStatsOnUnblock
(
c
,
bc
->
background_duration
,
0
);
updateStatsOnUnblock
(
c
,
bc
->
background_duration
,
0
,
server
.
stat_total_error_replies
!=
prev_error_replies
);
}
}
/* For timeout events, we do not want to call the disconnect callback,
/* For timeout events, we do not want to call the disconnect callback,
* because the blocked client will be automatically disconnected in
* because the blocked client will be automatically disconnected in
...
@@ -7427,6 +8038,24 @@ int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remain
...
@@ -7427,6 +8038,24 @@ int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remain
return
REDISMODULE_OK
;
return
REDISMODULE_OK
;
}
}
/* Query timers to see if any timer belongs to the module.
* Return 1 if any timer was found, otherwise 0 would be returned. */
int
moduleHoldsTimer
(
struct
RedisModule
*
module
)
{
raxIterator
iter
;
int
found
=
0
;
raxStart
(
&
iter
,
Timers
);
raxSeek
(
&
iter
,
"^"
,
NULL
,
0
);
while
(
raxNext
(
&
iter
))
{
RedisModuleTimer
*
timer
=
iter
.
data
;
if
(
timer
->
module
==
module
)
{
found
=
1
;
break
;
}
}
raxStop
(
&
iter
);
return
found
;
}
/* --------------------------------------------------------------------------
/* --------------------------------------------------------------------------
* ## Modules EventLoop API
* ## Modules EventLoop API
* --------------------------------------------------------------------------*/
* --------------------------------------------------------------------------*/
...
@@ -7808,28 +8437,34 @@ int RM_ACLCheckCommandPermissions(RedisModuleUser *user, RedisModuleString **arg
...
@@ -7808,28 +8437,34 @@ int RM_ACLCheckCommandPermissions(RedisModuleUser *user, RedisModuleString **arg
return
REDISMODULE_OK
;
return
REDISMODULE_OK
;
}
}
/* Check if the key can be accessed by the user, according to the ACLs associated with it
/* Check if the key can be accessed by the user according to the ACLs attached to the user
* and the flags used. The supported flags are:
* and the flags representing the key access. The flags are the same that are used in the
*
* keyspec for logical operations. These flags are documented in RedisModule_SetCommandInfo as
* REDISMODULE_KEY_PERMISSION_READ: Can the module read data from the key.
* the REDISMODULE_CMD_KEY_ACCESS, REDISMODULE_CMD_KEY_UPDATE, REDISMODULE_CMD_KEY_INSERT,
* REDISMODULE_KEY_PERMISSION_WRITE: Can the module write data to the key.
* and REDISMODULE_CMD_KEY_DELETE flags.
*
* If no flags are supplied, the user is still required to have some access to the key for
* this command to return successfully.
*
*
*
On success a
REDISMODULE_OK is returned, otherwise
*
If the user is able to access the key then
REDISMODULE_OK is returned, otherwise
* REDISMODULE_ERR is returned and errno is set to the following values:
* REDISMODULE_ERR is returned and errno is set to
one of
the following values:
*
*
* * EINVAL: The provided flags are invalid.
* * EINVAL: The provided flags are invalid.
* * EACCESS: The user does not have permission to access the key.
* * EACCESS: The user does not have permission to access the key.
*/
*/
int
RM_ACLCheckKeyPermissions
(
RedisModuleUser
*
user
,
RedisModuleString
*
key
,
int
flags
)
{
int
RM_ACLCheckKeyPermissions
(
RedisModuleUser
*
user
,
RedisModuleString
*
key
,
int
flags
)
{
int
acl_flags
=
0
;
const
int
allow_mask
=
(
REDISMODULE_CMD_KEY_ACCESS
if
(
flags
&
REDISMODULE_KEY_PERMISSION_READ
)
acl_flags
|=
ACL_READ_PERMISSION
;
|
REDISMODULE_CMD_KEY_INSERT
if
(
flags
&
REDISMODULE_KEY_PERMISSION_WRITE
)
acl_flags
|=
ACL_WRITE_PERMISSION
;
|
REDISMODULE_CMD_KEY_DELETE
if
(
!
acl_flags
||
((
flags
&
REDISMODULE_KEY_PERMISSION_ALL
)
!=
flags
))
{
|
REDISMODULE_CMD_KEY_UPDATE
);
if
((
flags
&
allow_mask
)
!=
flags
)
{
errno
=
EINVAL
;
errno
=
EINVAL
;
return
REDISMODULE_ERR
;
return
REDISMODULE_ERR
;
}
}
if
(
ACLUserCheckKeyPerm
(
user
->
user
,
key
->
ptr
,
sdslen
(
key
->
ptr
),
acl_flags
)
!=
ACL_OK
)
{
int
keyspec_flags
=
moduleConvertKeySpecsFlags
(
flags
,
0
);
if
(
ACLUserCheckKeyPerm
(
user
->
user
,
key
->
ptr
,
sdslen
(
key
->
ptr
),
keyspec_flags
)
!=
ACL_OK
)
{
errno
=
EACCES
;
errno
=
EACCES
;
return
REDISMODULE_ERR
;
return
REDISMODULE_ERR
;
}
}
...
@@ -7837,14 +8472,34 @@ int RM_ACLCheckKeyPermissions(RedisModuleUser *user, RedisModuleString *key, int
...
@@ -7837,14 +8472,34 @@ int RM_ACLCheckKeyPermissions(RedisModuleUser *user, RedisModuleString *key, int
return
REDISMODULE_OK
;
return
REDISMODULE_OK
;
}
}
/* Check if the pubsub channel can be accessed by the user
, according to the ACLs associated with it.
/* Check if the pubsub channel can be accessed by the user
based off of the given
*
Glob-style pattern matching is employed, unless the literal flag is
*
access flags. See RM_ChannelAtPosWithFlags for more information about the
*
set
.
*
possible flags that can be passed in
.
*
*
* If the user can access the pubsub channel, REDISMODULE_OK is returned, otherwise
* If the user is able to acecss the pubsub channel then REDISMODULE_OK is returned, otherwise
* REDISMODULE_ERR is returned. */
* REDISMODULE_ERR is returned and errno is set to one of the following values:
int
RM_ACLCheckChannelPermissions
(
RedisModuleUser
*
user
,
RedisModuleString
*
ch
,
int
literal
)
{
*
if
(
ACLUserCheckChannelPerm
(
user
->
user
,
ch
->
ptr
,
literal
)
!=
ACL_OK
)
* * EINVAL: The provided flags are invalid.
* * EACCESS: The user does not have permission to access the pubsub channel.
*/
int
RM_ACLCheckChannelPermissions
(
RedisModuleUser
*
user
,
RedisModuleString
*
ch
,
int
flags
)
{
const
int
allow_mask
=
(
REDISMODULE_CMD_CHANNEL_PUBLISH
|
REDISMODULE_CMD_CHANNEL_SUBSCRIBE
|
REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE
|
REDISMODULE_CMD_CHANNEL_PATTERN
);
if
((
flags
&
allow_mask
)
!=
flags
)
{
errno
=
EINVAL
;
return
REDISMODULE_ERR
;
}
/* Unsubscribe permissions are currently always allowed. */
if
(
flags
&
REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE
){
return
REDISMODULE_OK
;
}
int
is_pattern
=
flags
&
REDISMODULE_CMD_CHANNEL_PATTERN
;
if
(
ACLUserCheckChannelPerm
(
user
->
user
,
ch
->
ptr
,
is_pattern
)
!=
ACL_OK
)
return
REDISMODULE_ERR
;
return
REDISMODULE_ERR
;
return
REDISMODULE_OK
;
return
REDISMODULE_OK
;
...
@@ -8254,9 +8909,10 @@ int RM_InfoAddSection(RedisModuleInfoCtx *ctx, const char *name) {
...
@@ -8254,9 +8909,10 @@ int RM_InfoAddSection(RedisModuleInfoCtx *ctx, const char *name) {
* 1) no section was requested (emit all)
* 1) no section was requested (emit all)
* 2) the module name was requested (emit all)
* 2) the module name was requested (emit all)
* 3) this specific section was requested. */
* 3) this specific section was requested. */
if
(
ctx
->
requested_section
)
{
if
(
ctx
->
requested_sections
)
{
if
(
strcasecmp
(
ctx
->
requested_section
,
full_name
)
&&
if
((
!
full_name
||
!
dictFind
(
ctx
->
requested_sections
,
full_name
))
&&
strcasecmp
(
ctx
->
requested_section
,
ctx
->
module
->
name
))
{
(
!
dictFind
(
ctx
->
requested_sections
,
ctx
->
module
->
name
)))
{
sdsfree
(
full_name
);
sdsfree
(
full_name
);
ctx
->
in_section
=
0
;
ctx
->
in_section
=
0
;
return
REDISMODULE_ERR
;
return
REDISMODULE_ERR
;
...
@@ -8405,7 +9061,7 @@ int RM_RegisterInfoFunc(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) {
...
@@ -8405,7 +9061,7 @@ int RM_RegisterInfoFunc(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) {
return
REDISMODULE_OK
;
return
REDISMODULE_OK
;
}
}
sds
modulesCollectInfo
(
sds
info
,
const
char
*
section
,
int
for_crash_report
,
int
sections
)
{
sds
modulesCollectInfo
(
sds
info
,
dict
*
section
s_dict
,
int
for_crash_report
,
int
sections
)
{
dictIterator
*
di
=
dictGetIterator
(
modules
);
dictIterator
*
di
=
dictGetIterator
(
modules
);
dictEntry
*
de
;
dictEntry
*
de
;
...
@@ -8413,7 +9069,7 @@ sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int
...
@@ -8413,7 +9069,7 @@ sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int
struct
RedisModule
*
module
=
dictGetVal
(
de
);
struct
RedisModule
*
module
=
dictGetVal
(
de
);
if
(
!
module
->
info_cb
)
if
(
!
module
->
info_cb
)
continue
;
continue
;
RedisModuleInfoCtx
info_ctx
=
{
module
,
section
,
info
,
sections
,
0
,
0
};
RedisModuleInfoCtx
info_ctx
=
{
module
,
section
s_dict
,
info
,
sections
,
0
,
0
};
module
->
info_cb
(
&
info_ctx
,
for_crash_report
);
module
->
info_cb
(
&
info_ctx
,
for_crash_report
);
/* Implicitly end dicts (no way to handle errors, and we must add the newline). */
/* Implicitly end dicts (no way to handle errors, and we must add the newline). */
if
(
info_ctx
.
in_dict_field
)
if
(
info_ctx
.
in_dict_field
)
...
@@ -8435,7 +9091,11 @@ RedisModuleServerInfoData *RM_GetServerInfo(RedisModuleCtx *ctx, const char *sec
...
@@ -8435,7 +9091,11 @@ RedisModuleServerInfoData *RM_GetServerInfo(RedisModuleCtx *ctx, const char *sec
struct
RedisModuleServerInfoData
*
d
=
zmalloc
(
sizeof
(
*
d
));
struct
RedisModuleServerInfoData
*
d
=
zmalloc
(
sizeof
(
*
d
));
d
->
rax
=
raxNew
();
d
->
rax
=
raxNew
();
if
(
ctx
!=
NULL
)
autoMemoryAdd
(
ctx
,
REDISMODULE_AM_INFO
,
d
);
if
(
ctx
!=
NULL
)
autoMemoryAdd
(
ctx
,
REDISMODULE_AM_INFO
,
d
);
sds
info
=
genRedisInfoString
(
section
);
int
all
=
0
,
everything
=
0
;
robj
*
argv
[
1
];
argv
[
0
]
=
section
?
createStringObject
(
section
,
strlen
(
section
))
:
NULL
;
dict
*
section_dict
=
genInfoSectionDict
(
argv
,
section
?
1
:
0
,
NULL
,
&
all
,
&
everything
);
sds
info
=
genRedisInfoString
(
section_dict
,
all
,
everything
);
int
totlines
,
i
;
int
totlines
,
i
;
sds
*
lines
=
sdssplitlen
(
info
,
sdslen
(
info
),
"
\r\n
"
,
2
,
&
totlines
);
sds
*
lines
=
sdssplitlen
(
info
,
sdslen
(
info
),
"
\r\n
"
,
2
,
&
totlines
);
for
(
i
=
0
;
i
<
totlines
;
i
++
)
{
for
(
i
=
0
;
i
<
totlines
;
i
++
)
{
...
@@ -8451,6 +9111,8 @@ RedisModuleServerInfoData *RM_GetServerInfo(RedisModuleCtx *ctx, const char *sec
...
@@ -8451,6 +9111,8 @@ RedisModuleServerInfoData *RM_GetServerInfo(RedisModuleCtx *ctx, const char *sec
}
}
sdsfree
(
info
);
sdsfree
(
info
);
sdsfreesplitres
(
lines
,
totlines
);
sdsfreesplitres
(
lines
,
totlines
);
releaseInfoSectionDict
(
section_dict
);
if
(
argv
[
0
])
decrRefCount
(
argv
[
0
]);
return
d
;
return
d
;
}
}
...
@@ -9995,17 +10657,23 @@ int moduleFreeCommand(struct RedisModule *module, struct redisCommand *cmd) {
...
@@ -9995,17 +10657,23 @@ int moduleFreeCommand(struct RedisModule *module, struct redisCommand *cmd) {
return
C_ERR
;
return
C_ERR
;
/* Free everything except cmd->fullname and cmd itself. */
/* Free everything except cmd->fullname and cmd itself. */
for
(
int
j
=
0
;
j
<
cmd
->
key_specs_num
;
j
++
)
{
if
(
cmd
->
key_specs
[
j
].
notes
)
zfree
((
char
*
)
cmd
->
key_specs
[
j
].
notes
);
if
(
cmd
->
key_specs
[
j
].
begin_search_type
==
KSPEC_BS_KEYWORD
)
zfree
((
char
*
)
cmd
->
key_specs
[
j
].
bs
.
keyword
.
keyword
);
}
if
(
cmd
->
key_specs
!=
cmd
->
key_specs_static
)
if
(
cmd
->
key_specs
!=
cmd
->
key_specs_static
)
zfree
(
cmd
->
key_specs
);
zfree
(
cmd
->
key_specs
);
for
(
int
j
=
0
;
cmd
->
tips
&&
cmd
->
tips
[
j
];
j
++
)
for
(
int
j
=
0
;
cmd
->
tips
&&
cmd
->
tips
[
j
];
j
++
)
sds
free
((
sds
)
cmd
->
tips
[
j
]);
z
free
((
char
*
)
cmd
->
tips
[
j
]);
for
(
int
j
=
0
;
cmd
->
history
&&
cmd
->
history
[
j
].
since
;
j
++
)
{
for
(
int
j
=
0
;
cmd
->
history
&&
cmd
->
history
[
j
].
since
;
j
++
)
{
sds
free
((
sds
)
cmd
->
history
[
j
].
since
);
z
free
((
char
*
)
cmd
->
history
[
j
].
since
);
sds
free
((
sds
)
cmd
->
history
[
j
].
changes
);
z
free
((
char
*
)
cmd
->
history
[
j
].
changes
);
}
}
sds
free
((
sds
)
cmd
->
summary
);
z
free
((
char
*
)
cmd
->
summary
);
sds
free
((
sds
)
cmd
->
since
);
z
free
((
char
*
)
cmd
->
since
);
sds
free
((
sds
)
cmd
->
complexity
);
z
free
((
char
*
)
cmd
->
complexity
);
if
(
cmd
->
latency_histogram
)
{
if
(
cmd
->
latency_histogram
)
{
hdr_close
(
cmd
->
latency_histogram
);
hdr_close
(
cmd
->
latency_histogram
);
cmd
->
latency_histogram
=
NULL
;
cmd
->
latency_histogram
=
NULL
;
...
@@ -10125,6 +10793,7 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
...
@@ -10125,6 +10793,7 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
* * EBUSY: The module exports a new data type and can only be reloaded.
* * EBUSY: The module exports a new data type and can only be reloaded.
* * EPERM: The module exports APIs which are used by other module.
* * EPERM: The module exports APIs which are used by other module.
* * EAGAIN: The module has blocked clients.
* * EAGAIN: The module has blocked clients.
* * EINPROGRESS: The module holds timer not fired.
* * ECANCELED: Unload module error. */
* * ECANCELED: Unload module error. */
int
moduleUnload
(
sds
name
)
{
int
moduleUnload
(
sds
name
)
{
struct
RedisModule
*
module
=
dictFetchValue
(
modules
,
name
);
struct
RedisModule
*
module
=
dictFetchValue
(
modules
,
name
);
...
@@ -10141,6 +10810,9 @@ int moduleUnload(sds name) {
...
@@ -10141,6 +10810,9 @@ int moduleUnload(sds name) {
}
else
if
(
module
->
blocked_clients
)
{
}
else
if
(
module
->
blocked_clients
)
{
errno
=
EAGAIN
;
errno
=
EAGAIN
;
return
C_ERR
;
return
C_ERR
;
}
else
if
(
moduleHoldsTimer
(
module
))
{
errno
=
EINPROGRESS
;
return
C_ERR
;
}
}
/* Give module a chance to clean up. */
/* Give module a chance to clean up. */
...
@@ -10255,6 +10927,8 @@ sds genModulesInfoStringRenderModuleOptions(struct RedisModule *module) {
...
@@ -10255,6 +10927,8 @@ sds genModulesInfoStringRenderModuleOptions(struct RedisModule *module) {
output
=
sdscat
(
output
,
"handle-io-errors|"
);
output
=
sdscat
(
output
,
"handle-io-errors|"
);
if
(
module
->
options
&
REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD
)
if
(
module
->
options
&
REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD
)
output
=
sdscat
(
output
,
"handle-repl-async-load|"
);
output
=
sdscat
(
output
,
"handle-repl-async-load|"
);
if
(
module
->
options
&
REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED
)
output
=
sdscat
(
output
,
"no-implicit-signal-modified|"
);
output
=
sdstrim
(
output
,
"|"
);
output
=
sdstrim
(
output
,
"|"
);
output
=
sdscat
(
output
,
"]"
);
output
=
sdscat
(
output
,
"]"
);
return
output
;
return
output
;
...
@@ -10345,6 +11019,10 @@ NULL
...
@@ -10345,6 +11019,10 @@ NULL
errmsg
=
"the module has blocked clients. "
errmsg
=
"the module has blocked clients. "
"Please wait them unblocked and try again"
;
"Please wait them unblocked and try again"
;
break
;
break
;
case
EINPROGRESS
:
errmsg
=
"the module holds timer that is not fired. "
"Please stop the timer or wait until it fires."
;
break
;
default:
default:
errmsg
=
"operation not possible."
;
errmsg
=
"operation not possible."
;
break
;
break
;
...
@@ -10511,6 +11189,10 @@ int RM_ModuleTypeReplaceValue(RedisModuleKey *key, moduleType *mt, void *new_val
...
@@ -10511,6 +11189,10 @@ int RM_ModuleTypeReplaceValue(RedisModuleKey *key, moduleType *mt, void *new_val
* contains the indexes of all key name arguments. This function is
* contains the indexes of all key name arguments. This function is
* essentially a more efficient way to do `COMMAND GETKEYS`.
* essentially a more efficient way to do `COMMAND GETKEYS`.
*
*
* The out_flags argument is optional, and can be set to NULL.
* When provided it is filled with REDISMODULE_CMD_KEY_ flags in matching
* indexes with the key indexes of the returned array.
*
* A NULL return value indicates the specified command has no keys, or
* A NULL return value indicates the specified command has no keys, or
* an error condition. Error conditions are indicated by setting errno
* an error condition. Error conditions are indicated by setting errno
* as follows:
* as follows:
...
@@ -10520,9 +11202,10 @@ int RM_ModuleTypeReplaceValue(RedisModuleKey *key, moduleType *mt, void *new_val
...
@@ -10520,9 +11202,10 @@ int RM_ModuleTypeReplaceValue(RedisModuleKey *key, moduleType *mt, void *new_val
*
*
* NOTE: The returned array is not a Redis Module object so it does not
* NOTE: The returned array is not a Redis Module object so it does not
* get automatically freed even when auto-memory is used. The caller
* get automatically freed even when auto-memory is used. The caller
* must explicitly call RM_Free() to free it.
* must explicitly call RM_Free() to free it, same as the out_flags pointer if
* used.
*/
*/
int
*
RM_GetCommandKeys
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
,
int
*
num_keys
)
{
int
*
RM_GetCommandKeys
WithFlags
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
,
int
*
num_keys
,
int
**
out_flags
)
{
UNUSED
(
ctx
);
UNUSED
(
ctx
);
struct
redisCommand
*
cmd
;
struct
redisCommand
*
cmd
;
int
*
res
=
NULL
;
int
*
res
=
NULL
;
...
@@ -10557,13 +11240,22 @@ int *RM_GetCommandKeys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc,
...
@@ -10557,13 +11240,22 @@ int *RM_GetCommandKeys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc,
/* The return value here expects an array of key positions */
/* The return value here expects an array of key positions */
unsigned
long
int
size
=
sizeof
(
int
)
*
result
.
numkeys
;
unsigned
long
int
size
=
sizeof
(
int
)
*
result
.
numkeys
;
res
=
zmalloc
(
size
);
res
=
zmalloc
(
size
);
if
(
out_flags
)
*
out_flags
=
zmalloc
(
size
);
for
(
int
i
=
0
;
i
<
result
.
numkeys
;
i
++
)
{
for
(
int
i
=
0
;
i
<
result
.
numkeys
;
i
++
)
{
res
[
i
]
=
result
.
keys
[
i
].
pos
;
res
[
i
]
=
result
.
keys
[
i
].
pos
;
if
(
out_flags
)
(
*
out_flags
)[
i
]
=
moduleConvertKeySpecsFlags
(
result
.
keys
[
i
].
flags
,
0
);
}
}
return
res
;
return
res
;
}
}
/* Identinal to RM_GetCommandKeysWithFlags when flags are not needed. */
int
*
RM_GetCommandKeys
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
,
int
*
num_keys
)
{
return
RM_GetCommandKeysWithFlags
(
ctx
,
argv
,
argc
,
num_keys
,
NULL
);
}
/* Return the name of the command currently running */
/* Return the name of the command currently running */
const
char
*
RM_GetCurrentCommandName
(
RedisModuleCtx
*
ctx
)
{
const
char
*
RM_GetCurrentCommandName
(
RedisModuleCtx
*
ctx
)
{
if
(
!
ctx
||
!
ctx
->
client
||
!
ctx
->
client
->
cmd
)
if
(
!
ctx
||
!
ctx
->
client
||
!
ctx
->
client
->
cmd
)
...
@@ -10803,6 +11495,7 @@ void moduleRegisterCoreAPI(void) {
...
@@ -10803,6 +11495,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API
(
CreateCommand
);
REGISTER_API
(
CreateCommand
);
REGISTER_API
(
GetCommand
);
REGISTER_API
(
GetCommand
);
REGISTER_API
(
CreateSubcommand
);
REGISTER_API
(
CreateSubcommand
);
REGISTER_API
(
SetCommandInfo
);
REGISTER_API
(
SetModuleAttribs
);
REGISTER_API
(
SetModuleAttribs
);
REGISTER_API
(
IsModuleNameBusy
);
REGISTER_API
(
IsModuleNameBusy
);
REGISTER_API
(
WrongArity
);
REGISTER_API
(
WrongArity
);
...
@@ -10913,6 +11606,9 @@ void moduleRegisterCoreAPI(void) {
...
@@ -10913,6 +11606,9 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API
(
StreamTrimByID
);
REGISTER_API
(
StreamTrimByID
);
REGISTER_API
(
IsKeysPositionRequest
);
REGISTER_API
(
IsKeysPositionRequest
);
REGISTER_API
(
KeyAtPos
);
REGISTER_API
(
KeyAtPos
);
REGISTER_API
(
KeyAtPosWithFlags
);
REGISTER_API
(
IsChannelsPositionRequest
);
REGISTER_API
(
ChannelAtPosWithFlags
);
REGISTER_API
(
GetClientId
);
REGISTER_API
(
GetClientId
);
REGISTER_API
(
GetClientUserNameById
);
REGISTER_API
(
GetClientUserNameById
);
REGISTER_API
(
GetContextFlags
);
REGISTER_API
(
GetContextFlags
);
...
@@ -11090,6 +11786,7 @@ void moduleRegisterCoreAPI(void) {
...
@@ -11090,6 +11786,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API
(
GetServerVersion
);
REGISTER_API
(
GetServerVersion
);
REGISTER_API
(
GetClientCertificate
);
REGISTER_API
(
GetClientCertificate
);
REGISTER_API
(
GetCommandKeys
);
REGISTER_API
(
GetCommandKeys
);
REGISTER_API
(
GetCommandKeysWithFlags
);
REGISTER_API
(
GetCurrentCommandName
);
REGISTER_API
(
GetCurrentCommandName
);
REGISTER_API
(
GetTypeMethodVersion
);
REGISTER_API
(
GetTypeMethodVersion
);
REGISTER_API
(
RegisterDefragFunc
);
REGISTER_API
(
RegisterDefragFunc
);
...
@@ -11098,13 +11795,6 @@ void moduleRegisterCoreAPI(void) {
...
@@ -11098,13 +11795,6 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API
(
DefragShouldStop
);
REGISTER_API
(
DefragShouldStop
);
REGISTER_API
(
DefragCursorSet
);
REGISTER_API
(
DefragCursorSet
);
REGISTER_API
(
DefragCursorGet
);
REGISTER_API
(
DefragCursorGet
);
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API
REGISTER_API
(
AddCommandKeySpec
);
REGISTER_API
(
SetCommandKeySpecBeginSearchIndex
);
REGISTER_API
(
SetCommandKeySpecBeginSearchKeyword
);
REGISTER_API
(
SetCommandKeySpecFindKeysRange
);
REGISTER_API
(
SetCommandKeySpecFindKeysKeynum
);
#endif
REGISTER_API
(
EventLoopAdd
);
REGISTER_API
(
EventLoopAdd
);
REGISTER_API
(
EventLoopDel
);
REGISTER_API
(
EventLoopDel
);
REGISTER_API
(
EventLoopAddOneShot
);
REGISTER_API
(
EventLoopAddOneShot
);
...
...
src/modules/Makefile
View file @
a83e3663
...
@@ -11,6 +11,13 @@ else
...
@@ -11,6 +11,13 @@ else
SHOBJ_LDFLAGS
?=
-bundle
-undefined
dynamic_lookup
SHOBJ_LDFLAGS
?=
-bundle
-undefined
dynamic_lookup
endif
endif
# OS X 11.x doesn't have /usr/lib/libSystem.dylib and needs an explicit setting.
ifeq
($(uname_S),Darwin)
ifeq
("$(wildcard /usr/lib/libSystem.dylib)","")
LIBS
=
-L
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib
-lsystem
endif
endif
.SUFFIXES
:
.c .so .xo .o
.SUFFIXES
:
.c .so .xo .o
all
:
helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so
all
:
helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so
...
...
src/modules/hellocluster.c
View file @
a83e3663
...
@@ -76,7 +76,7 @@ int ListCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
...
@@ -76,7 +76,7 @@ int ListCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
void
PingReceiver
(
RedisModuleCtx
*
ctx
,
const
char
*
sender_id
,
uint8_t
type
,
const
unsigned
char
*
payload
,
uint32_t
len
)
{
void
PingReceiver
(
RedisModuleCtx
*
ctx
,
const
char
*
sender_id
,
uint8_t
type
,
const
unsigned
char
*
payload
,
uint32_t
len
)
{
RedisModule_Log
(
ctx
,
"notice"
,
"PING (type %d) RECEIVED from %.*s: '%.*s'"
,
RedisModule_Log
(
ctx
,
"notice"
,
"PING (type %d) RECEIVED from %.*s: '%.*s'"
,
type
,
REDISMODULE_NODE_ID_LEN
,
sender_id
,(
int
)
len
,
payload
);
type
,
REDISMODULE_NODE_ID_LEN
,
sender_id
,(
int
)
len
,
payload
);
RedisModule_SendClusterMessage
(
ctx
,
NULL
,
MSGTYPE_PONG
,
(
unsigned
char
*
)
"Ohi!"
,
4
);
RedisModule_SendClusterMessage
(
ctx
,
NULL
,
MSGTYPE_PONG
,
"Ohi!"
,
4
);
RedisModuleCallReply
*
reply
=
RedisModule_Call
(
ctx
,
"INCR"
,
"c"
,
"pings_received"
);
RedisModuleCallReply
*
reply
=
RedisModule_Call
(
ctx
,
"INCR"
,
"c"
,
"pings_received"
);
RedisModule_FreeCallReply
(
reply
);
RedisModule_FreeCallReply
(
reply
);
}
}
...
...
src/multi.c
View file @
a83e3663
...
@@ -189,7 +189,7 @@ void execCommand(client *c) {
...
@@ -189,7 +189,7 @@ void execCommand(client *c) {
c
->
argc
=
c
->
mstate
.
commands
[
j
].
argc
;
c
->
argc
=
c
->
mstate
.
commands
[
j
].
argc
;
c
->
argv
=
c
->
mstate
.
commands
[
j
].
argv
;
c
->
argv
=
c
->
mstate
.
commands
[
j
].
argv
;
c
->
argv_len
=
c
->
mstate
.
commands
[
j
].
argv_len
;
c
->
argv_len
=
c
->
mstate
.
commands
[
j
].
argv_len
;
c
->
cmd
=
c
->
mstate
.
commands
[
j
].
cmd
;
c
->
cmd
=
c
->
realcmd
=
c
->
mstate
.
commands
[
j
].
cmd
;
/* ACL permissions are also checked at the time of execution in case
/* ACL permissions are also checked at the time of execution in case
* they were changed after the commands were queued. */
* they were changed after the commands were queued. */
...
@@ -240,7 +240,7 @@ void execCommand(client *c) {
...
@@ -240,7 +240,7 @@ void execCommand(client *c) {
c
->
argv
=
orig_argv
;
c
->
argv
=
orig_argv
;
c
->
argv_len
=
orig_argv_len
;
c
->
argv_len
=
orig_argv_len
;
c
->
argc
=
orig_argc
;
c
->
argc
=
orig_argc
;
c
->
cmd
=
orig_cmd
;
c
->
cmd
=
c
->
realcmd
=
orig_cmd
;
discardTransaction
(
c
);
discardTransaction
(
c
);
server
.
in_exec
=
0
;
server
.
in_exec
=
0
;
...
@@ -257,10 +257,13 @@ void execCommand(client *c) {
...
@@ -257,10 +257,13 @@ void execCommand(client *c) {
/* In the client->watched_keys list we need to use watchedKey structures
/* In the client->watched_keys list we need to use watchedKey structures
* as in order to identify a key in Redis we need both the key name and the
* as in order to identify a key in Redis we need both the key name and the
* DB */
* DB. This struct is also referenced from db->watched_keys dict, where the
* values are lists of watchedKey pointers. */
typedef
struct
watchedKey
{
typedef
struct
watchedKey
{
robj
*
key
;
robj
*
key
;
redisDb
*
db
;
redisDb
*
db
;
client
*
client
;
unsigned
expired
:
1
;
/* Flag that we're watching an already expired key. */
}
watchedKey
;
}
watchedKey
;
/* Watch for the specified key */
/* Watch for the specified key */
...
@@ -284,13 +287,15 @@ void watchForKey(client *c, robj *key) {
...
@@ -284,13 +287,15 @@ void watchForKey(client *c, robj *key) {
dictAdd
(
c
->
db
->
watched_keys
,
key
,
clients
);
dictAdd
(
c
->
db
->
watched_keys
,
key
,
clients
);
incrRefCount
(
key
);
incrRefCount
(
key
);
}
}
listAddNodeTail
(
clients
,
c
);
/* Add the new key to the list of keys watched by this client */
/* Add the new key to the list of keys watched by this client */
wk
=
zmalloc
(
sizeof
(
*
wk
));
wk
=
zmalloc
(
sizeof
(
*
wk
));
wk
->
key
=
key
;
wk
->
key
=
key
;
wk
->
client
=
c
;
wk
->
db
=
c
->
db
;
wk
->
db
=
c
->
db
;
wk
->
expired
=
keyIsExpired
(
c
->
db
,
key
);
incrRefCount
(
key
);
incrRefCount
(
key
);
listAddNodeTail
(
c
->
watched_keys
,
wk
);
listAddNodeTail
(
c
->
watched_keys
,
wk
);
listAddNodeTail
(
clients
,
wk
);
}
}
/* Unwatch all the keys watched by this client. To clean the EXEC dirty
/* Unwatch all the keys watched by this client. To clean the EXEC dirty
...
@@ -305,12 +310,12 @@ void unwatchAllKeys(client *c) {
...
@@ -305,12 +310,12 @@ void unwatchAllKeys(client *c) {
list
*
clients
;
list
*
clients
;
watchedKey
*
wk
;
watchedKey
*
wk
;
/* Lookup the watched key -> clients list and remove the client
/* Lookup the watched key -> clients list and remove the client
's wk
* from the list */
* from the list */
wk
=
listNodeValue
(
ln
);
wk
=
listNodeValue
(
ln
);
clients
=
dictFetchValue
(
wk
->
db
->
watched_keys
,
wk
->
key
);
clients
=
dictFetchValue
(
wk
->
db
->
watched_keys
,
wk
->
key
);
serverAssertWithInfo
(
c
,
NULL
,
clients
!=
NULL
);
serverAssertWithInfo
(
c
,
NULL
,
clients
!=
NULL
);
listDelNode
(
clients
,
listSearchKey
(
clients
,
c
));
listDelNode
(
clients
,
listSearchKey
(
clients
,
wk
));
/* Kill the entry at all if this was the only client */
/* Kill the entry at all if this was the only client */
if
(
listLength
(
clients
)
==
0
)
if
(
listLength
(
clients
)
==
0
)
dictDelete
(
wk
->
db
->
watched_keys
,
wk
->
key
);
dictDelete
(
wk
->
db
->
watched_keys
,
wk
->
key
);
...
@@ -321,8 +326,8 @@ void unwatchAllKeys(client *c) {
...
@@ -321,8 +326,8 @@ void unwatchAllKeys(client *c) {
}
}
}
}
/*
i
terates over the watched_keys list and
/*
I
terates over the watched_keys list and
looks for an expired key. Keys which
*
look for an expired key
. */
*
were expired already when WATCH was called are ignored
. */
int
isWatchedKeyExpired
(
client
*
c
)
{
int
isWatchedKeyExpired
(
client
*
c
)
{
listIter
li
;
listIter
li
;
listNode
*
ln
;
listNode
*
ln
;
...
@@ -331,6 +336,7 @@ int isWatchedKeyExpired(client *c) {
...
@@ -331,6 +336,7 @@ int isWatchedKeyExpired(client *c) {
listRewind
(
c
->
watched_keys
,
&
li
);
listRewind
(
c
->
watched_keys
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
while
((
ln
=
listNext
(
&
li
)))
{
wk
=
listNodeValue
(
ln
);
wk
=
listNodeValue
(
ln
);
if
(
wk
->
expired
)
continue
;
/* was expired when WATCH was called */
if
(
keyIsExpired
(
wk
->
db
,
wk
->
key
))
return
1
;
if
(
keyIsExpired
(
wk
->
db
,
wk
->
key
))
return
1
;
}
}
...
@@ -352,13 +358,31 @@ void touchWatchedKey(redisDb *db, robj *key) {
...
@@ -352,13 +358,31 @@ void touchWatchedKey(redisDb *db, robj *key) {
/* Check if we are already watching for this key */
/* Check if we are already watching for this key */
listRewind
(
clients
,
&
li
);
listRewind
(
clients
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
while
((
ln
=
listNext
(
&
li
)))
{
client
*
c
=
listNodeValue
(
ln
);
watchedKey
*
wk
=
listNodeValue
(
ln
);
client
*
c
=
wk
->
client
;
if
(
wk
->
expired
)
{
/* The key was already expired when WATCH was called. */
if
(
db
==
wk
->
db
&&
equalStringObjects
(
key
,
wk
->
key
)
&&
dictFind
(
db
->
dict
,
key
->
ptr
)
==
NULL
)
{
/* Already expired key is deleted, so logically no change. Clear
* the flag. Deleted keys are not flagged as expired. */
wk
->
expired
=
0
;
goto
skip_client
;
}
break
;
}
c
->
flags
|=
CLIENT_DIRTY_CAS
;
c
->
flags
|=
CLIENT_DIRTY_CAS
;
/* As the client is marked as dirty, there is no point in getting here
/* As the client is marked as dirty, there is no point in getting here
* again in case that key (or others) are modified again (or keep the
* again in case that key (or others) are modified again (or keep the
* memory overhead till EXEC). */
* memory overhead till EXEC). */
unwatchAllKeys
(
c
);
unwatchAllKeys
(
c
);
skip_client:
continue
;
}
}
}
}
...
@@ -379,14 +403,31 @@ void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) {
...
@@ -379,14 +403,31 @@ void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) {
dictIterator
*
di
=
dictGetSafeIterator
(
emptied
->
watched_keys
);
dictIterator
*
di
=
dictGetSafeIterator
(
emptied
->
watched_keys
);
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
robj
*
key
=
dictGetKey
(
de
);
robj
*
key
=
dictGetKey
(
de
);
if
(
dictFind
(
emptied
->
dict
,
key
->
ptr
)
||
int
exists_in_emptied
=
dictFind
(
emptied
->
dict
,
key
->
ptr
)
!=
NULL
;
if
(
exists_in_emptied
||
(
replaced_with
&&
dictFind
(
replaced_with
->
dict
,
key
->
ptr
)))
(
replaced_with
&&
dictFind
(
replaced_with
->
dict
,
key
->
ptr
)))
{
{
list
*
clients
=
dictGetVal
(
de
);
list
*
clients
=
dictGetVal
(
de
);
if
(
!
clients
)
continue
;
if
(
!
clients
)
continue
;
listRewind
(
clients
,
&
li
);
listRewind
(
clients
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
while
((
ln
=
listNext
(
&
li
)))
{
client
*
c
=
listNodeValue
(
ln
);
watchedKey
*
wk
=
listNodeValue
(
ln
);
if
(
wk
->
expired
)
{
if
(
!
replaced_with
||
!
dictFind
(
replaced_with
->
dict
,
key
->
ptr
))
{
/* Expired key now deleted. No logical change. Clear the
* flag. Deleted keys are not flagged as expired. */
wk
->
expired
=
0
;
continue
;
}
else
if
(
keyIsExpired
(
replaced_with
,
key
))
{
/* Expired key remains expired. */
continue
;
}
}
else
if
(
!
exists_in_emptied
&&
keyIsExpired
(
replaced_with
,
key
))
{
/* Non-existing key is replaced with an expired key. */
wk
->
expired
=
1
;
continue
;
}
client
*
c
=
wk
->
client
;
c
->
flags
|=
CLIENT_DIRTY_CAS
;
c
->
flags
|=
CLIENT_DIRTY_CAS
;
/* As the client is marked as dirty, there is no point in getting here
/* As the client is marked as dirty, there is no point in getting here
* again for others keys (or keep the memory overhead till EXEC). */
* again for others keys (or keep the memory overhead till EXEC). */
...
...
src/networking.c
View file @
a83e3663
...
@@ -131,7 +131,7 @@ client *createClient(connection *conn) {
...
@@ -131,7 +131,7 @@ client *createClient(connection *conn) {
connSetReadHandler
(
conn
,
readQueryFromClient
);
connSetReadHandler
(
conn
,
readQueryFromClient
);
connSetPrivateData
(
conn
,
c
);
connSetPrivateData
(
conn
,
c
);
}
}
c
->
buf
=
zmalloc
(
PROTO_REPLY_CHUNK_BYTES
);
selectDb
(
c
,
0
);
selectDb
(
c
,
0
);
uint64_t
client_id
;
uint64_t
client_id
;
atomicGetIncr
(
server
.
next_client_id
,
client_id
,
1
);
atomicGetIncr
(
server
.
next_client_id
,
client_id
,
1
);
...
@@ -140,7 +140,9 @@ client *createClient(connection *conn) {
...
@@ -140,7 +140,9 @@ client *createClient(connection *conn) {
c
->
conn
=
conn
;
c
->
conn
=
conn
;
c
->
name
=
NULL
;
c
->
name
=
NULL
;
c
->
bufpos
=
0
;
c
->
bufpos
=
0
;
c
->
buf_usable_size
=
zmalloc_usable_size
(
c
)
-
offsetof
(
client
,
buf
);
c
->
buf_usable_size
=
zmalloc_usable_size
(
c
->
buf
);
c
->
buf_peak
=
c
->
buf_usable_size
;
c
->
buf_peak_last_reset_time
=
server
.
unixtime
;
c
->
ref_repl_buf_node
=
NULL
;
c
->
ref_repl_buf_node
=
NULL
;
c
->
ref_block_pos
=
0
;
c
->
ref_block_pos
=
0
;
c
->
qb_pos
=
0
;
c
->
qb_pos
=
0
;
...
@@ -154,7 +156,7 @@ client *createClient(connection *conn) {
...
@@ -154,7 +156,7 @@ client *createClient(connection *conn) {
c
->
argv_len_sum
=
0
;
c
->
argv_len_sum
=
0
;
c
->
original_argc
=
0
;
c
->
original_argc
=
0
;
c
->
original_argv
=
NULL
;
c
->
original_argv
=
NULL
;
c
->
cmd
=
c
->
lastcmd
=
NULL
;
c
->
cmd
=
c
->
lastcmd
=
c
->
realcmd
=
NULL
;
c
->
multibulklen
=
0
;
c
->
multibulklen
=
0
;
c
->
bulklen
=
-
1
;
c
->
bulklen
=
-
1
;
c
->
sentlen
=
0
;
c
->
sentlen
=
0
;
...
@@ -173,6 +175,7 @@ client *createClient(connection *conn) {
...
@@ -173,6 +175,7 @@ client *createClient(connection *conn) {
c
->
slave_capa
=
SLAVE_CAPA_NONE
;
c
->
slave_capa
=
SLAVE_CAPA_NONE
;
c
->
slave_req
=
SLAVE_REQ_NONE
;
c
->
slave_req
=
SLAVE_REQ_NONE
;
c
->
reply
=
listCreate
();
c
->
reply
=
listCreate
();
c
->
deferred_reply_errors
=
NULL
;
c
->
reply_bytes
=
0
;
c
->
reply_bytes
=
0
;
c
->
obuf_soft_limit_reached_time
=
0
;
c
->
obuf_soft_limit_reached_time
=
0
;
listSetFreeMethod
(
c
->
reply
,
freeClientReplyValue
);
listSetFreeMethod
(
c
->
reply
,
freeClientReplyValue
);
...
@@ -313,6 +316,9 @@ size_t _addReplyToBuffer(client *c, const char *s, size_t len) {
...
@@ -313,6 +316,9 @@ size_t _addReplyToBuffer(client *c, const char *s, size_t len) {
size_t
reply_len
=
len
>
available
?
available
:
len
;
size_t
reply_len
=
len
>
available
?
available
:
len
;
memcpy
(
c
->
buf
+
c
->
bufpos
,
s
,
reply_len
);
memcpy
(
c
->
buf
+
c
->
bufpos
,
s
,
reply_len
);
c
->
bufpos
+=
reply_len
;
c
->
bufpos
+=
reply_len
;
/* We update the buffer peak after appending the reply to the buffer */
if
(
c
->
buf_peak
<
(
size_t
)
c
->
bufpos
)
c
->
buf_peak
=
(
size_t
)
c
->
bufpos
;
return
reply_len
;
return
reply_len
;
}
}
...
@@ -437,24 +443,46 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
...
@@ -437,24 +443,46 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
addReplyProto
(
c
,
"
\r\n
"
,
2
);
addReplyProto
(
c
,
"
\r\n
"
,
2
);
}
}
/* Do some actions after an error reply was sent (Log if needed, updates stats, etc.) */
/* Do some actions after an error reply was sent (Log if needed, updates stats, etc.)
void
afterErrorReply
(
client
*
c
,
const
char
*
s
,
size_t
len
)
{
* Possible flags:
/* Increment the global error counter */
* * ERR_REPLY_FLAG_NO_STATS_UPDATE - indicate not to update any error stats. */
server
.
stat_total_error_replies
++
;
void
afterErrorReply
(
client
*
c
,
const
char
*
s
,
size_t
len
,
int
flags
)
{
/* Increment the error stats
/* Module clients fall into two categories:
* If the string already starts with "-..." then the error prefix
* Calls to RM_Call, in which case the error isn't being returned to a client, so should not be counted.
* is provided by the caller ( we limit the search to 32 chars). Otherwise we use "-ERR". */
* Module thread safe context calls to RM_ReplyWithError, which will be added to a real client by the main thread later. */
if
(
s
[
0
]
!=
'-'
)
{
if
(
c
->
flags
&
CLIENT_MODULE
)
{
incrementErrorCount
(
"ERR"
,
3
);
if
(
!
c
->
deferred_reply_errors
)
{
}
else
{
c
->
deferred_reply_errors
=
listCreate
();
char
*
spaceloc
=
memchr
(
s
,
' '
,
len
<
32
?
len
:
32
);
listSetFreeMethod
(
c
->
deferred_reply_errors
,
(
void
(
*
)(
void
*
))
sdsfree
);
if
(
spaceloc
)
{
}
const
size_t
errEndPos
=
(
size_t
)(
spaceloc
-
s
);
listAddNodeTail
(
c
->
deferred_reply_errors
,
sdsnewlen
(
s
,
len
));
incrementErrorCount
(
s
+
1
,
errEndPos
-
1
);
return
;
}
else
{
}
/* Fallback to ERR if we can't retrieve the error prefix */
if
(
!
(
flags
&
ERR_REPLY_FLAG_NO_STATS_UPDATE
))
{
/* Increment the global error counter */
server
.
stat_total_error_replies
++
;
/* Increment the error stats
* If the string already starts with "-..." then the error prefix
* is provided by the caller ( we limit the search to 32 chars). Otherwise we use "-ERR". */
if
(
s
[
0
]
!=
'-'
)
{
incrementErrorCount
(
"ERR"
,
3
);
incrementErrorCount
(
"ERR"
,
3
);
}
else
{
char
*
spaceloc
=
memchr
(
s
,
' '
,
len
<
32
?
len
:
32
);
if
(
spaceloc
)
{
const
size_t
errEndPos
=
(
size_t
)(
spaceloc
-
s
);
incrementErrorCount
(
s
+
1
,
errEndPos
-
1
);
}
else
{
/* Fallback to ERR if we can't retrieve the error prefix */
incrementErrorCount
(
"ERR"
,
3
);
}
}
}
}
else
{
/* stat_total_error_replies will not be updated, which means that
* the cmd stats will not be updated as well, we still want this command
* to be counted as failed so we update it here. We update c->realcmd in
* case c->cmd was changed (like in GEOADD). */
c
->
realcmd
->
failed_calls
++
;
}
}
/* Sometimes it could be normal that a slave replies to a master with
/* Sometimes it could be normal that a slave replies to a master with
...
@@ -500,7 +528,7 @@ void afterErrorReply(client *c, const char *s, size_t len) {
...
@@ -500,7 +528,7 @@ void afterErrorReply(client *c, const char *s, size_t len) {
* Unlike addReplyErrorSds and others alike which rely on addReplyErrorLength. */
* Unlike addReplyErrorSds and others alike which rely on addReplyErrorLength. */
void
addReplyErrorObject
(
client
*
c
,
robj
*
err
)
{
void
addReplyErrorObject
(
client
*
c
,
robj
*
err
)
{
addReply
(
c
,
err
);
addReply
(
c
,
err
);
afterErrorReply
(
c
,
err
->
ptr
,
sdslen
(
err
->
ptr
)
-
2
);
/* Ignore trailing \r\n */
afterErrorReply
(
c
,
err
->
ptr
,
sdslen
(
err
->
ptr
)
-
2
,
0
);
/* Ignore trailing \r\n */
}
}
/* Sends either a reply or an error reply by checking the first char.
/* Sends either a reply or an error reply by checking the first char.
...
@@ -521,34 +549,57 @@ void addReplyOrErrorObject(client *c, robj *reply) {
...
@@ -521,34 +549,57 @@ void addReplyOrErrorObject(client *c, robj *reply) {
/* See addReplyErrorLength for expectations from the input string. */
/* See addReplyErrorLength for expectations from the input string. */
void
addReplyError
(
client
*
c
,
const
char
*
err
)
{
void
addReplyError
(
client
*
c
,
const
char
*
err
)
{
addReplyErrorLength
(
c
,
err
,
strlen
(
err
));
addReplyErrorLength
(
c
,
err
,
strlen
(
err
));
afterErrorReply
(
c
,
err
,
strlen
(
err
));
afterErrorReply
(
c
,
err
,
strlen
(
err
),
0
);
}
/* Add error reply to the given client.
* Supported flags:
* * ERR_REPLY_FLAG_NO_STATS_UPDATE - indicate not to perform any error stats updates */
void
addReplyErrorSdsEx
(
client
*
c
,
sds
err
,
int
flags
)
{
addReplyErrorLength
(
c
,
err
,
sdslen
(
err
));
afterErrorReply
(
c
,
err
,
sdslen
(
err
),
flags
);
sdsfree
(
err
);
}
}
/* See addReplyErrorLength for expectations from the input string. */
/* See addReplyErrorLength for expectations from the input string. */
/* As a side effect the SDS string is freed. */
/* As a side effect the SDS string is freed. */
void
addReplyErrorSds
(
client
*
c
,
sds
err
)
{
void
addReplyErrorSds
(
client
*
c
,
sds
err
)
{
addReplyErrorLength
(
c
,
err
,
sdslen
(
err
));
addReplyErrorSdsEx
(
c
,
err
,
0
);
afterErrorReply
(
c
,
err
,
sdslen
(
err
));
sdsfree
(
err
);
}
}
/*
See addReplyErrorLength for expectations from the formatted string
.
/*
Internal function used by addReplyErrorFormat and addReplyErrorFormatEx
.
*
The formatted string is safe to contain \r and \n anywhere
. */
*
Refer to afterErrorReply for more information about the flags
. */
void
addReplyErrorFormat
(
client
*
c
,
const
char
*
fmt
,
...
)
{
static
void
addReplyErrorFormat
Internal
(
client
*
c
,
int
flags
,
const
char
*
fmt
,
va_list
ap
)
{
va_list
ap
;
va_list
cpy
;
va_
start
(
ap
,
fmt
);
va_
copy
(
cpy
,
ap
);
sds
s
=
sdscatvprintf
(
sdsempty
(),
fmt
,
ap
);
sds
s
=
sdscatvprintf
(
sdsempty
(),
fmt
,
cpy
);
va_end
(
ap
);
va_end
(
cpy
);
/* Trim any newlines at the end (ones will be added by addReplyErrorLength) */
/* Trim any newlines at the end (ones will be added by addReplyErrorLength) */
s
=
sdstrim
(
s
,
"
\r\n
"
);
s
=
sdstrim
(
s
,
"
\r\n
"
);
/* Make sure there are no newlines in the middle of the string, otherwise
/* Make sure there are no newlines in the middle of the string, otherwise
* invalid protocol is emitted. */
* invalid protocol is emitted. */
s
=
sdsmapchars
(
s
,
"
\r\n
"
,
" "
,
2
);
s
=
sdsmapchars
(
s
,
"
\r\n
"
,
" "
,
2
);
addReplyErrorLength
(
c
,
s
,
sdslen
(
s
));
addReplyErrorLength
(
c
,
s
,
sdslen
(
s
));
afterErrorReply
(
c
,
s
,
sdslen
(
s
));
afterErrorReply
(
c
,
s
,
sdslen
(
s
)
,
flags
);
sdsfree
(
s
);
sdsfree
(
s
);
}
}
void
addReplyErrorFormatEx
(
client
*
c
,
int
flags
,
const
char
*
fmt
,
...)
{
va_list
ap
;
va_start
(
ap
,
fmt
);
addReplyErrorFormatInternal
(
c
,
flags
,
fmt
,
ap
);
va_end
(
ap
);
}
/* See addReplyErrorLength for expectations from the formatted string.
* The formatted string is safe to contain \r and \n anywhere. */
void
addReplyErrorFormat
(
client
*
c
,
const
char
*
fmt
,
...)
{
va_list
ap
;
va_start
(
ap
,
fmt
);
addReplyErrorFormatInternal
(
c
,
0
,
fmt
,
ap
);
va_end
(
ap
);
}
void
addReplyErrorArity
(
client
*
c
)
{
void
addReplyErrorArity
(
client
*
c
)
{
addReplyErrorFormat
(
c
,
"wrong number of arguments for '%s' command"
,
addReplyErrorFormat
(
c
,
"wrong number of arguments for '%s' command"
,
c
->
cmd
->
fullname
);
c
->
cmd
->
fullname
);
...
@@ -696,6 +747,24 @@ void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
...
@@ -696,6 +747,24 @@ void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
* we return NULL in addReplyDeferredLen() */
* we return NULL in addReplyDeferredLen() */
if
(
node
==
NULL
)
return
;
if
(
node
==
NULL
)
return
;
/* Things like *2\r\n, %3\r\n or ~4\r\n are emitted very often by the protocol
* so we have a few shared objects to use if the integer is small
* like it is most of the times. */
const
size_t
hdr_len
=
OBJ_SHARED_HDR_STRLEN
(
length
);
const
int
opt_hdr
=
length
<
OBJ_SHARED_BULKHDR_LEN
;
if
(
prefix
==
'*'
&&
opt_hdr
)
{
setDeferredReply
(
c
,
node
,
shared
.
mbulkhdr
[
length
]
->
ptr
,
hdr_len
);
return
;
}
if
(
prefix
==
'%'
&&
opt_hdr
)
{
setDeferredReply
(
c
,
node
,
shared
.
maphdr
[
length
]
->
ptr
,
hdr_len
);
return
;
}
if
(
prefix
==
'~'
&&
opt_hdr
)
{
setDeferredReply
(
c
,
node
,
shared
.
sethdr
[
length
]
->
ptr
,
hdr_len
);
return
;
}
char
lenstr
[
128
];
char
lenstr
[
128
];
size_t
lenstr_len
=
sprintf
(
lenstr
,
"%c%ld
\r\n
"
,
prefix
,
length
);
size_t
lenstr_len
=
sprintf
(
lenstr
,
"%c%ld
\r\n
"
,
prefix
,
length
);
setDeferredReply
(
c
,
node
,
lenstr
,
lenstr_len
);
setDeferredReply
(
c
,
node
,
lenstr
,
lenstr_len
);
...
@@ -788,11 +857,19 @@ void addReplyLongLongWithPrefix(client *c, long long ll, char prefix) {
...
@@ -788,11 +857,19 @@ void addReplyLongLongWithPrefix(client *c, long long ll, char prefix) {
/* Things like $3\r\n or *2\r\n are emitted very often by the protocol
/* Things like $3\r\n or *2\r\n are emitted very often by the protocol
* so we have a few shared objects to use if the integer is small
* so we have a few shared objects to use if the integer is small
* like it is most of the times. */
* like it is most of the times. */
if
(
prefix
==
'*'
&&
ll
<
OBJ_SHARED_BULKHDR_LEN
&&
ll
>=
0
)
{
const
int
opt_hdr
=
ll
<
OBJ_SHARED_BULKHDR_LEN
&&
ll
>=
0
;
addReply
(
c
,
shared
.
mbulkhdr
[
ll
]);
const
size_t
hdr_len
=
OBJ_SHARED_HDR_STRLEN
(
ll
);
if
(
prefix
==
'*'
&&
opt_hdr
)
{
addReplyProto
(
c
,
shared
.
mbulkhdr
[
ll
]
->
ptr
,
hdr_len
);
return
;
}
else
if
(
prefix
==
'$'
&&
opt_hdr
)
{
addReplyProto
(
c
,
shared
.
bulkhdr
[
ll
]
->
ptr
,
hdr_len
);
return
;
}
else
if
(
prefix
==
'%'
&&
opt_hdr
)
{
addReplyProto
(
c
,
shared
.
maphdr
[
ll
]
->
ptr
,
hdr_len
);
return
;
return
;
}
else
if
(
prefix
==
'
$
'
&&
ll
<
OBJ_SHARED_BULKHDR_LEN
&&
ll
>=
0
)
{
}
else
if
(
prefix
==
'
~
'
&&
opt_hdr
)
{
addReply
(
c
,
shared
.
bulk
hdr
[
ll
]);
addReply
Proto
(
c
,
shared
.
set
hdr
[
ll
]
->
ptr
,
hdr_len
);
return
;
return
;
}
}
...
@@ -1024,10 +1101,28 @@ void AddReplyFromClient(client *dst, client *src) {
...
@@ -1024,10 +1101,28 @@ void AddReplyFromClient(client *dst, client *src) {
src
->
reply_bytes
=
0
;
src
->
reply_bytes
=
0
;
src
->
bufpos
=
0
;
src
->
bufpos
=
0
;
if
(
src
->
deferred_reply_errors
)
{
deferredAfterErrorReply
(
dst
,
src
->
deferred_reply_errors
);
listRelease
(
src
->
deferred_reply_errors
);
src
->
deferred_reply_errors
=
NULL
;
}
/* Check output buffer limits */
/* Check output buffer limits */
closeClientOnOutputBufferLimitReached
(
dst
,
1
);
closeClientOnOutputBufferLimitReached
(
dst
,
1
);
}
}
/* Append the listed errors to the server error statistics. the input
* list is not modified and remains the responsibility of the caller. */
void
deferredAfterErrorReply
(
client
*
c
,
list
*
errors
)
{
listIter
li
;
listNode
*
ln
;
listRewind
(
errors
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
sds
err
=
ln
->
value
;
afterErrorReply
(
c
,
err
,
sdslen
(
err
),
0
);
}
}
/* Logically copy 'src' replica client buffers info to 'dst' replica.
/* Logically copy 'src' replica client buffers info to 'dst' replica.
* Basically increase referenced buffer block node reference count. */
* Basically increase referenced buffer block node reference count. */
void
copyReplicaOutputBuffer
(
client
*
dst
,
client
*
src
)
{
void
copyReplicaOutputBuffer
(
client
*
dst
,
client
*
src
)
{
...
@@ -1494,9 +1589,12 @@ void freeClient(client *c) {
...
@@ -1494,9 +1589,12 @@ void freeClient(client *c) {
/* Free data structures. */
/* Free data structures. */
listRelease
(
c
->
reply
);
listRelease
(
c
->
reply
);
zfree
(
c
->
buf
);
freeReplicaReferencedReplBuffer
(
c
);
freeReplicaReferencedReplBuffer
(
c
);
freeClientArgv
(
c
);
freeClientArgv
(
c
);
freeClientOriginalArgv
(
c
);
freeClientOriginalArgv
(
c
);
if
(
c
->
deferred_reply_errors
)
listRelease
(
c
->
deferred_reply_errors
);
/* Unlink the client: this will close the socket, remove the I/O
/* Unlink the client: this will close the socket, remove the I/O
* handlers, and remove references of the client from different
* handlers, and remove references of the client from different
...
@@ -1658,10 +1756,82 @@ client *lookupClientByID(uint64_t id) {
...
@@ -1658,10 +1756,82 @@ client *lookupClientByID(uint64_t id) {
return
(
c
==
raxNotFound
)
?
NULL
:
c
;
return
(
c
==
raxNotFound
)
?
NULL
:
c
;
}
}
/* This function should be called from _writeToClient when the reply list is not empty,
* it gathers the scattered buffers from reply list and sends them away with connWritev.
* If we write successfully, it returns C_OK, otherwise, C_ERR is returned,
* and 'nwritten' is an output parameter, it means how many bytes server write
* to client. */
static
int
_writevToClient
(
client
*
c
,
ssize_t
*
nwritten
)
{
struct
iovec
iov
[
IOV_MAX
];
int
iovcnt
=
0
;
size_t
iov_bytes_len
=
0
;
/* If the static reply buffer is not empty,
* add it to the iov array for writev() as well. */
if
(
c
->
bufpos
>
0
)
{
iov
[
iovcnt
].
iov_base
=
c
->
buf
+
c
->
sentlen
;
iov
[
iovcnt
].
iov_len
=
c
->
bufpos
-
c
->
sentlen
;
iov_bytes_len
+=
iov
[
iovcnt
++
].
iov_len
;
}
/* The first node of reply list might be incomplete from the last call,
* thus it needs to be calibrated to get the actual data address and length. */
size_t
offset
=
c
->
bufpos
>
0
?
0
:
c
->
sentlen
;
listIter
iter
;
listNode
*
next
;
clientReplyBlock
*
o
;
listRewind
(
c
->
reply
,
&
iter
);
while
((
next
=
listNext
(
&
iter
))
&&
iovcnt
<
IOV_MAX
&&
iov_bytes_len
<
NET_MAX_WRITES_PER_EVENT
)
{
o
=
listNodeValue
(
next
);
if
(
o
->
used
==
0
)
{
/* empty node, just release it and skip. */
c
->
reply_bytes
-=
o
->
size
;
listDelNode
(
c
->
reply
,
next
);
offset
=
0
;
continue
;
}
iov
[
iovcnt
].
iov_base
=
o
->
buf
+
offset
;
iov
[
iovcnt
].
iov_len
=
o
->
used
-
offset
;
iov_bytes_len
+=
iov
[
iovcnt
++
].
iov_len
;
offset
=
0
;
}
if
(
iovcnt
==
0
)
return
C_OK
;
*
nwritten
=
connWritev
(
c
->
conn
,
iov
,
iovcnt
);
if
(
*
nwritten
<=
0
)
return
C_ERR
;
/* Locate the new node which has leftover data and
* release all nodes in front of it. */
ssize_t
remaining
=
*
nwritten
;
if
(
c
->
bufpos
>
0
)
{
/* deal with static reply buffer first. */
int
buf_len
=
c
->
bufpos
-
c
->
sentlen
;
c
->
sentlen
+=
remaining
;
/* If the buffer was sent, set bufpos to zero to continue with
* the remainder of the reply. */
if
(
remaining
>=
buf_len
)
{
c
->
bufpos
=
0
;
c
->
sentlen
=
0
;
}
remaining
-=
buf_len
;
}
listRewind
(
c
->
reply
,
&
iter
);
while
(
remaining
>
0
)
{
next
=
listNext
(
&
iter
);
o
=
listNodeValue
(
next
);
if
(
remaining
<
(
ssize_t
)(
o
->
used
-
c
->
sentlen
))
{
c
->
sentlen
+=
remaining
;
break
;
}
remaining
-=
(
ssize_t
)(
o
->
used
-
c
->
sentlen
);
c
->
reply_bytes
-=
o
->
size
;
listDelNode
(
c
->
reply
,
next
);
c
->
sentlen
=
0
;
}
return
C_OK
;
}
/* This function does actual writing output buffers to different types of
/* This function does actual writing output buffers to different types of
* clients, it is called by writeToClient.
* clients, it is called by writeToClient.
* If we write successfully, it return C_OK, otherwise, C_ERR is returned,
* If we write successfully, it return
s
C_OK, otherwise, C_ERR is returned,
*
A
nd 'nwritten' is a output parameter, it means how many bytes server write
*
a
nd 'nwritten' is a
n
output parameter, it means how many bytes server write
* to client. */
* to client. */
int
_writeToClient
(
client
*
c
,
ssize_t
*
nwritten
)
{
int
_writeToClient
(
client
*
c
,
ssize_t
*
nwritten
)
{
*
nwritten
=
0
;
*
nwritten
=
0
;
...
@@ -1690,8 +1860,18 @@ int _writeToClient(client *c, ssize_t *nwritten) {
...
@@ -1690,8 +1860,18 @@ int _writeToClient(client *c, ssize_t *nwritten) {
return
C_OK
;
return
C_OK
;
}
}
if
(
c
->
bufpos
>
0
)
{
/* When the reply list is not empty, it's better to use writev to save us some
*
nwritten
=
connWrite
(
c
->
conn
,
c
->
buf
+
c
->
sentlen
,
c
->
bufpos
-
c
->
sentlen
);
* system calls and TCP packets. */
if
(
listLength
(
c
->
reply
)
>
0
)
{
int
ret
=
_writevToClient
(
c
,
nwritten
);
if
(
ret
!=
C_OK
)
return
ret
;
/* If there are no longer objects in the list, we expect
* the count of reply bytes to be exactly zero. */
if
(
listLength
(
c
->
reply
)
==
0
)
serverAssert
(
c
->
reply_bytes
==
0
);
}
else
if
(
c
->
bufpos
>
0
)
{
*
nwritten
=
connWrite
(
c
->
conn
,
c
->
buf
+
c
->
sentlen
,
c
->
bufpos
-
c
->
sentlen
);
if
(
*
nwritten
<=
0
)
return
C_ERR
;
if
(
*
nwritten
<=
0
)
return
C_ERR
;
c
->
sentlen
+=
*
nwritten
;
c
->
sentlen
+=
*
nwritten
;
...
@@ -1701,31 +1881,8 @@ int _writeToClient(client *c, ssize_t *nwritten) {
...
@@ -1701,31 +1881,8 @@ int _writeToClient(client *c, ssize_t *nwritten) {
c
->
bufpos
=
0
;
c
->
bufpos
=
0
;
c
->
sentlen
=
0
;
c
->
sentlen
=
0
;
}
}
}
else
{
}
clientReplyBlock
*
o
=
listNodeValue
(
listFirst
(
c
->
reply
));
size_t
objlen
=
o
->
used
;
if
(
objlen
==
0
)
{
c
->
reply_bytes
-=
o
->
size
;
listDelNode
(
c
->
reply
,
listFirst
(
c
->
reply
));
return
C_OK
;
}
*
nwritten
=
connWrite
(
c
->
conn
,
o
->
buf
+
c
->
sentlen
,
objlen
-
c
->
sentlen
);
if
(
*
nwritten
<=
0
)
return
C_ERR
;
c
->
sentlen
+=
*
nwritten
;
/* If we fully sent the object on head go to the next one */
if
(
c
->
sentlen
==
objlen
)
{
c
->
reply_bytes
-=
o
->
size
;
listDelNode
(
c
->
reply
,
listFirst
(
c
->
reply
));
c
->
sentlen
=
0
;
/* If there are no longer objects in the list, we expect
* the count of reply bytes to be exactly zero. */
if
(
listLength
(
c
->
reply
)
==
0
)
serverAssert
(
c
->
reply_bytes
==
0
);
}
}
return
C_OK
;
return
C_OK
;
}
}
...
@@ -1863,6 +2020,10 @@ void resetClient(client *c) {
...
@@ -1863,6 +2020,10 @@ void resetClient(client *c) {
c
->
multibulklen
=
0
;
c
->
multibulklen
=
0
;
c
->
bulklen
=
-
1
;
c
->
bulklen
=
-
1
;
if
(
c
->
deferred_reply_errors
)
listRelease
(
c
->
deferred_reply_errors
);
c
->
deferred_reply_errors
=
NULL
;
/* We clear the ASKING flag as well if we are not inside a MULTI, and
/* We clear the ASKING flag as well if we are not inside a MULTI, and
* if what we just executed is not the ASKING command itself. */
* if what we just executed is not the ASKING command itself. */
if
(
!
(
c
->
flags
&
CLIENT_MULTI
)
&&
prevcmd
!=
askingCommand
)
if
(
!
(
c
->
flags
&
CLIENT_MULTI
)
&&
prevcmd
!=
askingCommand
)
...
@@ -2556,7 +2717,7 @@ sds catClientInfoString(sds s, client *client) {
...
@@ -2556,7 +2717,7 @@ sds catClientInfoString(sds s, client *client) {
}
}
sds
ret
=
sdscatfmt
(
s
,
sds
ret
=
sdscatfmt
(
s
,
"id=%U addr=%s laddr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U argv-mem=%U multi-mem=%U obl=%U oll=%U omem=%U tot-mem=%U events=%s cmd=%s user=%s redir=%I resp=%i"
,
"id=%U addr=%s laddr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U argv-mem=%U multi-mem=%U
rbs=%U rbp=%U
obl=%U oll=%U omem=%U tot-mem=%U events=%s cmd=%s user=%s redir=%I resp=%i"
,
(
unsigned
long
long
)
client
->
id
,
(
unsigned
long
long
)
client
->
id
,
getClientPeerId
(
client
),
getClientPeerId
(
client
),
getClientSockname
(
client
),
getClientSockname
(
client
),
...
@@ -2573,6 +2734,8 @@ sds catClientInfoString(sds s, client *client) {
...
@@ -2573,6 +2734,8 @@ sds catClientInfoString(sds s, client *client) {
(
unsigned
long
long
)
sdsavail
(
client
->
querybuf
),
(
unsigned
long
long
)
sdsavail
(
client
->
querybuf
),
(
unsigned
long
long
)
client
->
argv_len_sum
,
(
unsigned
long
long
)
client
->
argv_len_sum
,
(
unsigned
long
long
)
client
->
mstate
.
argv_len_sums
,
(
unsigned
long
long
)
client
->
mstate
.
argv_len_sums
,
(
unsigned
long
long
)
client
->
buf_usable_size
,
(
unsigned
long
long
)
client
->
buf_peak
,
(
unsigned
long
long
)
client
->
bufpos
,
(
unsigned
long
long
)
client
->
bufpos
,
(
unsigned
long
long
)
listLength
(
client
->
reply
)
+
used_blocks_of_repl_buf
,
(
unsigned
long
long
)
listLength
(
client
->
reply
)
+
used_blocks_of_repl_buf
,
(
unsigned
long
long
)
obufmem
,
/* should not include client->buf since we want to see 0 for static clients. */
(
unsigned
long
long
)
obufmem
,
/* should not include client->buf since we want to see 0 for static clients. */
...
@@ -2919,6 +3082,7 @@ NULL
...
@@ -2919,6 +3082,7 @@ NULL
else
else
replyToBlockedClientTimedOut
(
target
);
replyToBlockedClientTimedOut
(
target
);
unblockClient
(
target
);
unblockClient
(
target
);
updateStatsOnUnblock
(
target
,
0
,
0
,
1
);
addReply
(
c
,
shared
.
cone
);
addReply
(
c
,
shared
.
cone
);
}
else
{
}
else
{
addReply
(
c
,
shared
.
czero
);
addReply
(
c
,
shared
.
czero
);
...
@@ -3414,6 +3578,7 @@ size_t getClientMemoryUsage(client *c, size_t *output_buffer_mem_usage) {
...
@@ -3414,6 +3578,7 @@ size_t getClientMemoryUsage(client *c, size_t *output_buffer_mem_usage) {
*
output_buffer_mem_usage
=
mem
;
*
output_buffer_mem_usage
=
mem
;
mem
+=
sdsZmallocSize
(
c
->
querybuf
);
mem
+=
sdsZmallocSize
(
c
->
querybuf
);
mem
+=
zmalloc_size
(
c
);
mem
+=
zmalloc_size
(
c
);
mem
+=
c
->
buf_usable_size
;
/* For efficiency (less work keeping track of the argv memory), it doesn't include the used memory
/* For efficiency (less work keeping track of the argv memory), it doesn't include the used memory
* i.e. unused sds space and internal fragmentation, just the string length. but this is enough to
* i.e. unused sds space and internal fragmentation, just the string length. but this is enough to
* spot problematic clients. */
* spot problematic clients. */
...
...
src/rdb.c
View file @
a83e3663
...
@@ -692,7 +692,7 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
...
@@ -692,7 +692,7 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
else
else
serverPanic
(
"Unknown hash encoding"
);
serverPanic
(
"Unknown hash encoding"
);
case
OBJ_STREAM
:
case
OBJ_STREAM
:
return
rdbSaveType
(
rdb
,
RDB_TYPE_STREAM_LISTPACKS
);
return
rdbSaveType
(
rdb
,
RDB_TYPE_STREAM_LISTPACKS
_2
);
case
OBJ_MODULE
:
case
OBJ_MODULE
:
return
rdbSaveType
(
rdb
,
RDB_TYPE_MODULE_2
);
return
rdbSaveType
(
rdb
,
RDB_TYPE_MODULE_2
);
default:
default:
...
@@ -986,6 +986,19 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
...
@@ -986,6 +986,19 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
nwritten
+=
n
;
nwritten
+=
n
;
if
((
n
=
rdbSaveLen
(
rdb
,
s
->
last_id
.
seq
))
==
-
1
)
return
-
1
;
if
((
n
=
rdbSaveLen
(
rdb
,
s
->
last_id
.
seq
))
==
-
1
)
return
-
1
;
nwritten
+=
n
;
nwritten
+=
n
;
/* Save the first entry ID. */
if
((
n
=
rdbSaveLen
(
rdb
,
s
->
first_id
.
ms
))
==
-
1
)
return
-
1
;
nwritten
+=
n
;
if
((
n
=
rdbSaveLen
(
rdb
,
s
->
first_id
.
seq
))
==
-
1
)
return
-
1
;
nwritten
+=
n
;
/* Save the maximal tombstone ID. */
if
((
n
=
rdbSaveLen
(
rdb
,
s
->
max_deleted_entry_id
.
ms
))
==
-
1
)
return
-
1
;
nwritten
+=
n
;
if
((
n
=
rdbSaveLen
(
rdb
,
s
->
max_deleted_entry_id
.
seq
))
==
-
1
)
return
-
1
;
nwritten
+=
n
;
/* Save the offset. */
if
((
n
=
rdbSaveLen
(
rdb
,
s
->
entries_added
))
==
-
1
)
return
-
1
;
nwritten
+=
n
;
/* The consumer groups and their clients are part of the stream
/* The consumer groups and their clients are part of the stream
* type, so serialize every consumer group. */
* type, so serialize every consumer group. */
...
@@ -1020,6 +1033,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
...
@@ -1020,6 +1033,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
return
-
1
;
return
-
1
;
}
}
nwritten
+=
n
;
nwritten
+=
n
;
/* Save the group's logical reads counter. */
if
((
n
=
rdbSaveLen
(
rdb
,
cg
->
entries_read
))
==
-
1
)
{
raxStop
(
&
ri
);
return
-
1
;
}
nwritten
+=
n
;
/* Save the global PEL. */
/* Save the global PEL. */
if
((
n
=
rdbSaveStreamPEL
(
rdb
,
cg
->
pel
,
1
))
==
-
1
)
{
if
((
n
=
rdbSaveStreamPEL
(
rdb
,
cg
->
pel
,
1
))
==
-
1
)
{
...
@@ -1151,8 +1171,9 @@ ssize_t rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {
...
@@ -1151,8 +1171,9 @@ ssize_t rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {
/* Save a few default AUX fields with information about the RDB generated. */
/* Save a few default AUX fields with information about the RDB generated. */
int
rdbSaveInfoAuxFields
(
rio
*
rdb
,
int
rdbflags
,
rdbSaveInfo
*
rsi
)
{
int
rdbSaveInfoAuxFields
(
rio
*
rdb
,
int
rdbflags
,
rdbSaveInfo
*
rsi
)
{
UNUSED
(
rdbflags
);
int
redis_bits
=
(
sizeof
(
void
*
)
==
8
)
?
64
:
32
;
int
redis_bits
=
(
sizeof
(
void
*
)
==
8
)
?
64
:
32
;
int
aof_
preambl
e
=
(
rdbflags
&
RDBFLAGS_AOF_PREAMBLE
)
!=
0
;
int
aof_
bas
e
=
(
rdbflags
&
RDBFLAGS_AOF_PREAMBLE
)
!=
0
;
/* Add a few fields about the state when the RDB was created. */
/* Add a few fields about the state when the RDB was created. */
if
(
rdbSaveAuxFieldStrStr
(
rdb
,
"redis-ver"
,
REDIS_VERSION
)
==
-
1
)
return
-
1
;
if
(
rdbSaveAuxFieldStrStr
(
rdb
,
"redis-ver"
,
REDIS_VERSION
)
==
-
1
)
return
-
1
;
...
@@ -1169,7 +1190,7 @@ int rdbSaveInfoAuxFields(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
...
@@ -1169,7 +1190,7 @@ int rdbSaveInfoAuxFields(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
if
(
rdbSaveAuxFieldStrInt
(
rdb
,
"repl-offset"
,
server
.
master_repl_offset
)
if
(
rdbSaveAuxFieldStrInt
(
rdb
,
"repl-offset"
,
server
.
master_repl_offset
)
==
-
1
)
return
-
1
;
==
-
1
)
return
-
1
;
}
}
if
(
rdbSaveAuxFieldStrInt
(
rdb
,
"aof-
preambl
e"
,
aof_
preambl
e
)
==
-
1
)
return
-
1
;
if
(
rdbSaveAuxFieldStrInt
(
rdb
,
"aof-
bas
e"
,
aof_
bas
e
)
==
-
1
)
return
-
1
;
return
1
;
return
1
;
}
}
...
@@ -1470,6 +1491,7 @@ int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi) {
...
@@ -1470,6 +1491,7 @@ int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi) {
pid_t
childpid
;
pid_t
childpid
;
if
(
hasActiveChildProcess
())
return
C_ERR
;
if
(
hasActiveChildProcess
())
return
C_ERR
;
server
.
stat_rdb_saves
++
;
server
.
dirty_before_bgsave
=
server
.
dirty
;
server
.
dirty_before_bgsave
=
server
.
dirty
;
server
.
lastbgsave_try
=
time
(
NULL
);
server
.
lastbgsave_try
=
time
(
NULL
);
...
@@ -2319,7 +2341,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
...
@@ -2319,7 +2341,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
rdbReportCorruptRDB
(
"Unknown RDB encoding type %d"
,
rdbtype
);
rdbReportCorruptRDB
(
"Unknown RDB encoding type %d"
,
rdbtype
);
break
;
break
;
}
}
}
else
if
(
rdbtype
==
RDB_TYPE_STREAM_LISTPACKS
)
{
}
else
if
(
rdbtype
==
RDB_TYPE_STREAM_LISTPACKS
||
rdbtype
==
RDB_TYPE_STREAM_LISTPACKS_2
)
{
o
=
createStreamObject
();
o
=
createStreamObject
();
stream
*
s
=
o
->
ptr
;
stream
*
s
=
o
->
ptr
;
uint64_t
listpacks
=
rdbLoadLen
(
rdb
,
NULL
);
uint64_t
listpacks
=
rdbLoadLen
(
rdb
,
NULL
);
...
@@ -2395,6 +2417,30 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
...
@@ -2395,6 +2417,30 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
/* Load the last entry ID. */
/* Load the last entry ID. */
s
->
last_id
.
ms
=
rdbLoadLen
(
rdb
,
NULL
);
s
->
last_id
.
ms
=
rdbLoadLen
(
rdb
,
NULL
);
s
->
last_id
.
seq
=
rdbLoadLen
(
rdb
,
NULL
);
s
->
last_id
.
seq
=
rdbLoadLen
(
rdb
,
NULL
);
if
(
rdbtype
==
RDB_TYPE_STREAM_LISTPACKS_2
)
{
/* Load the first entry ID. */
s
->
first_id
.
ms
=
rdbLoadLen
(
rdb
,
NULL
);
s
->
first_id
.
seq
=
rdbLoadLen
(
rdb
,
NULL
);
/* Load the maximal deleted entry ID. */
s
->
max_deleted_entry_id
.
ms
=
rdbLoadLen
(
rdb
,
NULL
);
s
->
max_deleted_entry_id
.
seq
=
rdbLoadLen
(
rdb
,
NULL
);
/* Load the offset. */
s
->
entries_added
=
rdbLoadLen
(
rdb
,
NULL
);
}
else
{
/* During migration the offset can be initialized to the stream's
* length. At this point, we also don't care about tombstones
* because CG offsets will be later initialized as well. */
s
->
max_deleted_entry_id
.
ms
=
0
;
s
->
max_deleted_entry_id
.
seq
=
0
;
s
->
entries_added
=
s
->
length
;
/* Since the rax is already loaded, we can find the first entry's
* ID. */
streamGetEdgeID
(
s
,
1
,
1
,
&
s
->
first_id
);
}
if
(
rioGetReadError
(
rdb
))
{
if
(
rioGetReadError
(
rdb
))
{
rdbReportReadError
(
"Stream object metadata loading failed."
);
rdbReportReadError
(
"Stream object metadata loading failed."
);
...
@@ -2430,8 +2476,22 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
...
@@ -2430,8 +2476,22 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
decrRefCount
(
o
);
decrRefCount
(
o
);
return
NULL
;
return
NULL
;
}
}
/* Load group offset. */
uint64_t
cg_offset
;
if
(
rdbtype
==
RDB_TYPE_STREAM_LISTPACKS_2
)
{
cg_offset
=
rdbLoadLen
(
rdb
,
NULL
);
if
(
rioGetReadError
(
rdb
))
{
rdbReportReadError
(
"Stream cgroup offset loading failed."
);
sdsfree
(
cgname
);
decrRefCount
(
o
);
return
NULL
;
}
}
else
{
cg_offset
=
streamEstimateDistanceFromFirstEverEntry
(
s
,
&
cg_id
);
}
streamCG
*
cgroup
=
streamCreateCG
(
s
,
cgname
,
sdslen
(
cgname
),
&
cg_id
);
streamCG
*
cgroup
=
streamCreateCG
(
s
,
cgname
,
sdslen
(
cgname
),
&
cg_id
,
cg_offset
);
if
(
cgroup
==
NULL
)
{
if
(
cgroup
==
NULL
)
{
rdbReportCorruptRDB
(
"Duplicated consumer group name %s"
,
rdbReportCorruptRDB
(
"Duplicated consumer group name %s"
,
cgname
);
cgname
);
...
@@ -2962,6 +3022,9 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin
...
@@ -2962,6 +3022,9 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin
}
else
if
(
!
strcasecmp
(
auxkey
->
ptr
,
"aof-preamble"
))
{
}
else
if
(
!
strcasecmp
(
auxkey
->
ptr
,
"aof-preamble"
))
{
long
long
haspreamble
=
strtoll
(
auxval
->
ptr
,
NULL
,
10
);
long
long
haspreamble
=
strtoll
(
auxval
->
ptr
,
NULL
,
10
);
if
(
haspreamble
)
serverLog
(
LL_NOTICE
,
"RDB has an AOF tail"
);
if
(
haspreamble
)
serverLog
(
LL_NOTICE
,
"RDB has an AOF tail"
);
}
else
if
(
!
strcasecmp
(
auxkey
->
ptr
,
"aof-base"
))
{
long
long
isbase
=
strtoll
(
auxval
->
ptr
,
NULL
,
10
);
if
(
isbase
)
serverLog
(
LL_NOTICE
,
"RDB is base AOF"
);
}
else
if
(
!
strcasecmp
(
auxkey
->
ptr
,
"redis-bits"
))
{
}
else
if
(
!
strcasecmp
(
auxkey
->
ptr
,
"redis-bits"
))
{
/* Just ignored. */
/* Just ignored. */
}
else
{
}
else
{
...
@@ -3049,9 +3112,9 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin
...
@@ -3049,9 +3112,9 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin
* received from the master. In the latter case, the master is
* received from the master. In the latter case, the master is
* responsible for key expiry. If we would expire keys here, the
* responsible for key expiry. If we would expire keys here, the
* snapshot taken by the master may not be reflected on the slave.
* snapshot taken by the master may not be reflected on the slave.
* Similarly if the
RDB is the preamble of an AOF file
, we want to
* Similarly
,
if the
base AOF is RDB format
, we want to
load all
*
load all
the keys
as
they are, since the log of operations
later
* the keys they are, since the log of operations
in the incr AOF
* assume to work in
an
exact keyspace state. */
*
is
assume
d
to work in
the
exact keyspace state. */
if
(
val
==
NULL
)
{
if
(
val
==
NULL
)
{
/* Since we used to have bug that could lead to empty keys
/* Since we used to have bug that could lead to empty keys
* (See #8453), we rather not fail when empty key is encountered
* (See #8453), we rather not fail when empty key is encountered
...
...
src/rdb.h
View file @
a83e3663
...
@@ -94,10 +94,11 @@
...
@@ -94,10 +94,11 @@
#define RDB_TYPE_HASH_LISTPACK 16
#define RDB_TYPE_HASH_LISTPACK 16
#define RDB_TYPE_ZSET_LISTPACK 17
#define RDB_TYPE_ZSET_LISTPACK 17
#define RDB_TYPE_LIST_QUICKLIST_2 18
#define RDB_TYPE_LIST_QUICKLIST_2 18
#define RDB_TYPE_STREAM_LISTPACKS_2 19
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
/* Test if a type is an object type. */
/* Test if a type is an object type. */
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 1
8
))
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 1
9
))
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
#define RDB_OPCODE_FUNCTION 246
/* engine data */
#define RDB_OPCODE_FUNCTION 246
/* engine data */
...
...
Prev
1
2
3
4
5
6
7
8
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