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
fb4e0d40
Unverified
Commit
fb4e0d40
authored
Apr 05, 2022
by
Oran Agra
Committed by
GitHub
Apr 05, 2022
Browse files
Merge pull request #10532 from oranagra/7.0-rc3
Release 7.0 rc3
parents
d2b5a579
8b242ef9
Changes
171
Expand all
Hide whitespace changes
Inline
Side-by-side
.github/workflows/ci.yml
View file @
fb4e0d40
...
...
@@ -7,7 +7,7 @@ jobs:
test-ubuntu-latest
:
runs-on
:
ubuntu-latest
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
make
# Fail build if there are warnings
# build with TLS just for compilation coverage
...
...
@@ -22,7 +22,7 @@ jobs:
test-sanitizer-address
:
runs-on
:
ubuntu-latest
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
make
run
:
make SANITIZER=address REDIS_CFLAGS='-Werror'
-
name
:
testprep
...
...
@@ -36,7 +36,7 @@ jobs:
runs-on
:
ubuntu-latest
container
:
debian:oldoldstable
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
make
run
:
|
apt-get update && apt-get install -y build-essential
...
...
@@ -45,14 +45,14 @@ jobs:
build-macos-latest
:
runs-on
:
macos-latest
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
make
run
:
make REDIS_CFLAGS='-Werror'
build-32bit
:
runs-on
:
ubuntu-latest
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
make
run
:
|
sudo apt-get update && sudo apt-get install libc6-dev-i386
...
...
@@ -61,7 +61,7 @@ jobs:
build-libc-malloc
:
runs-on
:
ubuntu-latest
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
make
run
:
make REDIS_CFLAGS='-Werror' MALLOC=libc
...
...
@@ -69,7 +69,7 @@ jobs:
runs-on
:
ubuntu-latest
container
:
centos:7
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
make
run
:
|
yum -y install gcc make
...
...
.github/workflows/codeql-analysis.yml
View file @
fb4e0d40
...
...
@@ -4,7 +4,7 @@ on:
push
:
pull_request
:
schedule
:
# run weekly new vulnerability was added to the
the
database
# run weekly new vulnerability was added to the database
-
cron
:
'
0
0
*
*
0'
jobs
:
...
...
@@ -20,7 +20,7 @@ jobs:
steps
:
-
name
:
Checkout repository
uses
:
actions/checkout@v
2
uses
:
actions/checkout@v
3
-
name
:
Initialize CodeQL
uses
:
github/codeql-action/init@v1
...
...
.github/workflows/daily.yml
View file @
fb4e0d40
This diff is collapsed.
Click to expand it.
.github/workflows/external.yml
View file @
fb4e0d40
...
...
@@ -12,7 +12,7 @@ jobs:
if
:
github.event_name != 'schedule' || github.repository == 'redis/redis'
timeout-minutes
:
14400
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
Build
run
:
make REDIS_CFLAGS=-Werror
-
name
:
Start redis-server
...
...
@@ -36,7 +36,7 @@ jobs:
if
:
github.event_name != 'schedule' || github.repository == 'redis/redis'
timeout-minutes
:
14400
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
Build
run
:
make REDIS_CFLAGS=-Werror
-
name
:
Start redis-server
...
...
@@ -63,7 +63,7 @@ jobs:
if
:
github.event_name != 'schedule' || github.repository == 'redis/redis'
timeout-minutes
:
14400
steps
:
-
uses
:
actions/checkout@v
2
-
uses
:
actions/checkout@v
3
-
name
:
Build
run
:
make REDIS_CFLAGS=-Werror
-
name
:
Start redis-server
...
...
.github/workflows/spell-check.yml
View file @
fb4e0d40
...
...
@@ -16,10 +16,10 @@ jobs:
steps
:
-
name
:
Checkout repository
uses
:
actions/checkout@v
2
uses
:
actions/checkout@v
3
-
name
:
pip cache
uses
:
actions/cache@v
2
uses
:
actions/cache@v
3
with
:
path
:
~/.cache/pip
key
:
${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
...
...
00-RELEASENOTES
View file @
fb4e0d40
================================================================================
Redis 7.0 RC1 Released Mon Feb 28 12:00:00 IST 2022
Redis 7.0 RC3 Released Tue Apr 5 12:00:00 IST 2022
================================================================================
Upgrade urgency LOW: This is another Release Candidate of Redis 7.0.
New Features
============
New administrative and introspection commands and command arguments
-------------------------------------------------------------------
* CLUSTER SHARDS command deprecates CLUSTER SLOTS (#10293)
Potentially Breaking Changes
============================
* CONFIG GET response returned in a non-deterministic order.
It's possible that a client was relying on configs order (#10323)
* SORT / SORT_RO commands reject keys access patterns in GET and BY if ACL
doesn't grant the command full keyspace access (#10340)
* FUNCTION LOAD command introduced in 7.0-RC1 was stripped of the ENGINE, and
NAME arguments which are now part of the script itself. The DESCRIPTION
argument was completely removed (#10500)
* Set disable-thp config to be immutable (#10409)
Performance and resource utilization improvements
=================================================
* Optimize performance and memory usage on replicas (#10413)
* A faster and more robust code of zslRandomLevel using RAND_MAX (#5539)
Changes in CLI tools
====================
* redis-cli: Use exit code 1 on error (#10468)
* redis-cli: Do DNS lookup before sending CLUSTER MEET (#10436)
* redis-benchmark: Fix --cluster with IPv6. (#10393)
* redis-cli: Better --json Unicode support and --quoted-json (#10286)
INFO fields and introspection changes
=====================================
* MEMORY STATS: Show cluster.links memory usage (#10302)
Module API changes
==================
* APIs for exposing module configs to config file and CONFIG command (#10285)
* Add an event notifying about configuration changes (#10311)
* Add API for redacting command arguments from SLOWLOG and MONITOR (#10425)
* RM_Call: new flags for script mode compatibility, no writes, and error replies (#10372)
Bug Fixes
=========
* Sentinel: Fix no reconnect after auth-pass is changed (#10400)
* Cluster: Fix race condition: Turn into replica on SETSLOT (#10489, #10381)
* XREADGROUP: Unblock client when the stream key is deleted (#10306)
Fixes for issue in previous release candidates of Redis 7.0
-----------------------------------------------------------
* ACL DRYRUN does not validate the verified command args. (#10405)
* ACL DRYRUN returns the tested common permission error (#10359)
* Incorrect parsing of hostname information from nodes.conf (#10435)
* BITSET and BITFIELD SET should propagate even if just length changed (#10459)
* SHUTDOWN, Fix a possible crash when the shutdown was aborted (#10440)
* Script should not allow may-replicate commands when client pause write (#10364)
* Optimization tracking memory usage from i/o threads. (#10401)
* Initialize help when using redis-cli help or redis-cli ? (#10382)
* Dismiss COW of client output buffer now that it's dynamic (#10371)
* Fix memory corruption when EVAL fails before being processed (#10519)
================================================================================
Redis 7.0 RC2 Released Mon Feb 28 12:00:00 IST 2022
================================================================================
Upgrade urgency LOW: This is another Release Candidate of Redis 7.0.
...
...
@@ -439,6 +521,7 @@ A special thank you for the amount of work put into this release by:
- Viktor Söderqvist
- Wang Yuan
- Harkrishn Patro
- Nick Chun
- Ozan Tezcan
- Wen Hui
- Huang Zhw
...
...
@@ -449,8 +532,10 @@ A special thank you for the amount of work put into this release by:
- Itamar Haber
- Zhao Zhao
- Itay Perry
- Moti Cohen
- Ning Sun
- zhugezy
- Ran Shidlansik
- menwen
- Andy Pan
redis.conf
View file @
fb4e0d40
...
...
@@ -126,7 +126,7 @@ protected-mode yes
#
# no - Block for any connection (remain immutable)
# yes - Allow for any connection (no protection)
# local - Allow only for local
local
connections. Ones originating from the
# local - Allow only for local connections. Ones originating from the
# IPv4 address (127.0.0.1), IPv6 address (::1) or Unix domain sockets.
#
# enable-protected-configs no
...
...
@@ -627,7 +627,7 @@ repl-diskless-sync-max-replicas 0
#
# In many cases the disk is slower than the network, and storing and loading
# the RDB file may increase replication time (and even increase the master's
# Copy on Write memory and
salve
buffers).
# Copy on Write memory and
replica
buffers).
# However, parsing the RDB file directly from the socket may mean that we have
# to flush the contents of the current database before the full rdb was
# received. For this reason we have the following options:
...
...
@@ -1224,7 +1224,7 @@ replica-lazy-flush no
lazyfree
-
lazy
-
user
-
del
no
# FLUSHDB, FLUSHALL,
and
SCRIPT FLUSH support both asynchronous and synchronous
# FLUSHDB, FLUSHALL, SCRIPT
FLUSH and FUNCTION
FLUSH support both asynchronous and synchronous
# deletion, which can be controlled by passing the [SYNC|ASYNC] flags into the
# commands. When neither flag is passed, this directive will be used to determine
# if the data should be deleted asynchronously.
...
...
@@ -1287,7 +1287,7 @@ lazyfree-lazy-user-flush no
# attempt to have background child processes killed before all others, and
# replicas killed before masters.
#
# Redis supports th
r
ee options:
# Redis supports the
s
e options:
#
# no: Don't make changes to oom-score-adj (default).
# yes: Alias to "relative" see below.
...
...
@@ -1640,7 +1640,7 @@ aof-timestamp-enabled no
# cluster-replica-no-failover no
# This option, when set to yes, allows nodes to serve read traffic while the
#
the
cluster is in a down state, as long as it believes it owns the slots.
# cluster is in a down state, as long as it believes it owns the slots.
#
# This is useful for two cases. The first case is for when an application
# doesn't require consistency of data during node failures or network partitions.
...
...
@@ -1958,7 +1958,7 @@ activerehashing yes
# The limit can be set differently for the three different classes of clients:
#
# normal -> normal clients including MONITOR clients
# replica
-> replica clients
# replica -> replica clients
# pubsub -> clients subscribed to at least one pubsub channel or pattern
#
# The syntax of every client-output-buffer-limit directive is the following:
...
...
@@ -2164,7 +2164,7 @@ rdb-save-incremental-fsync yes
# defragmentation process. If you are not sure about what they mean it is
# a good idea to leave the defaults untouched.
#
Enabled a
ctive defragmentation
#
A
ctive defragmentation
is disabled by default
# activedefrag no
# Minimum amount of fragmentation waste to start active defrag
...
...
runtest-moduleapi
View file @
fb4e0d40
...
...
@@ -20,6 +20,7 @@ $TCLSH tests/test_helper.tcl \
--single
unit/moduleapi/fork
\
--single
unit/moduleapi/testrdb
\
--single
unit/moduleapi/infotest
\
--single
unit/moduleapi/moduleconfigs
\
--single
unit/moduleapi/infra
\
--single
unit/moduleapi/propagate
\
--single
unit/moduleapi/hooks
\
...
...
src/acl.c
View file @
fb4e0d40
...
...
@@ -120,10 +120,7 @@ typedef struct {
* understand if the command can be executed. */
uint64_t
allowed_commands
[
USER_COMMAND_BITS_COUNT
/
64
];
/* allowed_firstargs is used by ACL rules to block access to a command unless a
* specific argv[1] is given (or argv[2] in case it is applied on a sub-command).
* For example, a user can use the rule "-select +select|0" to block all
* SELECT commands, except "SELECT 0".
* And for a sub-command: "+config -config|set +config|set|loglevel"
* specific argv[1] is given.
*
* For each command ID (corresponding to the command bit set in allowed_commands),
* This array points to an array of SDS strings, terminated by a NULL pointer,
...
...
@@ -1531,6 +1528,37 @@ static int ACLSelectorCheckKey(aclSelector *selector, const char *key, int keyle
return
ACL_DENIED_KEY
;
}
/* Checks if the provided selector selector has access specified in flags
* to all keys in the keyspace. For example, CMD_KEY_READ access requires either
* '%R~*', '~*', or allkeys to be granted to the selector. Returns 1 if all
* the access flags are satisfied with this selector or 0 otherwise.
*/
static
int
ACLSelectorHasUnrestrictedKeyAccess
(
aclSelector
*
selector
,
int
flags
)
{
/* The selector can access any key */
if
(
selector
->
flags
&
SELECTOR_FLAG_ALLKEYS
)
return
1
;
listIter
li
;
listNode
*
ln
;
listRewind
(
selector
->
patterns
,
&
li
);
int
access_flags
=
0
;
if
(
flags
&
CMD_KEY_ACCESS
)
access_flags
|=
ACL_READ_PERMISSION
;
if
(
flags
&
CMD_KEY_INSERT
)
access_flags
|=
ACL_WRITE_PERMISSION
;
if
(
flags
&
CMD_KEY_DELETE
)
access_flags
|=
ACL_WRITE_PERMISSION
;
if
(
flags
&
CMD_KEY_UPDATE
)
access_flags
|=
ACL_WRITE_PERMISSION
;
/* Test this key against every pattern. */
while
((
ln
=
listNext
(
&
li
)))
{
keyPattern
*
pattern
=
listNodeValue
(
ln
);
if
((
pattern
->
flags
&
access_flags
)
!=
access_flags
)
continue
;
if
(
!
strcmp
(
pattern
->
pattern
,
"*"
))
{
return
1
;
}
}
return
0
;
}
/* Checks a channel against a provided list of channels. The is_pattern
* argument should only be used when subscribing (not when publishing)
* and controls whether the input channel is evaluated as a channel pattern
...
...
@@ -1675,6 +1703,39 @@ int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags) {
return
ACL_DENIED_KEY
;
}
/* Checks if the user can execute the given command with the added restriction
* it must also have the access specified in flags to any key in the key space.
* For example, CMD_KEY_READ access requires either '%R~*', '~*', or allkeys to be
* granted in addition to the access required by the command. Returns 1
* if the user has access or 0 otherwise.
*/
int
ACLUserCheckCmdWithUnrestrictedKeyAccess
(
user
*
u
,
struct
redisCommand
*
cmd
,
robj
**
argv
,
int
argc
,
int
flags
)
{
listIter
li
;
listNode
*
ln
;
int
local_idxptr
;
/* If there is no associated user, the connection can run anything. */
if
(
u
==
NULL
)
return
1
;
/* For multiple selectors, we cache the key result in between selector
* calls to prevent duplicate lookups. */
aclKeyResultCache
cache
;
initACLKeyResultCache
(
&
cache
);
/* Check each selector sequentially */
listRewind
(
u
->
selectors
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
aclSelector
*
s
=
(
aclSelector
*
)
listNodeValue
(
ln
);
int
acl_retval
=
ACLSelectorCheckCmd
(
s
,
cmd
,
argv
,
argc
,
&
local_idxptr
,
&
cache
);
if
(
acl_retval
==
ACL_OK
&&
ACLSelectorHasUnrestrictedKeyAccess
(
s
,
flags
))
{
cleanupACLKeyResultCache
(
&
cache
);
return
1
;
}
}
cleanupACLKeyResultCache
(
&
cache
);
return
0
;
}
/* Check if the channel can be accessed by the client according to
* the ACLs associated with the specified user.
*
...
...
@@ -2411,6 +2472,22 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username
}
}
const
char
*
getAclErrorMessage
(
int
acl_res
)
{
/* Notice that a variant of this code also exists on aclCommand so
* it also need to be updated on changed. */
switch
(
acl_res
)
{
case
ACL_DENIED_CMD
:
return
"can't run this command or subcommand"
;
case
ACL_DENIED_KEY
:
return
"can't access at least one of the keys mentioned in the command arguments"
;
case
ACL_DENIED_CHANNEL
:
return
"can't publish to the channel mentioned in the command"
;
default:
return
"lacking the permissions for the command"
;
}
serverPanic
(
"Reached deadcode on getAclErrorMessage"
);
}
/* =============================================================================
* ACL related commands
* ==========================================================================*/
...
...
@@ -2793,13 +2870,22 @@ setuser_cleanup:
return
;
}
if
((
cmd
->
arity
>
0
&&
cmd
->
arity
!=
c
->
argc
-
3
)
||
(
c
->
argc
-
3
<
-
cmd
->
arity
))
{
addReplyErrorFormat
(
c
,
"wrong number of arguments for '%s' command"
,
cmd
->
fullname
);
return
;
}
int
idx
;
int
result
=
ACLCheckAllUserCommandPerm
(
u
,
cmd
,
c
->
argv
+
3
,
c
->
argc
-
3
,
&
idx
);
/* Notice that a variant of this code also exists on getAclErrorMessage so
* it also need to be updated on changed. */
if
(
result
!=
ACL_OK
)
{
sds
err
=
sdsempty
();
if
(
result
==
ACL_DENIED_CMD
)
{
err
=
sdscatfmt
(
err
,
"This user has no permissions to run "
"the '%s' command"
,
c
->
cmd
->
fullname
);
"the '%s' command"
,
cmd
->
fullname
);
}
else
if
(
result
==
ACL_DENIED_KEY
)
{
err
=
sdscatfmt
(
err
,
"This user has no permissions to access "
"the '%s' key"
,
c
->
argv
[
idx
+
3
]
->
ptr
);
...
...
src/aof.c
View file @
fb4e0d40
...
...
@@ -813,10 +813,10 @@ int openNewIncrAofForAppend(void) {
* AOFs has not reached the limit threshold.
* */
#define AOF_REWRITE_LIMITE_THRESHOLD 3
#define AOF_REWRITE_LIMITE_
N
AX_MINUTES 60
/* 1 hour */
#define AOF_REWRITE_LIMITE_
M
AX_MINUTES 60
/* 1 hour */
int
aofRewriteLimited
(
void
)
{
int
limit
=
0
;
static
int
limit_del
e
y_minutes
=
0
;
static
int
limit_del
a
y_minutes
=
0
;
static
time_t
next_rewrite_time
=
0
;
unsigned
long
incr_aof_num
=
listLength
(
server
.
aof_manifest
->
incr_aof_list
);
...
...
@@ -824,25 +824,25 @@ int aofRewriteLimited(void) {
if
(
server
.
unixtime
<
next_rewrite_time
)
{
limit
=
1
;
}
else
{
if
(
limit_del
e
y_minutes
==
0
)
{
if
(
limit_del
a
y_minutes
==
0
)
{
limit
=
1
;
limit_del
e
y_minutes
=
1
;
limit_del
a
y_minutes
=
1
;
}
else
{
limit_del
e
y_minutes
*=
2
;
limit_del
a
y_minutes
*=
2
;
}
if
(
limit_del
e
y_minutes
>
AOF_REWRITE_LIMITE_
N
AX_MINUTES
)
{
limit_del
e
y_minutes
=
AOF_REWRITE_LIMITE_
N
AX_MINUTES
;
if
(
limit_del
a
y_minutes
>
AOF_REWRITE_LIMITE_
M
AX_MINUTES
)
{
limit_del
a
y_minutes
=
AOF_REWRITE_LIMITE_
M
AX_MINUTES
;
}
next_rewrite_time
=
server
.
unixtime
+
limit_del
e
y_minutes
*
60
;
next_rewrite_time
=
server
.
unixtime
+
limit_del
a
y_minutes
*
60
;
serverLog
(
LL_WARNING
,
"Background AOF rewrite has repeatedly failed %ld times and triggered the limit, will retry in %d minutes"
,
incr_aof_num
,
limit_del
e
y_minutes
);
incr_aof_num
,
limit_del
a
y_minutes
);
}
}
else
{
limit_del
e
y_minutes
=
0
;
limit_del
a
y_minutes
=
0
;
next_rewrite_time
=
0
;
}
...
...
@@ -2142,19 +2142,9 @@ static int rewriteFunctions(rio *aof) {
dictEntry
*
entry
=
NULL
;
while
((
entry
=
dictNext
(
iter
)))
{
functionLibInfo
*
li
=
dictGetVal
(
entry
);
if
(
li
->
desc
)
{
if
(
rioWrite
(
aof
,
"*7
\r\n
"
,
4
)
==
0
)
goto
werr
;
}
else
{
if
(
rioWrite
(
aof
,
"*5
\r\n
"
,
4
)
==
0
)
goto
werr
;
}
if
(
rioWrite
(
aof
,
"*3
\r\n
"
,
4
)
==
0
)
goto
werr
;
char
function_load
[]
=
"$8
\r\n
FUNCTION
\r\n
$4
\r\n
LOAD
\r\n
"
;
if
(
rioWrite
(
aof
,
function_load
,
sizeof
(
function_load
)
-
1
)
==
0
)
goto
werr
;
if
(
rioWriteBulkString
(
aof
,
li
->
ei
->
name
,
sdslen
(
li
->
ei
->
name
))
==
0
)
goto
werr
;
if
(
rioWriteBulkString
(
aof
,
li
->
name
,
sdslen
(
li
->
name
))
==
0
)
goto
werr
;
if
(
li
->
desc
)
{
if
(
rioWriteBulkString
(
aof
,
"description"
,
11
)
==
0
)
goto
werr
;
if
(
rioWriteBulkString
(
aof
,
li
->
desc
,
sdslen
(
li
->
desc
))
==
0
)
goto
werr
;
}
if
(
rioWriteBulkString
(
aof
,
li
->
code
,
sdslen
(
li
->
code
))
==
0
)
goto
werr
;
}
dictReleaseIterator
(
iter
);
...
...
src/bitops.c
View file @
fb4e0d40
...
...
@@ -478,19 +478,21 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
* so that the 'maxbit' bit can be addressed. The object is finally
* returned. Otherwise if the key holds a wrong type NULL is returned and
* an error is sent to the client. */
robj
*
lookupStringForBitCommand
(
client
*
c
,
uint64_t
maxbit
,
int
*
created
)
{
robj
*
lookupStringForBitCommand
(
client
*
c
,
uint64_t
maxbit
,
int
*
dirty
)
{
size_t
byte
=
maxbit
>>
3
;
robj
*
o
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
1
]);
if
(
checkType
(
c
,
o
,
OBJ_STRING
))
return
NULL
;
if
(
dirty
)
*
dirty
=
0
;
if
(
o
==
NULL
)
{
if
(
created
)
*
created
=
1
;
o
=
createObject
(
OBJ_STRING
,
sdsnewlen
(
NULL
,
byte
+
1
));
dbAdd
(
c
->
db
,
c
->
argv
[
1
],
o
);
if
(
dirty
)
*
dirty
=
1
;
}
else
{
if
(
created
)
*
created
=
0
;
o
=
dbUnshareStringValue
(
c
->
db
,
c
->
argv
[
1
],
o
);
size_t
oldlen
=
sdslen
(
o
->
ptr
);
o
->
ptr
=
sdsgrowzero
(
o
->
ptr
,
byte
+
1
);
if
(
dirty
&&
oldlen
!=
sdslen
(
o
->
ptr
))
*
dirty
=
1
;
}
return
o
;
}
...
...
@@ -547,8 +549,8 @@ void setbitCommand(client *c) {
return
;
}
int
created
;
if
((
o
=
lookupStringForBitCommand
(
c
,
bitoffset
,
&
created
))
==
NULL
)
return
;
int
dirty
;
if
((
o
=
lookupStringForBitCommand
(
c
,
bitoffset
,
&
dirty
))
==
NULL
)
return
;
/* Get current values */
byte
=
bitoffset
>>
3
;
...
...
@@ -556,10 +558,10 @@ void setbitCommand(client *c) {
bit
=
7
-
(
bitoffset
&
0x7
);
bitval
=
byteval
&
(
1
<<
bit
);
/* Either it is newly created, or the bit changes before and after.
/* Either it is newly created,
changed length,
or the bit changes before and after.
* Note that the bitval here is actually a decimal number.
* So we need to use `!!` to convert it to 0 or 1 for comparison. */
if
(
created
||
(
!!
bitval
!=
on
))
{
if
(
dirty
||
(
!!
bitval
!=
on
))
{
/* Update byte with new bit value. */
byteval
&=
~
(
1
<<
bit
);
byteval
|=
((
on
&
0x1
)
<<
bit
);
...
...
@@ -1028,7 +1030,7 @@ struct bitfieldOp {
void
bitfieldGeneric
(
client
*
c
,
int
flags
)
{
robj
*
o
;
uint64_t
bitoffset
;
int
j
,
numops
=
0
,
changes
=
0
,
created
=
0
;
int
j
,
numops
=
0
,
changes
=
0
,
dirty
=
0
;
struct
bitfieldOp
*
ops
=
NULL
;
/* Array of ops to execute at end. */
int
owtype
=
BFOVERFLOW_WRAP
;
/* Overflow type. */
int
readonly
=
1
;
...
...
@@ -1122,7 +1124,7 @@ void bitfieldGeneric(client *c, int flags) {
/* Lookup by making room up to the farthest bit reached by
* this operation. */
if
((
o
=
lookupStringForBitCommand
(
c
,
highest_write_offset
,
&
created
))
==
NULL
)
{
highest_write_offset
,
&
dirty
))
==
NULL
)
{
zfree
(
ops
);
return
;
}
...
...
@@ -1172,7 +1174,7 @@ void bitfieldGeneric(client *c, int flags) {
setSignedBitfield
(
o
->
ptr
,
thisop
->
offset
,
thisop
->
bits
,
newval
);
if
(
created
||
(
oldval
!=
newval
))
if
(
dirty
||
(
oldval
!=
newval
))
changes
++
;
}
else
{
addReplyNull
(
c
);
...
...
@@ -1204,7 +1206,7 @@ void bitfieldGeneric(client *c, int flags) {
setUnsignedBitfield
(
o
->
ptr
,
thisop
->
offset
,
thisop
->
bits
,
newval
);
if
(
created
||
(
oldval
!=
newval
))
if
(
dirty
||
(
oldval
!=
newval
))
changes
++
;
}
else
{
addReplyNull
(
c
);
...
...
src/blocked.c
View file @
fb4e0d40
...
...
@@ -141,12 +141,7 @@ void processUnblockedClients(void) {
* the code is conceptually more correct this way. */
if
(
!
(
c
->
flags
&
CLIENT_BLOCKED
))
{
/* If we have a queued command, execute it now. */
if
(
processPendingCommandsAndResetClient
(
c
)
==
C_OK
)
{
/* Now process client if it has more data in it's buffer. */
if
(
c
->
querybuf
&&
sdslen
(
c
->
querybuf
)
>
0
)
{
if
(
processInputBuffer
(
c
)
==
C_ERR
)
c
=
NULL
;
}
}
else
{
if
(
processPendingCommandAndInputBuffer
(
c
)
==
C_ERR
)
{
c
=
NULL
;
}
}
...
...
@@ -204,7 +199,7 @@ void unblockClient(client *c) {
* we do not do it immediately after the command returns (when the
* client got blocked) in order to be still able to access the argument
* vector from module callbacks and updateStatsOnUnblock. */
if
(
c
->
btype
!=
BLOCKED_POSTPONE
)
{
if
(
c
->
btype
!=
BLOCKED_POSTPONE
&&
c
->
btype
!=
BLOCKED_SHUTDOWN
)
{
freeClientOriginalArgv
(
c
);
resetClient
(
c
);
}
...
...
@@ -288,25 +283,24 @@ void disconnectAllBlockedClients(void) {
* when there may be clients blocked on a list key, and there may be new
* data to fetch (the key is ready). */
void
serveClientsBlockedOnListKey
(
robj
*
o
,
readyList
*
rl
)
{
/* Optimization: If no clients are in type BLOCKED_LIST,
* we can skip this loop. */
if
(
!
server
.
blocked_clients_by_type
[
BLOCKED_LIST
])
return
;
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
dictEntry
*
de
=
dictFind
(
rl
->
db
->
blocking_keys
,
rl
->
key
);
if
(
de
)
{
list
*
clients
=
dictGetVal
(
de
);
int
numclients
=
listLength
(
clients
);
int
deleted
=
0
;
while
(
numclients
--
)
{
listNode
*
clientnode
=
listFirst
(
clients
);
client
*
receiver
=
clientnode
->
value
;
listNode
*
ln
;
listIter
li
;
listRewind
(
clients
,
&
li
);
if
(
receiver
->
btype
!=
BLOCKED_LIST
)
{
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listRotateHeadToTail
(
clients
);
continue
;
}
while
((
ln
=
listNext
(
&
li
)))
{
client
*
receiver
=
listNodeValue
(
ln
);
if
(
receiver
->
btype
!=
BLOCKED_LIST
)
continue
;
int
deleted
=
0
;
robj
*
dstkey
=
receiver
->
bpop
.
target
;
int
wherefrom
=
receiver
->
bpop
.
blockpos
.
wherefrom
;
int
whereto
=
receiver
->
bpop
.
blockpos
.
whereto
;
...
...
@@ -342,25 +336,24 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
* when there may be clients blocked on a sorted set key, and there may be new
* data to fetch (the key is ready). */
void
serveClientsBlockedOnSortedSetKey
(
robj
*
o
,
readyList
*
rl
)
{
/* Optimization: If no clients are in type BLOCKED_ZSET,
* we can skip this loop. */
if
(
!
server
.
blocked_clients_by_type
[
BLOCKED_ZSET
])
return
;
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
dictEntry
*
de
=
dictFind
(
rl
->
db
->
blocking_keys
,
rl
->
key
);
if
(
de
)
{
list
*
clients
=
dictGetVal
(
de
);
int
numclients
=
listLength
(
clients
);
int
deleted
=
0
;
while
(
numclients
--
)
{
listNode
*
clientnode
=
listFirst
(
clients
);
client
*
receiver
=
clientnode
->
value
;
listNode
*
ln
;
listIter
li
;
listRewind
(
clients
,
&
li
);
if
(
receiver
->
btype
!=
BLOCKED_ZSET
)
{
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listRotateHeadToTail
(
clients
);
continue
;
}
while
((
ln
=
listNext
(
&
li
)))
{
client
*
receiver
=
listNodeValue
(
ln
);
if
(
receiver
->
btype
!=
BLOCKED_ZSET
)
continue
;
int
deleted
=
0
;
long
llen
=
zsetLength
(
o
);
long
count
=
receiver
->
bpop
.
count
;
int
where
=
receiver
->
bpop
.
blockpos
.
wherefrom
;
...
...
@@ -407,6 +400,10 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
* when there may be clients blocked on a stream key, and there may be new
* data to fetch (the key is ready). */
void
serveClientsBlockedOnStreamKey
(
robj
*
o
,
readyList
*
rl
)
{
/* Optimization: If no clients are in type BLOCKED_STREAM,
* we can skip this loop. */
if
(
!
server
.
blocked_clients_by_type
[
BLOCKED_STREAM
])
return
;
dictEntry
*
de
=
dictFind
(
rl
->
db
->
blocking_keys
,
rl
->
key
);
stream
*
s
=
o
->
ptr
;
...
...
@@ -520,30 +517,21 @@ unblock_receiver:
* see if the key is really able to serve the client, and in that case,
* unblock it. */
void
serveClientsBlockedOnKeyByModule
(
readyList
*
rl
)
{
dictEntry
*
de
;
/* Optimization: If no clients are in type BLOCKED_MODULE,
* we can skip this loop. */
if
(
!
server
.
blocked_clients_by_type
[
BLOCKED_MODULE
])
return
;
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
de
=
dictFind
(
rl
->
db
->
blocking_keys
,
rl
->
key
);
dictEntry
*
de
=
dictFind
(
rl
->
db
->
blocking_keys
,
rl
->
key
);
if
(
de
)
{
list
*
clients
=
dictGetVal
(
de
);
int
numclients
=
listLength
(
clients
);
while
(
numclients
--
)
{
listNode
*
clientnode
=
listFirst
(
clients
);
client
*
receiver
=
clientnode
->
value
;
/* Put at the tail, so that at the next call
* we'll not run into it again: clients here may not be
* ready to be served, so they'll remain in the list
* sometimes. We want also be able to skip clients that are
* not blocked for the MODULE type safely. */
listRotateHeadToTail
(
clients
);
listNode
*
ln
;
listIter
li
;
listRewind
(
clients
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
client
*
receiver
=
listNodeValue
(
ln
);
if
(
receiver
->
btype
!=
BLOCKED_MODULE
)
continue
;
/* Note that if *this* client cannot be served by this key,
...
...
@@ -566,6 +554,49 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
}
}
/* Helper function for handleClientsBlockedOnKeys(). This function is called
* when there may be clients blocked, via XREADGROUP, on an existing stream which
* was deleted. We need to unblock the clients in that case.
* The idea is that a client that is blocked via XREADGROUP is different from
* any other blocking type in the sense that it depends on the existence of both
* the key and the group. Even if the key is deleted and then revived with XADD
* it won't help any clients blocked on XREADGROUP because the group no longer
* exist, so they would fail with -NOGROUP anyway.
* The conclusion is that it's better to unblock these client (with error) upon
* the deletion of the key, rather than waiting for the first XADD. */
void
unblockDeletedStreamReadgroupClients
(
readyList
*
rl
)
{
/* Optimization: If no clients are in type BLOCKED_STREAM,
* we can skip this loop. */
if
(
!
server
.
blocked_clients_by_type
[
BLOCKED_STREAM
])
return
;
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
dictEntry
*
de
=
dictFind
(
rl
->
db
->
blocking_keys
,
rl
->
key
);
if
(
de
)
{
list
*
clients
=
dictGetVal
(
de
);
listNode
*
ln
;
listIter
li
;
listRewind
(
clients
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
client
*
receiver
=
listNodeValue
(
ln
);
if
(
receiver
->
btype
!=
BLOCKED_STREAM
||
!
receiver
->
bpop
.
xread_group
)
continue
;
long
long
prev_error_replies
=
server
.
stat_total_error_replies
;
client
*
old_client
=
server
.
current_client
;
server
.
current_client
=
receiver
;
monotime
replyTimer
;
elapsedStart
(
&
replyTimer
);
addReplyError
(
receiver
,
"-UNBLOCKED the stream key no longer exists"
);
updateStatsOnUnblock
(
receiver
,
0
,
elapsedUs
(
replyTimer
),
server
.
stat_total_error_replies
!=
prev_error_replies
);
unblockClient
(
receiver
);
afterCommand
(
receiver
);
server
.
current_client
=
old_client
;
}
}
}
/* This function should be called by Redis every time a single command,
* a MULTI/EXEC block, or a Lua script, terminated its execution after
* being called by a client. It handles serving clients blocked in
...
...
@@ -624,17 +655,27 @@ void handleClientsBlockedOnKeys(void) {
/* Serve clients blocked on the key. */
robj
*
o
=
lookupKeyReadWithFlags
(
rl
->
db
,
rl
->
key
,
LOOKUP_NONOTIFY
|
LOOKUP_NOSTATS
);
if
(
o
!=
NULL
)
{
if
(
o
->
type
==
OBJ_LIST
)
int
objtype
=
o
->
type
;
if
(
objtype
==
OBJ_LIST
)
serveClientsBlockedOnListKey
(
o
,
rl
);
else
if
(
o
->
type
==
OBJ_ZSET
)
else
if
(
o
bj
type
==
OBJ_ZSET
)
serveClientsBlockedOnSortedSetKey
(
o
,
rl
);
else
if
(
o
->
type
==
OBJ_STREAM
)
else
if
(
o
bj
type
==
OBJ_STREAM
)
serveClientsBlockedOnStreamKey
(
o
,
rl
);
/* We want to serve clients blocked on module keys
* regardless of the object type: we don't know what the
* module is trying to accomplish right now. */
serveClientsBlockedOnKeyByModule
(
rl
);
/* If we have XREADGROUP clients blocked on this key, and
* the key is not a stream, it must mean that the key was
* overwritten by either SET or something like
* (MULTI, DEL key, SADD key e, EXEC).
* In this case we need to unblock all these clients. */
if
(
objtype
!=
OBJ_STREAM
)
unblockDeletedStreamReadgroupClients
(
rl
);
}
else
{
/* Unblock all XREADGROUP clients of this deleted key */
unblockDeletedStreamReadgroupClients
(
rl
);
/* Edge case: If lookupKeyReadWithFlags decides to expire the key we have to
* take care of the propagation here, because afterCommand wasn't called */
if
(
server
.
also_propagate
.
numops
>
0
)
...
...
@@ -823,4 +864,3 @@ void signalKeyAsReady(redisDb *db, robj *key, int type) {
incrRefCount
(
key
);
serverAssert
(
dictAdd
(
db
->
ready_keys
,
key
,
NULL
)
==
DICT_OK
);
}
src/call_reply.c
View file @
fb4e0d40
...
...
@@ -525,3 +525,18 @@ CallReply *callReplyCreate(sds reply, list *deferred_error_list, void *private_d
res
->
deferred_error_list
=
deferred_error_list
;
return
res
;
}
/* Create a new CallReply struct from the reply blob representing an error message.
* Automatically creating deferred_error_list and set a copy of the reply in it.
* Refer to callReplyCreate for detailed explanation. */
CallReply
*
callReplyCreateError
(
sds
reply
,
void
*
private_data
)
{
sds
err_buff
=
reply
;
if
(
err_buff
[
0
]
!=
'-'
)
{
err_buff
=
sdscatfmt
(
sdsempty
(),
"-ERR %S
\r\n
"
,
reply
);
sdsfree
(
reply
);
}
list
*
deferred_error_list
=
listCreate
();
listSetFreeMethod
(
deferred_error_list
,
(
void
(
*
)(
void
*
))
sdsfree
);
listAddNodeTail
(
deferred_error_list
,
sdsnew
(
err_buff
));
return
callReplyCreate
(
err_buff
,
deferred_error_list
,
private_data
);
}
src/call_reply.h
View file @
fb4e0d40
...
...
@@ -35,6 +35,7 @@
typedef
struct
CallReply
CallReply
;
CallReply
*
callReplyCreate
(
sds
reply
,
list
*
deferred_error_list
,
void
*
private_data
);
CallReply
*
callReplyCreateError
(
sds
reply
,
void
*
private_data
);
int
callReplyType
(
CallReply
*
rep
);
const
char
*
callReplyGetString
(
CallReply
*
rep
,
size_t
*
len
);
long
long
callReplyGetLongLong
(
CallReply
*
rep
);
...
...
src/cli_common.c
View file @
fb4e0d40
...
...
@@ -299,7 +299,7 @@ static sds percentDecode(const char *pe, size_t len) {
}
/* Parse a URI and extract the server connection information.
* URI scheme is based on the
the
provisional specification[1] excluding support
* URI scheme is based on the provisional specification[1] excluding support
* for query parameters. Valid URIs are:
* scheme: "redis://"
* authority: [[<username> ":"] <password> "@"] [<hostname> [":" <port>]]
...
...
@@ -371,3 +371,28 @@ void freeCliConnInfo(cliConnInfo connInfo){
if
(
connInfo
.
auth
)
sdsfree
(
connInfo
.
auth
);
if
(
connInfo
.
user
)
sdsfree
(
connInfo
.
user
);
}
/*
* Escape a Unicode string for JSON output (--json), following RFC 7159:
* https://datatracker.ietf.org/doc/html/rfc7159#section-7
*/
sds
escapeJsonString
(
sds
s
,
const
char
*
p
,
size_t
len
)
{
s
=
sdscatlen
(
s
,
"
\"
"
,
1
);
while
(
len
--
)
{
switch
(
*
p
)
{
case
'\\'
:
case
'"'
:
s
=
sdscatprintf
(
s
,
"
\\
%c"
,
*
p
);
break
;
case
'\n'
:
s
=
sdscatlen
(
s
,
"
\\
n"
,
2
);
break
;
case
'\f'
:
s
=
sdscatlen
(
s
,
"
\\
f"
,
2
);
break
;
case
'\r'
:
s
=
sdscatlen
(
s
,
"
\\
r"
,
2
);
break
;
case
'\t'
:
s
=
sdscatlen
(
s
,
"
\\
t"
,
2
);
break
;
case
'\b'
:
s
=
sdscatlen
(
s
,
"
\\
b"
,
2
);
break
;
default:
s
=
sdscatprintf
(
s
,(
*
p
>=
0
&&
*
p
<=
0x1f
)
?
"
\\
u%04x"
:
"%c"
,
*
p
);
}
p
++
;
}
return
sdscatlen
(
s
,
"
\"
"
,
1
);
}
src/cli_common.h
View file @
fb4e0d40
...
...
@@ -48,4 +48,7 @@ sds unquoteCString(char *str);
void
parseRedisUri
(
const
char
*
uri
,
const
char
*
tool_name
,
cliConnInfo
*
connInfo
,
int
*
tls_flag
);
void
freeCliConnInfo
(
cliConnInfo
connInfo
);
sds
escapeJsonString
(
sds
s
,
const
char
*
p
,
size_t
len
);
#endif
/* __CLICOMMON_H */
src/cluster.c
View file @
fb4e0d40
This diff is collapsed.
Click to expand it.
src/cluster.h
View file @
fb4e0d40
...
...
@@ -118,7 +118,9 @@ typedef struct clusterNode {
int
flags
;
/* CLUSTER_NODE_... */
uint64_t
configEpoch
;
/* Last configEpoch observed for this node */
unsigned
char
slots
[
CLUSTER_SLOTS
/
8
];
/* slots handled by this node */
sds
slots_info
;
/* Slots info represented by string. */
uint16_t
*
slot_info_pairs
;
/* Slots info represented as (start/end) pair (consecutive index). */
int
slot_info_pairs_count
;
/* Used number of slots in slot_info_pairs */
int
slot_info_pairs_alloc
;
/* Allocated number of slots in slot_info_pairs */
int
numslots
;
/* Number of slots handled by this node */
int
numslaves
;
/* Number of slave nodes, if this is a master */
struct
clusterNode
**
slaves
;
/* pointers to slave nodes */
...
...
@@ -375,7 +377,8 @@ void clusterInit(void);
void
clusterCron
(
void
);
void
clusterBeforeSleep
(
void
);
clusterNode
*
getNodeByQuery
(
client
*
c
,
struct
redisCommand
*
cmd
,
robj
**
argv
,
int
argc
,
int
*
hashslot
,
int
*
ask
);
clusterNode
*
clusterLookupNode
(
const
char
*
name
);
int
verifyClusterNodeId
(
const
char
*
name
,
int
length
);
clusterNode
*
clusterLookupNode
(
const
char
*
name
,
int
length
);
int
clusterRedirectBlockedClientIfNeeded
(
client
*
c
);
void
clusterRedirectClient
(
client
*
c
,
clusterNode
*
n
,
int
hashslot
,
int
error_code
);
void
migrateCloseTimedoutSockets
(
void
);
...
...
src/commands.c
View file @
fb4e0d40
This diff is collapsed.
Click to expand it.
src/commands/client-pause.json
View file @
fb4e0d40
...
...
@@ -8,10 +8,6 @@
"container"
:
"CLIENT"
,
"function"
:
"clientCommand"
,
"history"
:
[
[
"3.2.10"
,
"Client pause prevents client pause and key eviction as well."
],
[
"6.2.0"
,
"`CLIENT PAUSE WRITE` mode added along with the `mode` option."
...
...
Prev
1
2
3
4
5
…
9
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