Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
49816941
Unverified
Commit
49816941
authored
Feb 27, 2019
by
chendianqiang
Committed by
GitHub
Feb 27, 2019
Browse files
Merge pull request #2 from antirez/unstable
merge from redis
parents
68ceb466
f311a529
Changes
120
Hide whitespace changes
Inline
Side-by-side
src/t_set.c
View file @
49816941
...
@@ -207,7 +207,7 @@ sds setTypeNextObject(setTypeIterator *si) {
...
@@ -207,7 +207,7 @@ sds setTypeNextObject(setTypeIterator *si) {
* used field with values which are easy to trap if misused. */
* used field with values which are easy to trap if misused. */
int
setTypeRandomElement
(
robj
*
setobj
,
sds
*
sdsele
,
int64_t
*
llele
)
{
int
setTypeRandomElement
(
robj
*
setobj
,
sds
*
sdsele
,
int64_t
*
llele
)
{
if
(
setobj
->
encoding
==
OBJ_ENCODING_HT
)
{
if
(
setobj
->
encoding
==
OBJ_ENCODING_HT
)
{
dictEntry
*
de
=
dictGetRandomKey
(
setobj
->
ptr
);
dictEntry
*
de
=
dictGet
Fair
RandomKey
(
setobj
->
ptr
);
*
sdsele
=
dictGetKey
(
de
);
*
sdsele
=
dictGetKey
(
de
);
*
llele
=
-
123456789
;
/* Not needed. Defensive. */
*
llele
=
-
123456789
;
/* Not needed. Defensive. */
}
else
if
(
setobj
->
encoding
==
OBJ_ENCODING_INTSET
)
{
}
else
if
(
setobj
->
encoding
==
OBJ_ENCODING_INTSET
)
{
...
@@ -415,13 +415,13 @@ void spopWithCountCommand(client *c) {
...
@@ -415,13 +415,13 @@ void spopWithCountCommand(client *c) {
/* Make sure a key with the name inputted exists, and that it's type is
/* Make sure a key with the name inputted exists, and that it's type is
* indeed a set. Otherwise, return nil */
* indeed a set. Otherwise, return nil */
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
emptymultibulk
))
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
[
c
->
resp
]
))
==
NULL
||
checkType
(
c
,
set
,
OBJ_SET
))
return
;
==
NULL
||
checkType
(
c
,
set
,
OBJ_SET
))
return
;
/* If count is zero, serve an empty multibulk ASAP to avoid special
/* If count is zero, serve an empty multibulk ASAP to avoid special
* cases later. */
* cases later. */
if
(
count
==
0
)
{
if
(
count
==
0
)
{
addReply
(
c
,
shared
.
emptymultibulk
);
addReply
Null
(
c
);
return
;
return
;
}
}
...
@@ -455,7 +455,7 @@ void spopWithCountCommand(client *c) {
...
@@ -455,7 +455,7 @@ void spopWithCountCommand(client *c) {
robj
*
propargv
[
3
];
robj
*
propargv
[
3
];
propargv
[
0
]
=
createStringObject
(
"SREM"
,
4
);
propargv
[
0
]
=
createStringObject
(
"SREM"
,
4
);
propargv
[
1
]
=
c
->
argv
[
1
];
propargv
[
1
]
=
c
->
argv
[
1
];
addReply
MultiBulk
Len
(
c
,
count
);
addReply
Set
Len
(
c
,
count
);
/* Common iteration vars. */
/* Common iteration vars. */
sds
sdsele
;
sds
sdsele
;
...
@@ -516,11 +516,7 @@ void spopWithCountCommand(client *c) {
...
@@ -516,11 +516,7 @@ void spopWithCountCommand(client *c) {
sdsfree
(
sdsele
);
sdsfree
(
sdsele
);
}
}
/* Assign the new set as the key value. */
/* Transfer the old set to the client. */
incrRefCount
(
set
);
/* Protect the old set value. */
dbOverwrite
(
c
->
db
,
c
->
argv
[
1
],
newset
);
/* Tranfer the old set to the client and release it. */
setTypeIterator
*
si
;
setTypeIterator
*
si
;
si
=
setTypeInitIterator
(
set
);
si
=
setTypeInitIterator
(
set
);
while
((
encoding
=
setTypeNext
(
si
,
&
sdsele
,
&
llele
))
!=
-
1
)
{
while
((
encoding
=
setTypeNext
(
si
,
&
sdsele
,
&
llele
))
!=
-
1
)
{
...
@@ -539,7 +535,9 @@ void spopWithCountCommand(client *c) {
...
@@ -539,7 +535,9 @@ void spopWithCountCommand(client *c) {
decrRefCount
(
objele
);
decrRefCount
(
objele
);
}
}
setTypeReleaseIterator
(
si
);
setTypeReleaseIterator
(
si
);
decrRefCount
(
set
);
/* Assign the new set as the key value. */
dbOverwrite
(
c
->
db
,
c
->
argv
[
1
],
newset
);
}
}
/* Don't propagate the command itself even if we incremented the
/* Don't propagate the command itself even if we incremented the
...
@@ -568,8 +566,8 @@ void spopCommand(client *c) {
...
@@ -568,8 +566,8 @@ void spopCommand(client *c) {
/* Make sure a key with the name inputted exists, and that it's type is
/* Make sure a key with the name inputted exists, and that it's type is
* indeed a set */
* indeed a set */
if
((
set
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
bulk
))
==
NULL
||
if
((
set
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
[
c
->
resp
]))
checkType
(
c
,
set
,
OBJ_SET
))
return
;
==
NULL
||
checkType
(
c
,
set
,
OBJ_SET
))
return
;
/* Get a random element from the set */
/* Get a random element from the set */
encoding
=
setTypeRandomElement
(
set
,
&
sdsele
,
&
llele
);
encoding
=
setTypeRandomElement
(
set
,
&
sdsele
,
&
llele
);
...
@@ -634,13 +632,13 @@ void srandmemberWithCountCommand(client *c) {
...
@@ -634,13 +632,13 @@ void srandmemberWithCountCommand(client *c) {
uniq
=
0
;
uniq
=
0
;
}
}
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
emptymultibulk
))
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
[
c
->
resp
]
))
==
NULL
||
checkType
(
c
,
set
,
OBJ_SET
))
return
;
==
NULL
||
checkType
(
c
,
set
,
OBJ_SET
))
return
;
size
=
setTypeSize
(
set
);
size
=
setTypeSize
(
set
);
/* If count is zero, serve it ASAP to avoid special cases later. */
/* If count is zero, serve it ASAP to avoid special cases later. */
if
(
count
==
0
)
{
if
(
count
==
0
)
{
addReply
(
c
,
shared
.
emptymultibulk
);
addReply
Null
(
c
);
return
;
return
;
}
}
...
@@ -649,7 +647,7 @@ void srandmemberWithCountCommand(client *c) {
...
@@ -649,7 +647,7 @@ void srandmemberWithCountCommand(client *c) {
* This case is trivial and can be served without auxiliary data
* This case is trivial and can be served without auxiliary data
* structures. */
* structures. */
if
(
!
uniq
)
{
if
(
!
uniq
)
{
addReply
MultiBulk
Len
(
c
,
count
);
addReply
Set
Len
(
c
,
count
);
while
(
count
--
)
{
while
(
count
--
)
{
encoding
=
setTypeRandomElement
(
set
,
&
ele
,
&
llele
);
encoding
=
setTypeRandomElement
(
set
,
&
ele
,
&
llele
);
if
(
encoding
==
OBJ_ENCODING_INTSET
)
{
if
(
encoding
==
OBJ_ENCODING_INTSET
)
{
...
@@ -739,7 +737,7 @@ void srandmemberWithCountCommand(client *c) {
...
@@ -739,7 +737,7 @@ void srandmemberWithCountCommand(client *c) {
dictIterator
*
di
;
dictIterator
*
di
;
dictEntry
*
de
;
dictEntry
*
de
;
addReply
MultiBulk
Len
(
c
,
count
);
addReply
Set
Len
(
c
,
count
);
di
=
dictGetIterator
(
d
);
di
=
dictGetIterator
(
d
);
while
((
de
=
dictNext
(
di
))
!=
NULL
)
while
((
de
=
dictNext
(
di
))
!=
NULL
)
addReplyBulk
(
c
,
dictGetKey
(
de
));
addReplyBulk
(
c
,
dictGetKey
(
de
));
...
@@ -762,8 +760,8 @@ void srandmemberCommand(client *c) {
...
@@ -762,8 +760,8 @@ void srandmemberCommand(client *c) {
return
;
return
;
}
}
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
bulk
))
==
NULL
||
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
[
c
->
resp
]))
checkType
(
c
,
set
,
OBJ_SET
))
return
;
==
NULL
||
checkType
(
c
,
set
,
OBJ_SET
))
return
;
encoding
=
setTypeRandomElement
(
set
,
&
ele
,
&
llele
);
encoding
=
setTypeRandomElement
(
set
,
&
ele
,
&
llele
);
if
(
encoding
==
OBJ_ENCODING_INTSET
)
{
if
(
encoding
==
OBJ_ENCODING_INTSET
)
{
...
@@ -815,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
...
@@ -815,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
}
}
addReply
(
c
,
shared
.
czero
);
addReply
(
c
,
shared
.
czero
);
}
else
{
}
else
{
addReply
(
c
,
shared
.
emptymultibulk
);
addReply
Null
(
c
);
}
}
return
;
return
;
}
}
...
@@ -835,7 +833,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
...
@@ -835,7 +833,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
* to the output list and save the pointer to later modify it with the
* to the output list and save the pointer to later modify it with the
* right length */
* right length */
if
(
!
dstkey
)
{
if
(
!
dstkey
)
{
replylen
=
addDeferred
MultiBulkLength
(
c
);
replylen
=
add
Reply
Deferred
Len
(
c
);
}
else
{
}
else
{
/* If we have a target key where to store the resulting set
/* If we have a target key where to store the resulting set
* create this key with an empty set inside */
* create this key with an empty set inside */
...
@@ -913,7 +911,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
...
@@ -913,7 +911,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
signalModifiedKey
(
c
->
db
,
dstkey
);
signalModifiedKey
(
c
->
db
,
dstkey
);
server
.
dirty
++
;
server
.
dirty
++
;
}
else
{
}
else
{
setDeferred
MultiBulkLength
(
c
,
replylen
,
cardinality
);
setDeferred
SetLen
(
c
,
replylen
,
cardinality
);
}
}
zfree
(
sets
);
zfree
(
sets
);
}
}
...
@@ -1059,7 +1057,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
...
@@ -1059,7 +1057,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
/* Output the content of the resulting set, if not in STORE mode */
/* Output the content of the resulting set, if not in STORE mode */
if
(
!
dstkey
)
{
if
(
!
dstkey
)
{
addReply
MultiBulk
Len
(
c
,
cardinality
);
addReply
Set
Len
(
c
,
cardinality
);
si
=
setTypeInitIterator
(
dstset
);
si
=
setTypeInitIterator
(
dstset
);
while
((
ele
=
setTypeNextObject
(
si
))
!=
NULL
)
{
while
((
ele
=
setTypeNextObject
(
si
))
!=
NULL
)
{
addReplyBulkCBuffer
(
c
,
ele
,
sdslen
(
ele
));
addReplyBulkCBuffer
(
c
,
ele
,
sdslen
(
ele
));
...
...
src/t_stream.c
View file @
49816941
...
@@ -37,7 +37,7 @@
...
@@ -37,7 +37,7 @@
* mark the entry as deleted, or having the same field as the "master"
* mark the entry as deleted, or having the same field as the "master"
* entry at the start of the listpack> */
* entry at the start of the listpack> */
#define STREAM_ITEM_FLAG_NONE 0
/* No special flags. */
#define STREAM_ITEM_FLAG_NONE 0
/* No special flags. */
#define STREAM_ITEM_FLAG_DELETED (1<<0)
/* Entry is delted. Skip it. */
#define STREAM_ITEM_FLAG_DELETED (1<<0)
/* Entry is del
e
ted. Skip it. */
#define STREAM_ITEM_FLAG_SAMEFIELDS (1<<1)
/* Same fields as master entry. */
#define STREAM_ITEM_FLAG_SAMEFIELDS (1<<1)
/* Same fields as master entry. */
void
streamFreeCG
(
streamCG
*
cg
);
void
streamFreeCG
(
streamCG
*
cg
);
...
@@ -165,7 +165,7 @@ int streamCompareID(streamID *a, streamID *b) {
...
@@ -165,7 +165,7 @@ int streamCompareID(streamID *a, streamID *b) {
* Returns the new entry ID populating the 'added_id' structure.
* Returns the new entry ID populating the 'added_id' structure.
*
*
* If 'use_id' is not NULL, the ID is not auto-generated by the function,
* If 'use_id' is not NULL, the ID is not auto-generated by the function,
* but instead the passed ID is u
e
sd to add the new entry. In this case
* but instead the passed ID is us
e
d to add the new entry. In this case
* adding the entry may fail as specified later in this comment.
* adding the entry may fail as specified later in this comment.
*
*
* The function returns C_OK if the item was added, this is always true
* The function returns C_OK if the item was added, this is always true
...
@@ -223,13 +223,13 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
...
@@ -223,13 +223,13 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
*
*
* count and deleted just represent respectively the total number of
* count and deleted just represent respectively the total number of
* entries inside the listpack that are valid, and marked as deleted
* entries inside the listpack that are valid, and marked as deleted
* (delted flag in the entry flags set). So the total number of items
* (del
e
ted flag in the entry flags set). So the total number of items
* actually inside the listpack (both deleted and not) is count+deleted.
* actually inside the listpack (both deleted and not) is count+deleted.
*
*
* The real entries will be encoded with an ID that is just the
* The real entries will be encoded with an ID that is just the
* millisecond and sequence difference compared to the key stored at
* millisecond and sequence difference compared to the key stored at
* the radix tree node containing the listpack (delta encoding), and
* the radix tree node containing the listpack (delta encoding), and
* if the fields of the entry are the same as the master enty fields, the
* if the fields of the entry are the same as the master ent
r
y fields, the
* entry flags will specify this fact and the entry fields and number
* entry flags will specify this fact and the entry fields and number
* of fields will be omitted (see later in the code of this function).
* of fields will be omitted (see later in the code of this function).
*
*
...
@@ -349,7 +349,8 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
...
@@ -349,7 +349,8 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
lp
=
lpAppendInteger
(
lp
,
lp_count
);
lp
=
lpAppendInteger
(
lp
,
lp_count
);
/* Insert back into the tree in order to update the listpack pointer. */
/* Insert back into the tree in order to update the listpack pointer. */
raxInsert
(
s
->
rax
,(
unsigned
char
*
)
&
rax_key
,
sizeof
(
rax_key
),
lp
,
NULL
);
if
(
ri
.
data
!=
lp
)
raxInsert
(
s
->
rax
,(
unsigned
char
*
)
&
rax_key
,
sizeof
(
rax_key
),
lp
,
NULL
);
s
->
length
++
;
s
->
length
++
;
s
->
last_id
=
id
;
s
->
last_id
=
id
;
if
(
added_id
)
*
added_id
=
id
;
if
(
added_id
)
*
added_id
=
id
;
...
@@ -485,7 +486,7 @@ int64_t streamTrimByLength(stream *s, size_t maxlen, int approx) {
...
@@ -485,7 +486,7 @@ int64_t streamTrimByLength(stream *s, size_t maxlen, int approx) {
* }
* }
* streamIteratorStop(&myiterator); */
* streamIteratorStop(&myiterator); */
void
streamIteratorStart
(
streamIterator
*
si
,
stream
*
s
,
streamID
*
start
,
streamID
*
end
,
int
rev
)
{
void
streamIteratorStart
(
streamIterator
*
si
,
stream
*
s
,
streamID
*
start
,
streamID
*
end
,
int
rev
)
{
/* Intialize the iterator and translates the iteration start/stop
/* In
i
tialize the iterator and translates the iteration start/stop
* elements into a 128 big big-endian number. */
* elements into a 128 big big-endian number. */
if
(
start
)
{
if
(
start
)
{
streamEncodeID
(
si
->
start_key
,
start
);
streamEncodeID
(
si
->
start_key
,
start
);
...
@@ -548,15 +549,22 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
...
@@ -548,15 +549,22 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
si
->
master_fields_count
=
lpGetInteger
(
si
->
lp_ele
);
si
->
master_fields_count
=
lpGetInteger
(
si
->
lp_ele
);
si
->
lp_ele
=
lpNext
(
si
->
lp
,
si
->
lp_ele
);
/* Seek first field. */
si
->
lp_ele
=
lpNext
(
si
->
lp
,
si
->
lp_ele
);
/* Seek first field. */
si
->
master_fields_start
=
si
->
lp_ele
;
si
->
master_fields_start
=
si
->
lp_ele
;
/* Skip master fileds to seek the first entry. */
/* We are now pointing to the first field of the master entry.
for
(
uint64_t
i
=
0
;
i
<
si
->
master_fields_count
;
i
++
)
* We need to seek either the first or the last entry depending
si
->
lp_ele
=
lpNext
(
si
->
lp
,
si
->
lp_ele
);
* on the direction of the iteration. */
/* We are now pointing the zero term of the master entry. If
if
(
!
si
->
rev
)
{
* we are iterating in reverse order, we need to seek the
/* If we are iterating in normal order, skip the master fields
* end of the listpack. */
* to seek the first actual entry. */
if
(
si
->
rev
)
si
->
lp_ele
=
lpLast
(
si
->
lp
);
for
(
uint64_t
i
=
0
;
i
<
si
->
master_fields_count
;
i
++
)
si
->
lp_ele
=
lpNext
(
si
->
lp
,
si
->
lp_ele
);
}
else
{
/* If we are iterating in reverse direction, just seek the
* last part of the last entry in the listpack (that is, the
* fields count). */
si
->
lp_ele
=
lpLast
(
si
->
lp
);
}
}
else
if
(
si
->
rev
)
{
}
else
if
(
si
->
rev
)
{
/* If we are iter
e
ating in the reverse order, and this is not
/* If we are iterating in the reverse order, and this is not
* the first entry emitted for this listpack, then we already
* the first entry emitted for this listpack, then we already
* emitted the current entry, and have to go back to the previous
* emitted the current entry, and have to go back to the previous
* one. */
* one. */
...
@@ -717,6 +725,10 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
...
@@ -717,6 +725,10 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
p
=
lpNext
(
lp
,
p
);
/* Seek deleted field. */
p
=
lpNext
(
lp
,
p
);
/* Seek deleted field. */
aux
=
lpGetInteger
(
p
);
aux
=
lpGetInteger
(
p
);
lp
=
lpReplaceInteger
(
lp
,
&
p
,
aux
+
1
);
lp
=
lpReplaceInteger
(
lp
,
&
p
,
aux
+
1
);
/* Update the listpack with the new pointer. */
if
(
si
->
lp
!=
lp
)
raxInsert
(
si
->
stream
->
rax
,
si
->
ri
.
key
,
si
->
ri
.
key_len
,
lp
,
NULL
);
}
}
/* Update the number of entries counter. */
/* Update the number of entries counter. */
...
@@ -739,7 +751,7 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
...
@@ -739,7 +751,7 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
}
}
/* Stop the stream iterator. The only cleanup we need is to free the rax
/* Stop the stream iterator. The only cleanup we need is to free the rax
* iter
e
ator, since the stream iterator itself is supposed to be stack
* iterator, since the stream iterator itself is supposed to be stack
* allocated. */
* allocated. */
void
streamIteratorStop
(
streamIterator
*
si
)
{
void
streamIteratorStop
(
streamIterator
*
si
)
{
raxStop
(
&
si
->
ri
);
raxStop
(
&
si
->
ri
);
...
@@ -757,6 +769,7 @@ int streamDeleteItem(stream *s, streamID *id) {
...
@@ -757,6 +769,7 @@ int streamDeleteItem(stream *s, streamID *id) {
streamIteratorRemoveEntry
(
&
si
,
&
myid
);
streamIteratorRemoveEntry
(
&
si
,
&
myid
);
deleted
=
1
;
deleted
=
1
;
}
}
streamIteratorStop
(
&
si
);
return
deleted
;
return
deleted
;
}
}
...
@@ -764,8 +777,8 @@ int streamDeleteItem(stream *s, streamID *id) {
...
@@ -764,8 +777,8 @@ int streamDeleteItem(stream *s, streamID *id) {
* in the standard <ms>-<seq> format, using the simple string protocol
* in the standard <ms>-<seq> format, using the simple string protocol
* of REPL. */
* of REPL. */
void
addReplyStreamID
(
client
*
c
,
streamID
*
id
)
{
void
addReplyStreamID
(
client
*
c
,
streamID
*
id
)
{
sds
replyid
=
sdscatfmt
(
sdsempty
(),
"
+
%U-%U
\r\n
"
,
id
->
ms
,
id
->
seq
);
sds
replyid
=
sdscatfmt
(
sdsempty
(),
"%U-%U"
,
id
->
ms
,
id
->
seq
);
addReplySds
(
c
,
replyid
);
addReply
Bulk
Sds
(
c
,
replyid
);
}
}
/* Similar to the above function, but just creates an object, usually useful
/* Similar to the above function, but just creates an object, usually useful
...
@@ -778,18 +791,18 @@ robj *createObjectFromStreamID(streamID *id) {
...
@@ -778,18 +791,18 @@ robj *createObjectFromStreamID(streamID *id) {
/* As a result of an explicit XCLAIM or XREADGROUP command, new entries
/* As a result of an explicit XCLAIM or XREADGROUP command, new entries
* are created in the pending list of the stream and consumers. We need
* are created in the pending list of the stream and consumers. We need
* to propagate this changes in the form of XCLAIM commands. */
* to propagate this changes in the form of XCLAIM commands. */
void
streamPropagateXCLAIM
(
client
*
c
,
robj
*
key
,
robj
*
group
,
robj
*
id
,
streamNACK
*
nack
)
{
void
streamPropagateXCLAIM
(
client
*
c
,
robj
*
key
,
streamCG
*
group
,
robj
*
group
name
,
robj
*
id
,
streamNACK
*
nack
)
{
/* We need to generate an XCLAIM that will work in a idempotent fashion:
/* We need to generate an XCLAIM that will work in a idempotent fashion:
*
*
* XCLAIM <key> <group> <consumer> 0 <id> TIME <milliseconds-unix-time>
* XCLAIM <key> <group> <consumer> 0 <id> TIME <milliseconds-unix-time>
* RETRYCOUNT <count> FORCE JUSTID.
* RETRYCOUNT <count> FORCE JUSTID
LASTID <id>
.
*
*
* Note that JUSTID is useful in order to avoid that XCLAIM will do
* Note that JUSTID is useful in order to avoid that XCLAIM will do
* useless work in the slave side, trying to fetch the stream item. */
* useless work in the slave side, trying to fetch the stream item. */
robj
*
argv
[
1
2
];
robj
*
argv
[
1
4
];
argv
[
0
]
=
createStringObject
(
"XCLAIM"
,
6
);
argv
[
0
]
=
createStringObject
(
"XCLAIM"
,
6
);
argv
[
1
]
=
key
;
argv
[
1
]
=
key
;
argv
[
2
]
=
group
;
argv
[
2
]
=
group
name
;
argv
[
3
]
=
createStringObject
(
nack
->
consumer
->
name
,
sdslen
(
nack
->
consumer
->
name
));
argv
[
3
]
=
createStringObject
(
nack
->
consumer
->
name
,
sdslen
(
nack
->
consumer
->
name
));
argv
[
4
]
=
createStringObjectFromLongLong
(
0
);
argv
[
4
]
=
createStringObjectFromLongLong
(
0
);
argv
[
5
]
=
id
;
argv
[
5
]
=
id
;
...
@@ -799,7 +812,9 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA
...
@@ -799,7 +812,9 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA
argv
[
9
]
=
createStringObjectFromLongLong
(
nack
->
delivery_count
);
argv
[
9
]
=
createStringObjectFromLongLong
(
nack
->
delivery_count
);
argv
[
10
]
=
createStringObject
(
"FORCE"
,
5
);
argv
[
10
]
=
createStringObject
(
"FORCE"
,
5
);
argv
[
11
]
=
createStringObject
(
"JUSTID"
,
6
);
argv
[
11
]
=
createStringObject
(
"JUSTID"
,
6
);
propagate
(
server
.
xclaimCommand
,
c
->
db
->
id
,
argv
,
12
,
PROPAGATE_AOF
|
PROPAGATE_REPL
);
argv
[
12
]
=
createStringObject
(
"LASTID"
,
6
);
argv
[
13
]
=
createObjectFromStreamID
(
&
group
->
last_id
);
propagate
(
server
.
xclaimCommand
,
c
->
db
->
id
,
argv
,
14
,
PROPAGATE_AOF
|
PROPAGATE_REPL
);
decrRefCount
(
argv
[
0
]);
decrRefCount
(
argv
[
0
]);
decrRefCount
(
argv
[
3
]);
decrRefCount
(
argv
[
3
]);
decrRefCount
(
argv
[
4
]);
decrRefCount
(
argv
[
4
]);
...
@@ -809,13 +824,38 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA
...
@@ -809,13 +824,38 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA
decrRefCount
(
argv
[
9
]);
decrRefCount
(
argv
[
9
]);
decrRefCount
(
argv
[
10
]);
decrRefCount
(
argv
[
10
]);
decrRefCount
(
argv
[
11
]);
decrRefCount
(
argv
[
11
]);
decrRefCount
(
argv
[
12
]);
decrRefCount
(
argv
[
13
]);
}
}
/* Send the specified range to the client 'c'. The range the client will
/* We need this when we want to propoagate the new last-id of a consumer group
* receive is between start and end inclusive, if 'count' is non zero, no more
* that was consumed by XREADGROUP with the NOACK option: in that case we can't
* than 'count' elemnets are sent. The 'end' pointer can be NULL to mean that
* propagate the last ID just using the XCLAIM LASTID option, so we emit
* we want all the elements from 'start' till the end of the stream. If 'rev'
*
* is non zero, elements are produced in reversed order from end to start.
* XGROUP SETID <key> <groupname> <id>
*/
void
streamPropagateGroupID
(
client
*
c
,
robj
*
key
,
streamCG
*
group
,
robj
*
groupname
)
{
robj
*
argv
[
5
];
argv
[
0
]
=
createStringObject
(
"XGROUP"
,
6
);
argv
[
1
]
=
createStringObject
(
"SETID"
,
5
);
argv
[
2
]
=
key
;
argv
[
3
]
=
groupname
;
argv
[
4
]
=
createObjectFromStreamID
(
&
group
->
last_id
);
propagate
(
server
.
xgroupCommand
,
c
->
db
->
id
,
argv
,
5
,
PROPAGATE_AOF
|
PROPAGATE_REPL
);
decrRefCount
(
argv
[
0
]);
decrRefCount
(
argv
[
1
]);
decrRefCount
(
argv
[
4
]);
}
/* Send the stream items in the specified range to the client 'c'. The range
* the client will receive is between start and end inclusive, if 'count' is
* non zero, no more than 'count' elements are sent.
*
* The 'end' pointer can be NULL to mean that we want all the elements from
* 'start' till the end of the stream. If 'rev' is non zero, elements are
* produced in reversed order from end to start.
*
* The function returns the number of entries emitted.
*
*
* If group and consumer are not NULL, the function performs additional work:
* If group and consumer are not NULL, the function performs additional work:
* 1. It updates the last delivered ID in the group in case we are
* 1. It updates the last delivered ID in the group in case we are
...
@@ -827,15 +867,15 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA
...
@@ -827,15 +867,15 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA
*
*
* The behavior may be modified passing non-zero flags:
* The behavior may be modified passing non-zero flags:
*
*
* STREAM_RWR_NOACK: Do not cr
a
ete PEL entries, that is, the point "3" above
* STREAM_RWR_NOACK: Do not cre
a
te PEL entries, that is, the point "3" above
* is not performed.
* is not performed.
* STREAM_RWR_RAWENTRIES: Do not emit array boundaries, but just the entries,
* STREAM_RWR_RAWENTRIES: Do not emit array boundaries, but just the entries,
* and return the number of entries emitted as usually.
* and return the number of entries emitted as usually.
* This is used when the function is just used in order
* This is used when the function is just used in order
* to emit data and there is some higher level logic.
* to emit data and there is some higher level logic.
*
*
* The final argument 'spi' (stream propagati
n
o info pointer) is a structure
* The final argument 'spi' (stream propagatio
n
info pointer) is a structure
* filled with information needed to propagte the command execution to AOF
* filled with information needed to propag
a
te the command execution to AOF
* and slaves, in the case a consumer group was passed: we need to generate
* and slaves, in the case a consumer group was passed: we need to generate
* XCLAIM commands to create the pending list into AOF/slaves in that case.
* XCLAIM commands to create the pending list into AOF/slaves in that case.
*
*
...
@@ -854,39 +894,41 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA
...
@@ -854,39 +894,41 @@ void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNA
#define STREAM_RWR_NOACK (1<<0)
/* Do not create entries in the PEL. */
#define STREAM_RWR_NOACK (1<<0)
/* Do not create entries in the PEL. */
#define STREAM_RWR_RAWENTRIES (1<<1)
/* Do not emit protocol for array
#define STREAM_RWR_RAWENTRIES (1<<1)
/* Do not emit protocol for array
boundaries, just the entries. */
boundaries, just the entries. */
#define STREAM_RWR_HISTORY (1<<2)
/* Only serve consumer local PEL. */
size_t
streamReplyWithRange
(
client
*
c
,
stream
*
s
,
streamID
*
start
,
streamID
*
end
,
size_t
count
,
int
rev
,
streamCG
*
group
,
streamConsumer
*
consumer
,
int
flags
,
streamPropInfo
*
spi
)
{
size_t
streamReplyWithRange
(
client
*
c
,
stream
*
s
,
streamID
*
start
,
streamID
*
end
,
size_t
count
,
int
rev
,
streamCG
*
group
,
streamConsumer
*
consumer
,
int
flags
,
streamPropInfo
*
spi
)
{
void
*
arraylen_ptr
=
NULL
;
void
*
arraylen_ptr
=
NULL
;
size_t
arraylen
=
0
;
size_t
arraylen
=
0
;
streamIterator
si
;
streamIterator
si
;
int64_t
numfields
;
int64_t
numfields
;
streamID
id
;
streamID
id
;
int
propagate_last_id
=
0
;
/* If a group was passed, we check if the request is about messages
/* If the client is asking for some history, we serve it using a
* never delivered so far (normally this happens when ">" ID is passed).
* different function, so that we return entries *solely* from its
*
* own PEL. This ensures each consumer will always and only see
* If instead the client is asking for some history, we serve it
* the history of messages delivered to it and not yet confirmed
* using a different function, so that we return entries *solely*
* from its own PEL. This ensures each consumer will always and only
* see the history of messages delivered to it and not yet confirmed
* as delivered. */
* as delivered. */
if
(
group
&&
streamCompareID
(
start
,
&
group
->
last_id
)
<=
0
)
{
if
(
group
&&
(
flags
&
STREAM_RWR_HISTORY
)
)
{
return
streamReplyWithRangeFromConsumerPEL
(
c
,
s
,
start
,
end
,
count
,
return
streamReplyWithRangeFromConsumerPEL
(
c
,
s
,
start
,
end
,
count
,
consumer
);
consumer
);
}
}
if
(
!
(
flags
&
STREAM_RWR_RAWENTRIES
))
if
(
!
(
flags
&
STREAM_RWR_RAWENTRIES
))
arraylen_ptr
=
addDeferred
MultiBulkLength
(
c
);
arraylen_ptr
=
add
Reply
Deferred
Len
(
c
);
streamIteratorStart
(
&
si
,
s
,
start
,
end
,
rev
);
streamIteratorStart
(
&
si
,
s
,
start
,
end
,
rev
);
while
(
streamIteratorGetID
(
&
si
,
&
id
,
&
numfields
))
{
while
(
streamIteratorGetID
(
&
si
,
&
id
,
&
numfields
))
{
/* Update the group last_id if needed. */
/* Update the group last_id if needed. */
if
(
group
&&
streamCompareID
(
&
id
,
&
group
->
last_id
)
>
0
)
if
(
group
&&
streamCompareID
(
&
id
,
&
group
->
last_id
)
>
0
)
{
group
->
last_id
=
id
;
group
->
last_id
=
id
;
propagate_last_id
=
1
;
}
/* Emit a two elements array for each item. The first is
/* Emit a two elements array for each item. The first is
* the ID, the second is an array of field-value pairs. */
* the ID, the second is an array of field-value pairs. */
addReply
MultiBulk
Len
(
c
,
2
);
addReply
Array
Len
(
c
,
2
);
addReplyStreamID
(
c
,
&
id
);
addReplyStreamID
(
c
,
&
id
);
addReplyMultiBulkLen
(
c
,
numfields
*
2
);
addReplyMapLen
(
c
,
numfields
);
/* Emit the field-value pairs. */
/* Emit the field-value pairs. */
while
(
numfields
--
)
{
while
(
numfields
--
)
{
...
@@ -940,16 +982,19 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
...
@@ -940,16 +982,19 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
/* Propagate as XCLAIM. */
/* Propagate as XCLAIM. */
if
(
spi
)
{
if
(
spi
)
{
robj
*
idarg
=
createObjectFromStreamID
(
&
id
);
robj
*
idarg
=
createObjectFromStreamID
(
&
id
);
streamPropagateXCLAIM
(
c
,
spi
->
keyname
,
spi
->
groupname
,
idarg
,
nack
);
streamPropagateXCLAIM
(
c
,
spi
->
keyname
,
group
,
spi
->
groupname
,
idarg
,
nack
);
decrRefCount
(
idarg
);
decrRefCount
(
idarg
);
}
}
}
else
{
if
(
propagate_last_id
)
streamPropagateGroupID
(
c
,
spi
->
keyname
,
group
,
spi
->
groupname
);
}
}
arraylen
++
;
arraylen
++
;
if
(
count
&&
count
==
arraylen
)
break
;
if
(
count
&&
count
==
arraylen
)
break
;
}
}
streamIteratorStop
(
&
si
);
streamIteratorStop
(
&
si
);
if
(
arraylen_ptr
)
setDeferred
MultiBulkLength
(
c
,
arraylen_ptr
,
arraylen
);
if
(
arraylen_ptr
)
setDeferred
ArrayLen
(
c
,
arraylen_ptr
,
arraylen
);
return
arraylen
;
return
arraylen
;
}
}
...
@@ -974,25 +1019,25 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
...
@@ -974,25 +1019,25 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
if
(
end
)
streamEncodeID
(
endkey
,
end
);
if
(
end
)
streamEncodeID
(
endkey
,
end
);
size_t
arraylen
=
0
;
size_t
arraylen
=
0
;
void
*
arraylen_ptr
=
addDeferred
MultiBulkLength
(
c
);
void
*
arraylen_ptr
=
add
Reply
Deferred
Len
(
c
);
raxStart
(
&
ri
,
consumer
->
pel
);
raxStart
(
&
ri
,
consumer
->
pel
);
raxSeek
(
&
ri
,
">="
,
startkey
,
sizeof
(
startkey
));
raxSeek
(
&
ri
,
">="
,
startkey
,
sizeof
(
startkey
));
while
(
raxNext
(
&
ri
)
&&
(
!
count
||
arraylen
<
count
))
{
while
(
raxNext
(
&
ri
)
&&
(
!
count
||
arraylen
<
count
))
{
if
(
end
&&
memcmp
(
ri
.
key
,
end
,
ri
.
key_len
)
>
0
)
break
;
if
(
end
&&
memcmp
(
ri
.
key
,
end
,
ri
.
key_len
)
>
0
)
break
;
streamID
thisid
;
streamID
thisid
;
streamDecodeID
(
ri
.
key
,
&
thisid
);
streamDecodeID
(
ri
.
key
,
&
thisid
);
if
(
streamReplyWithRange
(
c
,
s
,
&
thisid
,
NULL
,
1
,
0
,
NULL
,
NULL
,
if
(
streamReplyWithRange
(
c
,
s
,
&
thisid
,
&
thisid
,
1
,
0
,
NULL
,
NULL
,
STREAM_RWR_RAWENTRIES
,
NULL
)
==
0
)
STREAM_RWR_RAWENTRIES
,
NULL
)
==
0
)
{
{
/* Note that we may have a not acknowledged entry in the PEL
/* Note that we may have a not acknowledged entry in the PEL
* about a message that's no longer here because was removed
* about a message that's no longer here because was removed
* by the user by other means. In that case we signal it emitting
* by the user by other means. In that case we signal it emitting
* the ID but then a NULL entry for the fields. */
* the ID but then a NULL entry for the fields. */
addReply
MultiBulk
Len
(
c
,
2
);
addReply
Array
Len
(
c
,
2
);
streamID
id
;
streamID
id
;
streamDecodeID
(
ri
.
key
,
&
id
);
streamDecodeID
(
ri
.
key
,
&
id
);
addReplyStreamID
(
c
,
&
id
);
addReplyStreamID
(
c
,
&
id
);
addReply
(
c
,
shared
.
nullmultibulk
);
addReply
NullArray
(
c
);
}
else
{
}
else
{
streamNACK
*
nack
=
ri
.
data
;
streamNACK
*
nack
=
ri
.
data
;
nack
->
delivery_time
=
mstime
();
nack
->
delivery_time
=
mstime
();
...
@@ -1001,7 +1046,7 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
...
@@ -1001,7 +1046,7 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
arraylen
++
;
arraylen
++
;
}
}
raxStop
(
&
ri
);
raxStop
(
&
ri
);
setDeferred
MultiBulkLength
(
c
,
arraylen_ptr
,
arraylen
);
setDeferred
ArrayLen
(
c
,
arraylen_ptr
,
arraylen
);
return
arraylen
;
return
arraylen
;
}
}
...
@@ -1051,15 +1096,20 @@ int string2ull(const char *s, unsigned long long *value) {
...
@@ -1051,15 +1096,20 @@ int string2ull(const char *s, unsigned long long *value) {
* to the client, otherwise C_OK is returned. The ID may be in incomplete
* to the client, otherwise C_OK is returned. The ID may be in incomplete
* form, just stating the milliseconds time part of the stream. In such a case
* form, just stating the milliseconds time part of the stream. In such a case
* the missing part is set according to the value of 'missing_seq' parameter.
* the missing part is set according to the value of 'missing_seq' parameter.
*
* The IDs "-" and "+" specify respectively the minimum and maximum IDs
* The IDs "-" and "+" specify respectively the minimum and maximum IDs
* that can be represented.
* that can be represented. If 'strict' is set to 1, "-" and "+" will be
* treated as an invalid ID.
*
*
* If 'c' is set to NULL, no reply is sent to the client. */
* If 'c' is set to NULL, no reply is sent to the client. */
int
streamParseIDOrReply
(
client
*
c
,
robj
*
o
,
streamID
*
id
,
uint64_t
missing_seq
)
{
int
stream
Generic
ParseIDOrReply
(
client
*
c
,
robj
*
o
,
streamID
*
id
,
uint64_t
missing_seq
,
int
strict
)
{
char
buf
[
128
];
char
buf
[
128
];
if
(
sdslen
(
o
->
ptr
)
>
sizeof
(
buf
)
-
1
)
goto
invalid
;
if
(
sdslen
(
o
->
ptr
)
>
sizeof
(
buf
)
-
1
)
goto
invalid
;
memcpy
(
buf
,
o
->
ptr
,
sdslen
(
o
->
ptr
)
+
1
);
memcpy
(
buf
,
o
->
ptr
,
sdslen
(
o
->
ptr
)
+
1
);
if
(
strict
&&
(
buf
[
0
]
==
'-'
||
buf
[
0
]
==
'+'
)
&&
buf
[
1
]
==
'\0'
)
goto
invalid
;
/* Handle the "-" and "+" special cases. */
/* Handle the "-" and "+" special cases. */
if
(
buf
[
0
]
==
'-'
&&
buf
[
1
]
==
'\0'
)
{
if
(
buf
[
0
]
==
'-'
&&
buf
[
1
]
==
'\0'
)
{
id
->
ms
=
0
;
id
->
ms
=
0
;
...
@@ -1088,7 +1138,33 @@ invalid:
...
@@ -1088,7 +1138,33 @@ invalid:
return
C_ERR
;
return
C_ERR
;
}
}
/* XADD key [MAXLEN <count>] <ID or *> [field value] [field value] ... */
/* Wrapper for streamGenericParseIDOrReply() with 'strict' argument set to
* 0, to be used when - and + are acceptable IDs. */
int
streamParseIDOrReply
(
client
*
c
,
robj
*
o
,
streamID
*
id
,
uint64_t
missing_seq
)
{
return
streamGenericParseIDOrReply
(
c
,
o
,
id
,
missing_seq
,
0
);
}
/* Wrapper for streamGenericParseIDOrReply() with 'strict' argument set to
* 1, to be used when we want to return an error if the special IDs + or -
* are provided. */
int
streamParseStrictIDOrReply
(
client
*
c
,
robj
*
o
,
streamID
*
id
,
uint64_t
missing_seq
)
{
return
streamGenericParseIDOrReply
(
c
,
o
,
id
,
missing_seq
,
1
);
}
/* We propagate MAXLEN ~ <count> as MAXLEN = <resulting-len-of-stream>
* otherwise trimming is no longer determinsitic on replicas / AOF. */
void
streamRewriteApproxMaxlen
(
client
*
c
,
stream
*
s
,
int
maxlen_arg_idx
)
{
robj
*
maxlen_obj
=
createStringObjectFromLongLong
(
s
->
length
);
robj
*
equal_obj
=
createStringObject
(
"="
,
1
);
rewriteClientCommandArgument
(
c
,
maxlen_arg_idx
,
maxlen_obj
);
rewriteClientCommandArgument
(
c
,
maxlen_arg_idx
-
1
,
equal_obj
);
decrRefCount
(
equal_obj
);
decrRefCount
(
maxlen_obj
);
}
/* XADD key [MAXLEN [~|=] <count>] <ID or *> [field value] [field value] ... */
void
xaddCommand
(
client
*
c
)
{
void
xaddCommand
(
client
*
c
)
{
streamID
id
;
streamID
id
;
int
id_given
=
0
;
/* Was an ID different than "*" specified? */
int
id_given
=
0
;
/* Was an ID different than "*" specified? */
...
@@ -1108,11 +1184,14 @@ void xaddCommand(client *c) {
...
@@ -1108,11 +1184,14 @@ void xaddCommand(client *c) {
* creation. */
* creation. */
break
;
break
;
}
else
if
(
!
strcasecmp
(
opt
,
"maxlen"
)
&&
moreargs
)
{
}
else
if
(
!
strcasecmp
(
opt
,
"maxlen"
)
&&
moreargs
)
{
approx_maxlen
=
0
;
char
*
next
=
c
->
argv
[
i
+
1
]
->
ptr
;
char
*
next
=
c
->
argv
[
i
+
1
]
->
ptr
;
/* Check for the form MAXLEN ~ <count>. */
/* Check for the form MAXLEN ~ <count>. */
if
(
moreargs
>=
2
&&
next
[
0
]
==
'~'
&&
next
[
1
]
==
'\0'
)
{
if
(
moreargs
>=
2
&&
next
[
0
]
==
'~'
&&
next
[
1
]
==
'\0'
)
{
approx_maxlen
=
1
;
approx_maxlen
=
1
;
i
++
;
i
++
;
}
else
if
(
moreargs
>=
2
&&
next
[
0
]
==
'='
&&
next
[
1
]
==
'\0'
)
{
i
++
;
}
}
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
i
+
1
],
&
maxlen
,
NULL
)
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
i
+
1
],
&
maxlen
,
NULL
)
!=
C_OK
)
return
;
!=
C_OK
)
return
;
...
@@ -1125,7 +1204,7 @@ void xaddCommand(client *c) {
...
@@ -1125,7 +1204,7 @@ void xaddCommand(client *c) {
maxlen_arg_idx
=
i
;
maxlen_arg_idx
=
i
;
}
else
{
}
else
{
/* If we are here is a syntax error or a valid ID. */
/* If we are here is a syntax error or a valid ID. */
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
i
],
&
id
,
0
)
!=
C_OK
)
return
;
if
(
streamParse
Strict
IDOrReply
(
c
,
c
->
argv
[
i
],
&
id
,
0
)
!=
C_OK
)
return
;
id_given
=
1
;
id_given
=
1
;
break
;
break
;
}
}
...
@@ -1159,18 +1238,12 @@ void xaddCommand(client *c) {
...
@@ -1159,18 +1238,12 @@ void xaddCommand(client *c) {
notifyKeyspaceEvent
(
NOTIFY_STREAM
,
"xadd"
,
c
->
argv
[
1
],
c
->
db
->
id
);
notifyKeyspaceEvent
(
NOTIFY_STREAM
,
"xadd"
,
c
->
argv
[
1
],
c
->
db
->
id
);
server
.
dirty
++
;
server
.
dirty
++
;
/* Remove older elements if MAXLEN was specified. */
if
(
maxlen
>=
0
)
{
if
(
maxlen
>=
0
)
{
if
(
!
streamTrimByLength
(
s
,
maxlen
,
approx_maxlen
))
{
/* Notify xtrim event if needed. */
/* If no trimming was performed, for instance because approximated
if
(
streamTrimByLength
(
s
,
maxlen
,
approx_maxlen
))
{
* trimming length was specified, rewrite the MAXLEN argument
* as zero, so that the command is propagated without trimming. */
robj
*
zeroobj
=
createStringObjectFromLongLong
(
0
);
rewriteClientCommandArgument
(
c
,
maxlen_arg_idx
,
zeroobj
);
decrRefCount
(
zeroobj
);
}
else
{
notifyKeyspaceEvent
(
NOTIFY_STREAM
,
"xtrim"
,
c
->
argv
[
1
],
c
->
db
->
id
);
notifyKeyspaceEvent
(
NOTIFY_STREAM
,
"xtrim"
,
c
->
argv
[
1
],
c
->
db
->
id
);
}
}
if
(
approx_maxlen
)
streamRewriteApproxMaxlen
(
c
,
s
,
maxlen_arg_idx
);
}
}
/* Let's rewrite the ID argument with the one actually generated for
/* Let's rewrite the ID argument with the one actually generated for
...
@@ -1190,7 +1263,7 @@ void xrangeGenericCommand(client *c, int rev) {
...
@@ -1190,7 +1263,7 @@ void xrangeGenericCommand(client *c, int rev) {
robj
*
o
;
robj
*
o
;
stream
*
s
;
stream
*
s
;
streamID
startid
,
endid
;
streamID
startid
,
endid
;
long
long
count
=
0
;
long
long
count
=
-
1
;
robj
*
startarg
=
rev
?
c
->
argv
[
3
]
:
c
->
argv
[
2
];
robj
*
startarg
=
rev
?
c
->
argv
[
3
]
:
c
->
argv
[
2
];
robj
*
endarg
=
rev
?
c
->
argv
[
2
]
:
c
->
argv
[
3
];
robj
*
endarg
=
rev
?
c
->
argv
[
2
]
:
c
->
argv
[
3
];
...
@@ -1214,10 +1287,17 @@ void xrangeGenericCommand(client *c, int rev) {
...
@@ -1214,10 +1287,17 @@ void xrangeGenericCommand(client *c, int rev) {
}
}
/* Return the specified range to the user. */
/* Return the specified range to the user. */
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
emptymultibulk
))
==
NULL
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
emptyarray
))
==
NULL
||
||
checkType
(
c
,
o
,
OBJ_STREAM
))
return
;
checkType
(
c
,
o
,
OBJ_STREAM
))
return
;
s
=
o
->
ptr
;
s
=
o
->
ptr
;
streamReplyWithRange
(
c
,
s
,
&
startid
,
&
endid
,
count
,
rev
,
NULL
,
NULL
,
0
,
NULL
);
if
(
count
==
0
)
{
addReplyNullArray
(
c
);
}
else
{
if
(
count
==
-
1
)
count
=
0
;
streamReplyWithRange
(
c
,
s
,
&
startid
,
&
endid
,
count
,
rev
,
NULL
,
NULL
,
0
,
NULL
);
}
}
}
/* XRANGE key start end [COUNT <n>] */
/* XRANGE key start end [COUNT <n>] */
...
@@ -1381,7 +1461,7 @@ void xreadCommand(client *c) {
...
@@ -1381,7 +1461,7 @@ void xreadCommand(client *c) {
ids
[
id_idx
].
seq
=
UINT64_MAX
;
ids
[
id_idx
].
seq
=
UINT64_MAX
;
continue
;
continue
;
}
}
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
i
],
ids
+
id_idx
,
0
)
!=
C_OK
)
if
(
streamParse
Strict
IDOrReply
(
c
,
c
->
argv
[
i
],
ids
+
id_idx
,
0
)
!=
C_OK
)
goto
cleanup
;
goto
cleanup
;
}
}
...
@@ -1394,8 +1474,10 @@ void xreadCommand(client *c) {
...
@@ -1394,8 +1474,10 @@ void xreadCommand(client *c) {
stream
*
s
=
o
->
ptr
;
stream
*
s
=
o
->
ptr
;
streamID
*
gt
=
ids
+
i
;
/* ID must be greater than this. */
streamID
*
gt
=
ids
+
i
;
/* ID must be greater than this. */
int
serve_synchronously
=
0
;
int
serve_synchronously
=
0
;
int
serve_history
=
0
;
/* True for XREADGROUP with ID != ">". */
/* Check if there are the conditions to serve the client synchronously. */
/* Check if there are the conditions to serve the client
* synchronously. */
if
(
groups
)
{
if
(
groups
)
{
/* If the consumer is blocked on a group, we always serve it
/* If the consumer is blocked on a group, we always serve it
* synchronously (serving its local history) if the ID specified
* synchronously (serving its local history) if the ID specified
...
@@ -1404,12 +1486,13 @@ void xreadCommand(client *c) {
...
@@ -1404,12 +1486,13 @@ void xreadCommand(client *c) {
gt
->
seq
!=
UINT64_MAX
)
gt
->
seq
!=
UINT64_MAX
)
{
{
serve_synchronously
=
1
;
serve_synchronously
=
1
;
serve_history
=
1
;
}
else
{
}
else
{
/* We also want to serve a consumer in a consumer group
/* We also want to serve a consumer in a consumer group
* synchronously in case the group top item delivered is smaller
* synchronously in case the group top item delivered is smaller
* than what the stream has inside. */
* than what the stream has inside. */
streamID
*
last
=
&
groups
[
i
]
->
last_id
;
streamID
*
last
=
&
groups
[
i
]
->
last_id
;
if
(
streamCompareID
(
&
s
->
last_id
,
last
)
>
0
)
{
if
(
s
->
length
&&
(
streamCompareID
(
&
s
->
last_id
,
last
)
>
0
)
)
{
serve_synchronously
=
1
;
serve_synchronously
=
1
;
*
gt
=
*
last
;
*
gt
=
*
last
;
}
}
...
@@ -1417,14 +1500,14 @@ void xreadCommand(client *c) {
...
@@ -1417,14 +1500,14 @@ void xreadCommand(client *c) {
}
else
{
}
else
{
/* For consumers without a group, we serve synchronously if we can
/* For consumers without a group, we serve synchronously if we can
* actually provide at least one item from the stream. */
* actually provide at least one item from the stream. */
if
(
streamCompareID
(
&
s
->
last_id
,
gt
)
>
0
)
{
if
(
s
->
length
&&
(
streamCompareID
(
&
s
->
last_id
,
gt
)
>
0
)
)
{
serve_synchronously
=
1
;
serve_synchronously
=
1
;
}
}
}
}
if
(
serve_synchronously
)
{
if
(
serve_synchronously
)
{
arraylen
++
;
arraylen
++
;
if
(
arraylen
==
1
)
arraylen_ptr
=
addDeferred
MultiBulkLength
(
c
);
if
(
arraylen
==
1
)
arraylen_ptr
=
add
Reply
Deferred
Len
(
c
);
/* streamReplyWithRange() handles the 'start' ID as inclusive,
/* streamReplyWithRange() handles the 'start' ID as inclusive,
* so start from the next ID, since we want only messages with
* so start from the next ID, since we want only messages with
* IDs greater than start. */
* IDs greater than start. */
...
@@ -1433,22 +1516,28 @@ void xreadCommand(client *c) {
...
@@ -1433,22 +1516,28 @@ void xreadCommand(client *c) {
/* Emit the two elements sub-array consisting of the name
/* Emit the two elements sub-array consisting of the name
* of the stream and the data we extracted from it. */
* of the stream and the data we extracted from it. */
addReplyMultiBulk
Len
(
c
,
2
);
if
(
c
->
resp
==
2
)
addReplyArray
Len
(
c
,
2
);
addReplyBulk
(
c
,
c
->
argv
[
streams_arg
+
i
]);
addReplyBulk
(
c
,
c
->
argv
[
streams_arg
+
i
]);
streamConsumer
*
consumer
=
NULL
;
streamConsumer
*
consumer
=
NULL
;
if
(
groups
)
consumer
=
streamLookupConsumer
(
groups
[
i
],
if
(
groups
)
consumer
=
streamLookupConsumer
(
groups
[
i
],
consumername
->
ptr
,
1
);
consumername
->
ptr
,
1
);
streamPropInfo
spi
=
{
c
->
argv
[
i
+
streams_arg
],
groupname
};
streamPropInfo
spi
=
{
c
->
argv
[
i
+
streams_arg
],
groupname
};
int
flags
=
0
;
if
(
noack
)
flags
|=
STREAM_RWR_NOACK
;
if
(
serve_history
)
flags
|=
STREAM_RWR_HISTORY
;
streamReplyWithRange
(
c
,
s
,
&
start
,
NULL
,
count
,
0
,
streamReplyWithRange
(
c
,
s
,
&
start
,
NULL
,
count
,
0
,
groups
?
groups
[
i
]
:
NULL
,
groups
?
groups
[
i
]
:
NULL
,
consumer
,
noack
,
&
spi
);
consumer
,
flags
,
&
spi
);
if
(
groups
)
server
.
dirty
++
;
if
(
groups
)
server
.
dirty
++
;
}
}
}
}
/* We replied synchronously! Set the top array len and return to caller. */
/* We replied synchronously! Set the top array len and return to caller. */
if
(
arraylen
)
{
if
(
arraylen
)
{
setDeferredMultiBulkLength
(
c
,
arraylen_ptr
,
arraylen
);
if
(
c
->
resp
==
2
)
setDeferredArrayLen
(
c
,
arraylen_ptr
,
arraylen
);
else
setDeferredMapLen
(
c
,
arraylen_ptr
,
arraylen
);
goto
cleanup
;
goto
cleanup
;
}
}
...
@@ -1457,7 +1546,7 @@ void xreadCommand(client *c) {
...
@@ -1457,7 +1546,7 @@ void xreadCommand(client *c) {
/* If we are inside a MULTI/EXEC and the list is empty the only thing
/* If we are inside a MULTI/EXEC and the list is empty the only thing
* we can do is treating it as a timeout (even with timeout 0). */
* we can do is treating it as a timeout (even with timeout 0). */
if
(
c
->
flags
&
CLIENT_MULTI
)
{
if
(
c
->
flags
&
CLIENT_MULTI
)
{
addReply
(
c
,
shared
.
nullmultibulk
);
addReply
NullArray
(
c
);
goto
cleanup
;
goto
cleanup
;
}
}
blockForKeys
(
c
,
BLOCKED_STREAM
,
c
->
argv
+
streams_arg
,
streams_count
,
blockForKeys
(
c
,
BLOCKED_STREAM
,
c
->
argv
+
streams_arg
,
streams_count
,
...
@@ -1486,7 +1575,7 @@ void xreadCommand(client *c) {
...
@@ -1486,7 +1575,7 @@ void xreadCommand(client *c) {
/* No BLOCK option, nor any stream we can serve. Reply as with a
/* No BLOCK option, nor any stream we can serve. Reply as with a
* timeout happened. */
* timeout happened. */
addReply
(
c
,
shared
.
nullmultibulk
);
addReply
NullArray
(
c
);
/* Continue to cleanup... */
/* Continue to cleanup... */
cleanup:
/* Cleanup. */
cleanup:
/* Cleanup. */
...
@@ -1613,13 +1702,14 @@ uint64_t streamDelConsumer(streamCG *cg, sds name) {
...
@@ -1613,13 +1702,14 @@ uint64_t streamDelConsumer(streamCG *cg, sds name) {
* Consumer groups commands
* Consumer groups commands
* ----------------------------------------------------------------------- */
* ----------------------------------------------------------------------- */
/* XGROUP CREATE <key> <groupname> <id or $>
/* XGROUP CREATE <key> <groupname> <id or $>
[MKSTREAM]
* XGROUP SETID <key> <id or $>
* XGROUP SETID <key>
<groupname>
<id or $>
* XGROUP DESTROY <key> <groupname>
* XGROUP DESTROY <key> <groupname>
* XGROUP DELCONSUMER <key> <groupname> <consumername> */
* XGROUP DELCONSUMER <key> <groupname> <consumername> */
void
xgroupCommand
(
client
*
c
)
{
void
xgroupCommand
(
client
*
c
)
{
const
char
*
help
[]
=
{
const
char
*
help
[]
=
{
"CREATE <key> <groupname> <id or $> -- Create a new consumer group."
,
"CREATE <key> <groupname> <id or $> [opt] -- Create a new consumer group."
,
" option MKSTREAM: create the empty stream if it does not exist."
,
"SETID <key> <groupname> <id or $> -- Set the current group ID."
,
"SETID <key> <groupname> <id or $> -- Set the current group ID."
,
"DESTROY <key> <groupname> -- Remove the specified group."
,
"DESTROY <key> <groupname> -- Remove the specified group."
,
"DELCONSUMER <key> <groupname> <consumer> -- Remove the specified consumer."
,
"DELCONSUMER <key> <groupname> <consumer> -- Remove the specified consumer."
,
...
@@ -1630,13 +1720,39 @@ NULL
...
@@ -1630,13 +1720,39 @@ NULL
sds
grpname
=
NULL
;
sds
grpname
=
NULL
;
streamCG
*
cg
=
NULL
;
streamCG
*
cg
=
NULL
;
char
*
opt
=
c
->
argv
[
1
]
->
ptr
;
/* Subcommand name. */
char
*
opt
=
c
->
argv
[
1
]
->
ptr
;
/* Subcommand name. */
int
mkstream
=
0
;
robj
*
o
;
/* Lookup the key now, this is common for all the subcommands but HELP. */
/* CREATE has an MKSTREAM option that creates the stream if it
* does not exist. */
if
(
c
->
argc
==
6
&&
!
strcasecmp
(
opt
,
"CREATE"
))
{
if
(
strcasecmp
(
c
->
argv
[
5
]
->
ptr
,
"MKSTREAM"
))
{
addReplySubcommandSyntaxError
(
c
);
return
;
}
mkstream
=
1
;
grpname
=
c
->
argv
[
3
]
->
ptr
;
}
/* Everything but the "HELP" option requires a key and group name. */
if
(
c
->
argc
>=
4
)
{
if
(
c
->
argc
>=
4
)
{
robj
*
o
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
2
],
shared
.
nokeyerr
);
o
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
2
]);
if
(
o
==
NULL
||
checkType
(
c
,
o
,
OBJ_STREAM
))
return
;
if
(
o
)
s
=
o
->
ptr
;
s
=
o
->
ptr
;
grpname
=
c
->
argv
[
3
]
->
ptr
;
grpname
=
c
->
argv
[
3
]
->
ptr
;
}
/* Check for missing key/group. */
if
(
c
->
argc
>=
4
&&
!
mkstream
)
{
/* At this point key must exist, or there is an error. */
if
(
o
==
NULL
)
{
addReplyError
(
c
,
"The XGROUP subcommand requires the key to exist. "
"Note that for CREATE you may want to use the MKSTREAM "
"option to create an empty stream automatically."
);
return
;
}
if
(
checkType
(
c
,
o
,
OBJ_STREAM
))
return
;
/* Certain subcommands require the group to exist. */
/* Certain subcommands require the group to exist. */
if
((
cg
=
streamLookupCG
(
s
,
grpname
))
==
NULL
&&
if
((
cg
=
streamLookupCG
(
s
,
grpname
))
==
NULL
&&
...
@@ -1651,13 +1767,26 @@ NULL
...
@@ -1651,13 +1767,26 @@ NULL
}
}
/* Dispatch the different subcommands. */
/* Dispatch the different subcommands. */
if
(
!
strcasecmp
(
opt
,
"CREATE"
)
&&
c
->
argc
==
5
)
{
if
(
!
strcasecmp
(
opt
,
"CREATE"
)
&&
(
c
->
argc
==
5
||
c
->
argc
==
6
)
)
{
streamID
id
;
streamID
id
;
if
(
!
strcmp
(
c
->
argv
[
4
]
->
ptr
,
"$"
))
{
if
(
!
strcmp
(
c
->
argv
[
4
]
->
ptr
,
"$"
))
{
id
=
s
->
last_id
;
if
(
s
)
{
}
else
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
4
],
&
id
,
0
)
!=
C_OK
)
{
id
=
s
->
last_id
;
}
else
{
id
.
ms
=
0
;
id
.
seq
=
0
;
}
}
else
if
(
streamParseStrictIDOrReply
(
c
,
c
->
argv
[
4
],
&
id
,
0
)
!=
C_OK
)
{
return
;
return
;
}
}
/* Handle the MKSTREAM option now that the command can no longer fail. */
if
(
s
==
NULL
&&
mkstream
)
{
o
=
createStreamObject
();
dbAdd
(
c
->
db
,
c
->
argv
[
2
],
o
);
s
=
o
->
ptr
;
}
streamCG
*
cg
=
streamCreateCG
(
s
,
grpname
,
sdslen
(
grpname
),
&
id
);
streamCG
*
cg
=
streamCreateCG
(
s
,
grpname
,
sdslen
(
grpname
),
&
id
);
if
(
cg
)
{
if
(
cg
)
{
addReply
(
c
,
shared
.
ok
);
addReply
(
c
,
shared
.
ok
);
...
@@ -1705,6 +1834,40 @@ NULL
...
@@ -1705,6 +1834,40 @@ NULL
}
}
}
}
/* XSETID <stream> <groupname> <id>
*
* Set the internal "last ID" of a stream. */
void
xsetidCommand
(
client
*
c
)
{
robj
*
o
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
nokeyerr
);
if
(
o
==
NULL
||
checkType
(
c
,
o
,
OBJ_STREAM
))
return
;
stream
*
s
=
o
->
ptr
;
streamID
id
;
if
(
streamParseStrictIDOrReply
(
c
,
c
->
argv
[
2
],
&
id
,
0
)
!=
C_OK
)
return
;
/* If the stream has at least one item, we want to check that the user
* is setting a last ID that is equal or greater than the current top
* item, otherwise the fundamental ID monotonicity assumption is violated. */
if
(
s
->
length
>
0
)
{
streamID
maxid
;
streamIterator
si
;
streamIteratorStart
(
&
si
,
s
,
NULL
,
NULL
,
1
);
int64_t
numfields
;
streamIteratorGetID
(
&
si
,
&
maxid
,
&
numfields
);
streamIteratorStop
(
&
si
);
if
(
streamCompareID
(
&
id
,
&
maxid
)
<
0
)
{
addReplyError
(
c
,
"The ID specified in XSETID is smaller than the "
"target stream top item"
);
return
;
}
}
s
->
last_id
=
id
;
addReply
(
c
,
shared
.
ok
);
server
.
dirty
++
;
notifyKeyspaceEvent
(
NOTIFY_STREAM
,
"xsetid"
,
c
->
argv
[
1
],
c
->
db
->
id
);
}
/* XACK <key> <group> <id> <id> ... <id>
/* XACK <key> <group> <id> <id> ... <id>
*
*
* Acknowledge a message as processed. In practical terms we just check the
* Acknowledge a message as processed. In practical terms we just check the
...
@@ -1732,7 +1895,7 @@ void xackCommand(client *c) {
...
@@ -1732,7 +1895,7 @@ void xackCommand(client *c) {
for
(
int
j
=
3
;
j
<
c
->
argc
;
j
++
)
{
for
(
int
j
=
3
;
j
<
c
->
argc
;
j
++
)
{
streamID
id
;
streamID
id
;
unsigned
char
buf
[
sizeof
(
streamID
)];
unsigned
char
buf
[
sizeof
(
streamID
)];
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
j
],
&
id
,
0
)
!=
C_OK
)
return
;
if
(
streamParse
Strict
IDOrReply
(
c
,
c
->
argv
[
j
],
&
id
,
0
)
!=
C_OK
)
return
;
streamEncodeID
(
buf
,
&
id
);
streamEncodeID
(
buf
,
&
id
);
/* Lookup the ID in the group PEL: it will have a reference to the
/* Lookup the ID in the group PEL: it will have a reference to the
...
@@ -1750,7 +1913,7 @@ void xackCommand(client *c) {
...
@@ -1750,7 +1913,7 @@ void xackCommand(client *c) {
addReplyLongLong
(
c
,
acknowledged
);
addReplyLongLong
(
c
,
acknowledged
);
}
}
/* XPENDING <key> <group> [<start> <stop> <count>
]
[<consumer>]
/* XPENDING <key> <group> [<start> <stop> <count> [<consumer>]
]
*
*
* If start and stop are omitted, the command just outputs information about
* If start and stop are omitted, the command just outputs information about
* the amount of pending messages for the key/group pair, together with
* the amount of pending messages for the key/group pair, together with
...
@@ -1779,6 +1942,7 @@ void xpendingCommand(client *c) {
...
@@ -1779,6 +1942,7 @@ void xpendingCommand(client *c) {
if
(
c
->
argc
>=
6
)
{
if
(
c
->
argc
>=
6
)
{
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
5
],
&
count
,
NULL
)
==
C_ERR
)
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
5
],
&
count
,
NULL
)
==
C_ERR
)
return
;
return
;
if
(
count
<
0
)
count
=
0
;
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
3
],
&
startid
,
0
)
==
C_ERR
)
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
3
],
&
startid
,
0
)
==
C_ERR
)
return
;
return
;
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
4
],
&
endid
,
UINT64_MAX
)
==
C_ERR
)
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
4
],
&
endid
,
UINT64_MAX
)
==
C_ERR
)
...
@@ -1801,14 +1965,14 @@ void xpendingCommand(client *c) {
...
@@ -1801,14 +1965,14 @@ void xpendingCommand(client *c) {
/* XPENDING <key> <group> variant. */
/* XPENDING <key> <group> variant. */
if
(
justinfo
)
{
if
(
justinfo
)
{
addReply
MultiBulk
Len
(
c
,
4
);
addReply
Array
Len
(
c
,
4
);
/* Total number of messages in the PEL. */
/* Total number of messages in the PEL. */
addReplyLongLong
(
c
,
raxSize
(
group
->
pel
));
addReplyLongLong
(
c
,
raxSize
(
group
->
pel
));
/* First and last IDs. */
/* First and last IDs. */
if
(
raxSize
(
group
->
pel
)
==
0
)
{
if
(
raxSize
(
group
->
pel
)
==
0
)
{
addReply
(
c
,
shared
.
nullbulk
);
/* Start. */
addReply
Null
(
c
);
/* Start. */
addReply
(
c
,
shared
.
nullbulk
);
/* End. */
addReply
Null
(
c
);
/* End. */
addReply
(
c
,
shared
.
nullmultibulk
);
/* Clients. */
addReply
NullArray
(
c
);
/* Clients. */
}
else
{
}
else
{
/* Start. */
/* Start. */
raxIterator
ri
;
raxIterator
ri
;
...
@@ -1828,17 +1992,17 @@ void xpendingCommand(client *c) {
...
@@ -1828,17 +1992,17 @@ void xpendingCommand(client *c) {
/* Consumers with pending messages. */
/* Consumers with pending messages. */
raxStart
(
&
ri
,
group
->
consumers
);
raxStart
(
&
ri
,
group
->
consumers
);
raxSeek
(
&
ri
,
"^"
,
NULL
,
0
);
raxSeek
(
&
ri
,
"^"
,
NULL
,
0
);
void
*
arraylen_ptr
=
addDeferred
MultiBulkLength
(
c
);
void
*
arraylen_ptr
=
add
Reply
Deferred
Len
(
c
);
size_t
arraylen
=
0
;
size_t
arraylen
=
0
;
while
(
raxNext
(
&
ri
))
{
while
(
raxNext
(
&
ri
))
{
streamConsumer
*
consumer
=
ri
.
data
;
streamConsumer
*
consumer
=
ri
.
data
;
if
(
raxSize
(
consumer
->
pel
)
==
0
)
continue
;
if
(
raxSize
(
consumer
->
pel
)
==
0
)
continue
;
addReply
MultiBulk
Len
(
c
,
2
);
addReply
Array
Len
(
c
,
2
);
addReplyBulkCBuffer
(
c
,
ri
.
key
,
ri
.
key_len
);
addReplyBulkCBuffer
(
c
,
ri
.
key
,
ri
.
key_len
);
addReplyBulkLongLong
(
c
,
raxSize
(
consumer
->
pel
));
addReplyBulkLongLong
(
c
,
raxSize
(
consumer
->
pel
));
arraylen
++
;
arraylen
++
;
}
}
setDeferred
MultiBulkLength
(
c
,
arraylen_ptr
,
arraylen
);
setDeferred
ArrayLen
(
c
,
arraylen_ptr
,
arraylen
);
raxStop
(
&
ri
);
raxStop
(
&
ri
);
}
}
}
}
...
@@ -1851,7 +2015,7 @@ void xpendingCommand(client *c) {
...
@@ -1851,7 +2015,7 @@ void xpendingCommand(client *c) {
/* If a consumer name was mentioned but it does not exist, we can
/* If a consumer name was mentioned but it does not exist, we can
* just return an empty array. */
* just return an empty array. */
if
(
consumername
&&
consumer
==
NULL
)
{
if
(
consumername
&&
consumer
==
NULL
)
{
addReply
MultiBulk
Len
(
c
,
0
);
addReply
Array
Len
(
c
,
0
);
return
;
return
;
}
}
...
@@ -1865,7 +2029,7 @@ void xpendingCommand(client *c) {
...
@@ -1865,7 +2029,7 @@ void xpendingCommand(client *c) {
streamEncodeID
(
endkey
,
&
endid
);
streamEncodeID
(
endkey
,
&
endid
);
raxStart
(
&
ri
,
pel
);
raxStart
(
&
ri
,
pel
);
raxSeek
(
&
ri
,
">="
,
startkey
,
sizeof
(
startkey
));
raxSeek
(
&
ri
,
">="
,
startkey
,
sizeof
(
startkey
));
void
*
arraylen_ptr
=
addDeferred
MultiBulkLength
(
c
);
void
*
arraylen_ptr
=
add
Reply
Deferred
Len
(
c
);
size_t
arraylen
=
0
;
size_t
arraylen
=
0
;
while
(
count
&&
raxNext
(
&
ri
)
&&
memcmp
(
ri
.
key
,
endkey
,
ri
.
key_len
)
<=
0
)
{
while
(
count
&&
raxNext
(
&
ri
)
&&
memcmp
(
ri
.
key
,
endkey
,
ri
.
key_len
)
<=
0
)
{
...
@@ -1873,7 +2037,7 @@ void xpendingCommand(client *c) {
...
@@ -1873,7 +2037,7 @@ void xpendingCommand(client *c) {
arraylen
++
;
arraylen
++
;
count
--
;
count
--
;
addReply
MultiBulk
Len
(
c
,
4
);
addReply
Array
Len
(
c
,
4
);
/* Entry ID. */
/* Entry ID. */
streamID
id
;
streamID
id
;
...
@@ -1893,7 +2057,7 @@ void xpendingCommand(client *c) {
...
@@ -1893,7 +2057,7 @@ void xpendingCommand(client *c) {
addReplyLongLong
(
c
,
nack
->
delivery_count
);
addReplyLongLong
(
c
,
nack
->
delivery_count
);
}
}
raxStop
(
&
ri
);
raxStop
(
&
ri
);
setDeferred
MultiBulkLength
(
c
,
arraylen_ptr
,
arraylen
);
setDeferred
ArrayLen
(
c
,
arraylen_ptr
,
arraylen
);
}
}
}
}
...
@@ -1952,6 +2116,14 @@ void xpendingCommand(client *c) {
...
@@ -1952,6 +2116,14 @@ void xpendingCommand(client *c) {
* Return just an array of IDs of messages successfully claimed,
* Return just an array of IDs of messages successfully claimed,
* without returning the actual message.
* without returning the actual message.
*
*
* 6. LASTID <id>:
* Update the consumer group last ID with the specified ID if the
* current last ID is smaller than the provided one.
* This is used for replication / AOF, so that when we read from a
* consumer group, the XCLAIM that gets propagated to give ownership
* to the consumer, is also used in order to update the group current
* ID.
*
* The command returns an array of messages that the user
* The command returns an array of messages that the user
* successfully claimed, so that the caller is able to understand
* successfully claimed, so that the caller is able to understand
* what messages it is now in charge of. */
* what messages it is now in charge of. */
...
@@ -1988,15 +2160,17 @@ void xclaimCommand(client *c) {
...
@@ -1988,15 +2160,17 @@ void xclaimCommand(client *c) {
* the client successfully claimed some message, so it should be
* the client successfully claimed some message, so it should be
* executed in a "all or nothing" fashion. */
* executed in a "all or nothing" fashion. */
int
j
;
int
j
;
for
(
j
=
4
;
j
<
c
->
argc
;
j
++
)
{
for
(
j
=
5
;
j
<
c
->
argc
;
j
++
)
{
streamID
id
;
streamID
id
;
if
(
streamParseIDOrReply
(
NULL
,
c
->
argv
[
j
],
&
id
,
0
)
!=
C_OK
)
break
;
if
(
streamParse
Strict
IDOrReply
(
NULL
,
c
->
argv
[
j
],
&
id
,
0
)
!=
C_OK
)
break
;
}
}
int
last_id_arg
=
j
-
1
;
/* Next time we iterate the IDs we now the range. */
int
last_id_arg
=
j
-
1
;
/* Next time we iterate the IDs we now the range. */
/* If we stopped because some IDs cannot be parsed, perhaps they
/* If we stopped because some IDs cannot be parsed, perhaps they
* are trailing options. */
* are trailing options. */
time_t
now
=
mstime
();
mstime_t
now
=
mstime
();
streamID
last_id
=
{
0
,
0
};
int
propagate_last_id
=
0
;
for
(;
j
<
c
->
argc
;
j
++
)
{
for
(;
j
<
c
->
argc
;
j
++
)
{
int
moreargs
=
(
c
->
argc
-
1
)
-
j
;
/* Number of additional arguments. */
int
moreargs
=
(
c
->
argc
-
1
)
-
j
;
/* Number of additional arguments. */
char
*
opt
=
c
->
argv
[
j
]
->
ptr
;
char
*
opt
=
c
->
argv
[
j
]
->
ptr
;
...
@@ -2013,27 +2187,35 @@ void xclaimCommand(client *c) {
...
@@ -2013,27 +2187,35 @@ void xclaimCommand(client *c) {
}
else
if
(
!
strcasecmp
(
opt
,
"TIME"
)
&&
moreargs
)
{
}
else
if
(
!
strcasecmp
(
opt
,
"TIME"
)
&&
moreargs
)
{
j
++
;
j
++
;
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
j
],
&
deliverytime
,
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
j
],
&
deliverytime
,
"Invalid
IDL
E option argument for XCLAIM"
)
"Invalid
TIM
E option argument for XCLAIM"
)
!=
C_OK
)
return
;
!=
C_OK
)
return
;
}
else
if
(
!
strcasecmp
(
opt
,
"RETRYCOUNT"
)
&&
moreargs
)
{
}
else
if
(
!
strcasecmp
(
opt
,
"RETRYCOUNT"
)
&&
moreargs
)
{
j
++
;
j
++
;
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
j
],
&
retrycount
,
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
j
],
&
retrycount
,
"Invalid
IDLE
option argument for XCLAIM"
)
"Invalid
RETRYCOUNT
option argument for XCLAIM"
)
!=
C_OK
)
return
;
!=
C_OK
)
return
;
}
else
if
(
!
strcasecmp
(
opt
,
"LASTID"
)
&&
moreargs
)
{
j
++
;
if
(
streamParseStrictIDOrReply
(
c
,
c
->
argv
[
j
],
&
last_id
,
0
)
!=
C_OK
)
return
;
}
else
{
}
else
{
addReplyErrorFormat
(
c
,
"Unrecognized XCLAIM option '%s'"
,
opt
);
addReplyErrorFormat
(
c
,
"Unrecognized XCLAIM option '%s'"
,
opt
);
return
;
return
;
}
}
}
}
if
(
streamCompareID
(
&
last_id
,
&
group
->
last_id
)
>
0
)
{
group
->
last_id
=
last_id
;
propagate_last_id
=
1
;
}
if
(
deliverytime
!=
-
1
)
{
if
(
deliverytime
!=
-
1
)
{
/* If a delivery time was passed, either with IDLE or TIME, we
/* If a delivery time was passed, either with IDLE or TIME, we
* do some sanity check on it, and set the deliverytime to now
* do some sanity check on it, and set the deliverytime to now
* (which is a sane choice usually) if the value is bogus.
* (which is a sane choice usually) if the value is bogus.
* To raise an error here is not wise because clients may compute
* To raise an error here is not wise because clients may compute
* the idle time doing some math startin from their local time,
* the idle time doing some math startin
g
from their local time,
* and this is not a good excuse to fail in case, for instance,
* and this is not a good excuse to fail in case, for instance,
* the compute
d
time is a bit in the future from our POV. */
* the compute
r
time is a bit in the future from our POV. */
if
(
deliverytime
<
0
||
deliverytime
>
now
)
deliverytime
=
now
;
if
(
deliverytime
<
0
||
deliverytime
>
now
)
deliverytime
=
now
;
}
else
{
}
else
{
/* If no IDLE/TIME option was passed, we want the last delivery
/* If no IDLE/TIME option was passed, we want the last delivery
...
@@ -2044,12 +2226,13 @@ void xclaimCommand(client *c) {
...
@@ -2044,12 +2226,13 @@ void xclaimCommand(client *c) {
/* Do the actual claiming. */
/* Do the actual claiming. */
streamConsumer
*
consumer
=
streamLookupConsumer
(
group
,
c
->
argv
[
3
]
->
ptr
,
1
);
streamConsumer
*
consumer
=
streamLookupConsumer
(
group
,
c
->
argv
[
3
]
->
ptr
,
1
);
void
*
arraylenptr
=
addDeferred
MultiBulkLength
(
c
);
void
*
arraylenptr
=
add
Reply
Deferred
Len
(
c
);
size_t
arraylen
=
0
;
size_t
arraylen
=
0
;
for
(
int
j
=
5
;
j
<=
last_id_arg
;
j
++
)
{
for
(
int
j
=
5
;
j
<=
last_id_arg
;
j
++
)
{
streamID
id
;
streamID
id
;
unsigned
char
buf
[
sizeof
(
streamID
)];
unsigned
char
buf
[
sizeof
(
streamID
)];
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
j
],
&
id
,
0
)
!=
C_OK
)
return
;
if
(
streamParseStrictIDOrReply
(
c
,
c
->
argv
[
j
],
&
id
,
0
)
!=
C_OK
)
serverPanic
(
"StreamID invalid after check. Should not be possible."
);
streamEncodeID
(
buf
,
&
id
);
streamEncodeID
(
buf
,
&
id
);
/* Lookup the ID in the group PEL. */
/* Lookup the ID in the group PEL. */
...
@@ -2079,8 +2262,12 @@ void xclaimCommand(client *c) {
...
@@ -2079,8 +2262,12 @@ void xclaimCommand(client *c) {
if
(
nack
!=
raxNotFound
)
{
if
(
nack
!=
raxNotFound
)
{
/* We need to check if the minimum idle time requested
/* We need to check if the minimum idle time requested
* by the caller is satisfied by this entry. */
* by the caller is satisfied by this entry.
if
(
minidle
)
{
*
* Note that the nack could be created by FORCE, in this
* case there was no pre-existing entry and minidle should
* be ignored, but in that case nick->consumer is NULL. */
if
(
nack
->
consumer
&&
minidle
)
{
mstime_t
this_idle
=
now
-
nack
->
delivery_time
;
mstime_t
this_idle
=
now
-
nack
->
delivery_time
;
if
(
this_idle
<
minidle
)
continue
;
if
(
this_idle
<
minidle
)
continue
;
}
}
...
@@ -2100,17 +2287,23 @@ void xclaimCommand(client *c) {
...
@@ -2100,17 +2287,23 @@ void xclaimCommand(client *c) {
if
(
justid
)
{
if
(
justid
)
{
addReplyStreamID
(
c
,
&
id
);
addReplyStreamID
(
c
,
&
id
);
}
else
{
}
else
{
streamReplyWithRange
(
c
,
o
->
ptr
,
&
id
,
NULL
,
1
,
0
,
NULL
,
NULL
,
size_t
emitted
=
streamReplyWithRange
(
c
,
o
->
ptr
,
&
id
,
&
id
,
1
,
0
,
STREAM_RWR_RAWENTRIES
,
NULL
);
NULL
,
NULL
,
STREAM_RWR_RAWENTRIES
,
NULL
);
if
(
!
emitted
)
addReplyNull
(
c
);
}
}
arraylen
++
;
arraylen
++
;
/* Propagate this change. */
/* Propagate this change. */
streamPropagateXCLAIM
(
c
,
c
->
argv
[
1
],
c
->
argv
[
3
],
c
->
argv
[
j
],
nack
);
streamPropagateXCLAIM
(
c
,
c
->
argv
[
1
],
group
,
c
->
argv
[
2
],
c
->
argv
[
j
],
nack
);
propagate_last_id
=
0
;
/* Will be propagated by XCLAIM itself. */
server
.
dirty
++
;
server
.
dirty
++
;
}
}
}
}
setDeferredMultiBulkLength
(
c
,
arraylenptr
,
arraylen
);
if
(
propagate_last_id
)
{
streamPropagateGroupID
(
c
,
c
->
argv
[
1
],
group
,
c
->
argv
[
2
]);
server
.
dirty
++
;
}
setDeferredArrayLen
(
c
,
arraylenptr
,
arraylen
);
preventCommandPropagation
(
c
);
preventCommandPropagation
(
c
);
}
}
...
@@ -2132,13 +2325,13 @@ void xdelCommand(client *c) {
...
@@ -2132,13 +2325,13 @@ void xdelCommand(client *c) {
* executed because at some point an invalid ID is parsed. */
* executed because at some point an invalid ID is parsed. */
streamID
id
;
streamID
id
;
for
(
int
j
=
2
;
j
<
c
->
argc
;
j
++
)
{
for
(
int
j
=
2
;
j
<
c
->
argc
;
j
++
)
{
if
(
streamParseIDOrReply
(
c
,
c
->
argv
[
j
],
&
id
,
0
)
!=
C_OK
)
return
;
if
(
streamParse
Strict
IDOrReply
(
c
,
c
->
argv
[
j
],
&
id
,
0
)
!=
C_OK
)
return
;
}
}
/* Actually apply the command. */
/* Actually apply the command. */
int
deleted
=
0
;
int
deleted
=
0
;
for
(
int
j
=
2
;
j
<
c
->
argc
;
j
++
)
{
for
(
int
j
=
2
;
j
<
c
->
argc
;
j
++
)
{
streamParseIDOrReply
(
c
,
c
->
argv
[
j
],
&
id
,
0
);
/* Retval already checked. */
streamParse
Strict
IDOrReply
(
c
,
c
->
argv
[
j
],
&
id
,
0
);
/* Retval already checked. */
deleted
+=
streamDeleteItem
(
s
,
&
id
);
deleted
+=
streamDeleteItem
(
s
,
&
id
);
}
}
...
@@ -2155,7 +2348,7 @@ void xdelCommand(client *c) {
...
@@ -2155,7 +2348,7 @@ void xdelCommand(client *c) {
*
*
* List of options:
* List of options:
*
*
* MAXLEN [~] <count>
-- Trim so that the stream will be capped at
* MAXLEN [~
|=
] <count> -- Trim so that the stream will be capped at
* the specified length. Use ~ before the
* the specified length. Use ~ before the
* count in order to demand approximated trimming
* count in order to demand approximated trimming
* (like XADD MAXLEN option).
* (like XADD MAXLEN option).
...
@@ -2174,9 +2367,10 @@ void xtrimCommand(client *c) {
...
@@ -2174,9 +2367,10 @@ void xtrimCommand(client *c) {
/* Argument parsing. */
/* Argument parsing. */
int
trim_strategy
=
TRIM_STRATEGY_NONE
;
int
trim_strategy
=
TRIM_STRATEGY_NONE
;
long
long
maxlen
=
0
;
/*
0 means no maximum length
. */
long
long
maxlen
=
-
1
;
/*
If left to -1 no trimming is performed
. */
int
approx_maxlen
=
0
;
/* If 1 only delete whole radix tree nodes, so
int
approx_maxlen
=
0
;
/* If 1 only delete whole radix tree nodes, so
the maxium length is not applied verbatim. */
the maxium length is not applied verbatim. */
int
maxlen_arg_idx
=
0
;
/* Index of the count in MAXLEN, for rewriting. */
/* Parse options. */
/* Parse options. */
int
i
=
2
;
/* Start of options. */
int
i
=
2
;
/* Start of options. */
...
@@ -2184,16 +2378,25 @@ void xtrimCommand(client *c) {
...
@@ -2184,16 +2378,25 @@ void xtrimCommand(client *c) {
int
moreargs
=
(
c
->
argc
-
1
)
-
i
;
/* Number of additional arguments. */
int
moreargs
=
(
c
->
argc
-
1
)
-
i
;
/* Number of additional arguments. */
char
*
opt
=
c
->
argv
[
i
]
->
ptr
;
char
*
opt
=
c
->
argv
[
i
]
->
ptr
;
if
(
!
strcasecmp
(
opt
,
"maxlen"
)
&&
moreargs
)
{
if
(
!
strcasecmp
(
opt
,
"maxlen"
)
&&
moreargs
)
{
approx_maxlen
=
0
;
trim_strategy
=
TRIM_STRATEGY_MAXLEN
;
trim_strategy
=
TRIM_STRATEGY_MAXLEN
;
char
*
next
=
c
->
argv
[
i
+
1
]
->
ptr
;
char
*
next
=
c
->
argv
[
i
+
1
]
->
ptr
;
/* Check for the form MAXLEN ~ <count>. */
/* Check for the form MAXLEN ~ <count>. */
if
(
moreargs
>=
2
&&
next
[
0
]
==
'~'
&&
next
[
1
]
==
'\0'
)
{
if
(
moreargs
>=
2
&&
next
[
0
]
==
'~'
&&
next
[
1
]
==
'\0'
)
{
approx_maxlen
=
1
;
approx_maxlen
=
1
;
i
++
;
i
++
;
}
else
if
(
moreargs
>=
2
&&
next
[
0
]
==
'='
&&
next
[
1
]
==
'\0'
)
{
i
++
;
}
}
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
i
+
1
],
&
maxlen
,
NULL
)
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
i
+
1
],
&
maxlen
,
NULL
)
!=
C_OK
)
return
;
!=
C_OK
)
return
;
if
(
maxlen
<
0
)
{
addReplyError
(
c
,
"The MAXLEN argument must be >= 0."
);
return
;
}
i
++
;
i
++
;
maxlen_arg_idx
=
i
;
}
else
{
}
else
{
addReply
(
c
,
shared
.
syntaxerr
);
addReply
(
c
,
shared
.
syntaxerr
);
return
;
return
;
...
@@ -2214,11 +2417,12 @@ void xtrimCommand(client *c) {
...
@@ -2214,11 +2417,12 @@ void xtrimCommand(client *c) {
signalModifiedKey
(
c
->
db
,
c
->
argv
[
1
]);
signalModifiedKey
(
c
->
db
,
c
->
argv
[
1
]);
notifyKeyspaceEvent
(
NOTIFY_STREAM
,
"xtrim"
,
c
->
argv
[
1
],
c
->
db
->
id
);
notifyKeyspaceEvent
(
NOTIFY_STREAM
,
"xtrim"
,
c
->
argv
[
1
],
c
->
db
->
id
);
server
.
dirty
+=
deleted
;
server
.
dirty
+=
deleted
;
if
(
approx_maxlen
)
streamRewriteApproxMaxlen
(
c
,
s
,
maxlen_arg_idx
);
}
}
addReplyLongLong
(
c
,
deleted
);
addReplyLongLong
(
c
,
deleted
);
}
}
/* XINFO CONSUMERS key
group
/* XINFO CONSUMERS
<
key
> <
group
>
* XINFO GROUPS <key>
* XINFO GROUPS <key>
* XINFO STREAM <key>
* XINFO STREAM <key>
* XINFO HELP. */
* XINFO HELP. */
...
@@ -2264,7 +2468,7 @@ NULL
...
@@ -2264,7 +2468,7 @@ NULL
return
;
return
;
}
}
addReply
MultiBulk
Len
(
c
,
raxSize
(
cg
->
consumers
));
addReply
Array
Len
(
c
,
raxSize
(
cg
->
consumers
));
raxIterator
ri
;
raxIterator
ri
;
raxStart
(
&
ri
,
cg
->
consumers
);
raxStart
(
&
ri
,
cg
->
consumers
);
raxSeek
(
&
ri
,
"^"
,
NULL
,
0
);
raxSeek
(
&
ri
,
"^"
,
NULL
,
0
);
...
@@ -2274,51 +2478,51 @@ NULL
...
@@ -2274,51 +2478,51 @@ NULL
mstime_t
idle
=
now
-
consumer
->
seen_time
;
mstime_t
idle
=
now
-
consumer
->
seen_time
;
if
(
idle
<
0
)
idle
=
0
;
if
(
idle
<
0
)
idle
=
0
;
addReplyM
ultiBulk
Len
(
c
,
6
);
addReplyM
ap
Len
(
c
,
3
);
addReply
Status
(
c
,
"name"
);
addReply
BulkCString
(
c
,
"name"
);
addReplyBulkCBuffer
(
c
,
consumer
->
name
,
sdslen
(
consumer
->
name
));
addReplyBulkCBuffer
(
c
,
consumer
->
name
,
sdslen
(
consumer
->
name
));
addReply
Status
(
c
,
"pending"
);
addReply
BulkCString
(
c
,
"pending"
);
addReplyLongLong
(
c
,
raxSize
(
consumer
->
pel
));
addReplyLongLong
(
c
,
raxSize
(
consumer
->
pel
));
addReply
Status
(
c
,
"idle"
);
addReply
BulkCString
(
c
,
"idle"
);
addReplyLongLong
(
c
,
idle
);
addReplyLongLong
(
c
,
idle
);
}
}
raxStop
(
&
ri
);
raxStop
(
&
ri
);
}
else
if
(
!
strcasecmp
(
opt
,
"GROUPS"
)
&&
c
->
argc
==
3
)
{
}
else
if
(
!
strcasecmp
(
opt
,
"GROUPS"
)
&&
c
->
argc
==
3
)
{
/* XINFO GROUPS <key>. */
/* XINFO GROUPS <key>. */
if
(
s
->
cgroups
==
NULL
)
{
if
(
s
->
cgroups
==
NULL
)
{
addReply
MultiBulk
Len
(
c
,
0
);
addReply
Array
Len
(
c
,
0
);
return
;
return
;
}
}
addReply
MultiBulk
Len
(
c
,
raxSize
(
s
->
cgroups
));
addReply
Array
Len
(
c
,
raxSize
(
s
->
cgroups
));
raxIterator
ri
;
raxIterator
ri
;
raxStart
(
&
ri
,
s
->
cgroups
);
raxStart
(
&
ri
,
s
->
cgroups
);
raxSeek
(
&
ri
,
"^"
,
NULL
,
0
);
raxSeek
(
&
ri
,
"^"
,
NULL
,
0
);
while
(
raxNext
(
&
ri
))
{
while
(
raxNext
(
&
ri
))
{
streamCG
*
cg
=
ri
.
data
;
streamCG
*
cg
=
ri
.
data
;
addReplyM
ultiBulk
Len
(
c
,
8
);
addReplyM
ap
Len
(
c
,
4
);
addReply
Status
(
c
,
"name"
);
addReply
BulkCString
(
c
,
"name"
);
addReplyBulkCBuffer
(
c
,
ri
.
key
,
ri
.
key_len
);
addReplyBulkCBuffer
(
c
,
ri
.
key
,
ri
.
key_len
);
addReply
Status
(
c
,
"consumers"
);
addReply
BulkCString
(
c
,
"consumers"
);
addReplyLongLong
(
c
,
raxSize
(
cg
->
consumers
));
addReplyLongLong
(
c
,
raxSize
(
cg
->
consumers
));
addReply
Status
(
c
,
"pending"
);
addReply
BulkCString
(
c
,
"pending"
);
addReplyLongLong
(
c
,
raxSize
(
cg
->
pel
));
addReplyLongLong
(
c
,
raxSize
(
cg
->
pel
));
addReply
Status
(
c
,
"last-delivered-id"
);
addReply
BulkCString
(
c
,
"last-delivered-id"
);
addReplyStreamID
(
c
,
&
cg
->
last_id
);
addReplyStreamID
(
c
,
&
cg
->
last_id
);
}
}
raxStop
(
&
ri
);
raxStop
(
&
ri
);
}
else
if
(
!
strcasecmp
(
opt
,
"STREAM"
)
&&
c
->
argc
==
3
)
{
}
else
if
(
!
strcasecmp
(
opt
,
"STREAM"
)
&&
c
->
argc
==
3
)
{
/* XINFO STREAM <key> (or the alias XINFO <key>). */
/* XINFO STREAM <key> (or the alias XINFO <key>). */
addReplyM
ultiBulk
Len
(
c
,
14
);
addReplyM
ap
Len
(
c
,
7
);
addReply
Status
(
c
,
"length"
);
addReply
BulkCString
(
c
,
"length"
);
addReplyLongLong
(
c
,
s
->
length
);
addReplyLongLong
(
c
,
s
->
length
);
addReply
Status
(
c
,
"radix-tree-keys"
);
addReply
BulkCString
(
c
,
"radix-tree-keys"
);
addReplyLongLong
(
c
,
raxSize
(
s
->
rax
));
addReplyLongLong
(
c
,
raxSize
(
s
->
rax
));
addReply
Status
(
c
,
"radix-tree-nodes"
);
addReply
BulkCString
(
c
,
"radix-tree-nodes"
);
addReplyLongLong
(
c
,
s
->
rax
->
numnodes
);
addReplyLongLong
(
c
,
s
->
rax
->
numnodes
);
addReply
Status
(
c
,
"groups"
);
addReply
BulkCString
(
c
,
"groups"
);
addReplyLongLong
(
c
,
s
->
cgroups
?
raxSize
(
s
->
cgroups
)
:
0
);
addReplyLongLong
(
c
,
s
->
cgroups
?
raxSize
(
s
->
cgroups
)
:
0
);
addReply
Status
(
c
,
"last-generated-id"
);
addReply
BulkCString
(
c
,
"last-generated-id"
);
addReplyStreamID
(
c
,
&
s
->
last_id
);
addReplyStreamID
(
c
,
&
s
->
last_id
);
/* To emit the first/last entry we us the streamReplyWithRange()
/* To emit the first/last entry we us the streamReplyWithRange()
...
@@ -2327,14 +2531,14 @@ NULL
...
@@ -2327,14 +2531,14 @@ NULL
streamID
start
,
end
;
streamID
start
,
end
;
start
.
ms
=
start
.
seq
=
0
;
start
.
ms
=
start
.
seq
=
0
;
end
.
ms
=
end
.
seq
=
UINT64_MAX
;
end
.
ms
=
end
.
seq
=
UINT64_MAX
;
addReply
Status
(
c
,
"first-entry"
);
addReply
BulkCString
(
c
,
"first-entry"
);
count
=
streamReplyWithRange
(
c
,
s
,
&
start
,
&
end
,
1
,
0
,
NULL
,
NULL
,
count
=
streamReplyWithRange
(
c
,
s
,
&
start
,
&
end
,
1
,
0
,
NULL
,
NULL
,
STREAM_RWR_RAWENTRIES
,
NULL
);
STREAM_RWR_RAWENTRIES
,
NULL
);
if
(
!
count
)
addReply
(
c
,
shared
.
nullbulk
);
if
(
!
count
)
addReply
Null
(
c
);
addReply
Status
(
c
,
"last-entry"
);
addReply
BulkCString
(
c
,
"last-entry"
);
count
=
streamReplyWithRange
(
c
,
s
,
&
start
,
&
end
,
1
,
1
,
NULL
,
NULL
,
count
=
streamReplyWithRange
(
c
,
s
,
&
start
,
&
end
,
1
,
1
,
NULL
,
NULL
,
STREAM_RWR_RAWENTRIES
,
NULL
);
STREAM_RWR_RAWENTRIES
,
NULL
);
if
(
!
count
)
addReply
(
c
,
shared
.
nullbulk
);
if
(
!
count
)
addReply
Null
(
c
);
}
else
{
}
else
{
addReplySubcommandSyntaxError
(
c
);
addReplySubcommandSyntaxError
(
c
);
}
}
...
...
src/t_string.c
View file @
49816941
...
@@ -80,7 +80,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
...
@@ -80,7 +80,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
if
((
flags
&
OBJ_SET_NX
&&
lookupKeyWrite
(
c
->
db
,
key
)
!=
NULL
)
||
if
((
flags
&
OBJ_SET_NX
&&
lookupKeyWrite
(
c
->
db
,
key
)
!=
NULL
)
||
(
flags
&
OBJ_SET_XX
&&
lookupKeyWrite
(
c
->
db
,
key
)
==
NULL
))
(
flags
&
OBJ_SET_XX
&&
lookupKeyWrite
(
c
->
db
,
key
)
==
NULL
))
{
{
addReply
(
c
,
abort_reply
?
abort_reply
:
shared
.
null
bulk
);
addReply
(
c
,
abort_reply
?
abort_reply
:
shared
.
null
[
c
->
resp
]
);
return
;
return
;
}
}
setKey
(
c
->
db
,
key
,
val
);
setKey
(
c
->
db
,
key
,
val
);
...
@@ -157,7 +157,7 @@ void psetexCommand(client *c) {
...
@@ -157,7 +157,7 @@ void psetexCommand(client *c) {
int
getGenericCommand
(
client
*
c
)
{
int
getGenericCommand
(
client
*
c
)
{
robj
*
o
;
robj
*
o
;
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
bulk
))
==
NULL
)
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
[
c
->
resp
]
))
==
NULL
)
return
C_OK
;
return
C_OK
;
if
(
o
->
type
!=
OBJ_STRING
)
{
if
(
o
->
type
!=
OBJ_STRING
)
{
...
@@ -285,14 +285,14 @@ void getrangeCommand(client *c) {
...
@@ -285,14 +285,14 @@ void getrangeCommand(client *c) {
void
mgetCommand
(
client
*
c
)
{
void
mgetCommand
(
client
*
c
)
{
int
j
;
int
j
;
addReply
MultiBulk
Len
(
c
,
c
->
argc
-
1
);
addReply
Array
Len
(
c
,
c
->
argc
-
1
);
for
(
j
=
1
;
j
<
c
->
argc
;
j
++
)
{
for
(
j
=
1
;
j
<
c
->
argc
;
j
++
)
{
robj
*
o
=
lookupKeyRead
(
c
->
db
,
c
->
argv
[
j
]);
robj
*
o
=
lookupKeyRead
(
c
->
db
,
c
->
argv
[
j
]);
if
(
o
==
NULL
)
{
if
(
o
==
NULL
)
{
addReply
(
c
,
shared
.
nullbulk
);
addReply
Null
(
c
);
}
else
{
}
else
{
if
(
o
->
type
!=
OBJ_STRING
)
{
if
(
o
->
type
!=
OBJ_STRING
)
{
addReply
(
c
,
shared
.
nullbulk
);
addReply
Null
(
c
);
}
else
{
}
else
{
addReplyBulk
(
c
,
o
);
addReplyBulk
(
c
,
o
);
}
}
...
@@ -301,24 +301,22 @@ void mgetCommand(client *c) {
...
@@ -301,24 +301,22 @@ void mgetCommand(client *c) {
}
}
void
msetGenericCommand
(
client
*
c
,
int
nx
)
{
void
msetGenericCommand
(
client
*
c
,
int
nx
)
{
int
j
,
busykeys
=
0
;
int
j
;
if
((
c
->
argc
%
2
)
==
0
)
{
if
((
c
->
argc
%
2
)
==
0
)
{
addReplyError
(
c
,
"wrong number of arguments for MSET"
);
addReplyError
(
c
,
"wrong number of arguments for MSET"
);
return
;
return
;
}
}
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
* set
no
thing
at all
if at least one
already ke
y exists. */
* set
any
thing if at least one
key alerad
y exists. */
if
(
nx
)
{
if
(
nx
)
{
for
(
j
=
1
;
j
<
c
->
argc
;
j
+=
2
)
{
for
(
j
=
1
;
j
<
c
->
argc
;
j
+=
2
)
{
if
(
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
j
])
!=
NULL
)
{
if
(
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
j
])
!=
NULL
)
{
busykeys
++
;
addReply
(
c
,
shared
.
czero
);
return
;
}
}
}
}
if
(
busykeys
)
{
addReply
(
c
,
shared
.
czero
);
return
;
}
}
}
for
(
j
=
1
;
j
<
c
->
argc
;
j
+=
2
)
{
for
(
j
=
1
;
j
<
c
->
argc
;
j
+=
2
)
{
...
...
src/t_zset.c
View file @
49816941
...
@@ -244,6 +244,61 @@ int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
...
@@ -244,6 +244,61 @@ int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
return
0
;
/* not found */
return
0
;
/* not found */
}
}
/* Update the score of an elmenent inside the sorted set skiplist.
* Note that the element must exist and must match 'score'.
* This function does not update the score in the hash table side, the
* caller should take care of it.
*
* Note that this function attempts to just update the node, in case after
* the score update, the node would be exactly at the same position.
* Otherwise the skiplist is modified by removing and re-adding a new
* element, which is more costly.
*
* The function returns the updated element skiplist node pointer. */
zskiplistNode
*
zslUpdateScore
(
zskiplist
*
zsl
,
double
curscore
,
sds
ele
,
double
newscore
)
{
zskiplistNode
*
update
[
ZSKIPLIST_MAXLEVEL
],
*
x
;
int
i
;
/* We need to seek to element to update to start: this is useful anyway,
* we'll have to update or remove it. */
x
=
zsl
->
header
;
for
(
i
=
zsl
->
level
-
1
;
i
>=
0
;
i
--
)
{
while
(
x
->
level
[
i
].
forward
&&
(
x
->
level
[
i
].
forward
->
score
<
curscore
||
(
x
->
level
[
i
].
forward
->
score
==
curscore
&&
sdscmp
(
x
->
level
[
i
].
forward
->
ele
,
ele
)
<
0
)))
{
x
=
x
->
level
[
i
].
forward
;
}
update
[
i
]
=
x
;
}
/* Jump to our element: note that this function assumes that the
* element with the matching score exists. */
x
=
x
->
level
[
0
].
forward
;
serverAssert
(
x
&&
curscore
==
x
->
score
&&
sdscmp
(
x
->
ele
,
ele
)
==
0
);
/* If the node, after the score update, would be still exactly
* at the same position, we can just update the score without
* actually removing and re-inserting the element in the skiplist. */
if
((
x
->
backward
==
NULL
||
x
->
backward
->
score
<
newscore
)
&&
(
x
->
level
[
0
].
forward
==
NULL
||
x
->
level
[
0
].
forward
->
score
>
newscore
))
{
x
->
score
=
newscore
;
return
x
;
}
/* No way to reuse the old node: we need to remove and insert a new
* one at a different place. */
zslDeleteNode
(
zsl
,
x
,
update
);
zskiplistNode
*
newnode
=
zslInsert
(
zsl
,
newscore
,
x
->
ele
);
/* We reused the old node x->ele SDS string, free the node now
* since zslInsert created a new one. */
x
->
ele
=
NULL
;
zslFreeNode
(
x
);
return
newnode
;
}
int
zslValueGteMin
(
double
value
,
zrangespec
*
spec
)
{
int
zslValueGteMin
(
double
value
,
zrangespec
*
spec
)
{
return
spec
->
minex
?
(
value
>
spec
->
min
)
:
(
value
>=
spec
->
min
);
return
spec
->
minex
?
(
value
>
spec
->
min
)
:
(
value
>=
spec
->
min
);
}
}
...
@@ -519,12 +574,12 @@ int zslParseLexRangeItem(robj *item, sds *dest, int *ex) {
...
@@ -519,12 +574,12 @@ int zslParseLexRangeItem(robj *item, sds *dest, int *ex) {
switch
(
c
[
0
])
{
switch
(
c
[
0
])
{
case
'+'
:
case
'+'
:
if
(
c
[
1
]
!=
'\0'
)
return
C_ERR
;
if
(
c
[
1
]
!=
'\0'
)
return
C_ERR
;
*
ex
=
0
;
*
ex
=
1
;
*
dest
=
shared
.
maxstring
;
*
dest
=
shared
.
maxstring
;
return
C_OK
;
return
C_OK
;
case
'-'
:
case
'-'
:
if
(
c
[
1
]
!=
'\0'
)
return
C_ERR
;
if
(
c
[
1
]
!=
'\0'
)
return
C_ERR
;
*
ex
=
0
;
*
ex
=
1
;
*
dest
=
shared
.
minstring
;
*
dest
=
shared
.
minstring
;
return
C_OK
;
return
C_OK
;
case
'('
:
case
'('
:
...
@@ -597,9 +652,8 @@ int zslIsInLexRange(zskiplist *zsl, zlexrangespec *range) {
...
@@ -597,9 +652,8 @@ int zslIsInLexRange(zskiplist *zsl, zlexrangespec *range) {
zskiplistNode
*
x
;
zskiplistNode
*
x
;
/* Test for ranges that will always be empty. */
/* Test for ranges that will always be empty. */
if
(
sdscmplex
(
range
->
min
,
range
->
max
)
>
1
||
int
cmp
=
sdscmplex
(
range
->
min
,
range
->
max
);
(
sdscmp
(
range
->
min
,
range
->
max
)
==
0
&&
if
(
cmp
>
0
||
(
cmp
==
0
&&
(
range
->
minex
||
range
->
maxex
)))
(
range
->
minex
||
range
->
maxex
)))
return
0
;
return
0
;
x
=
zsl
->
tail
;
x
=
zsl
->
tail
;
if
(
x
==
NULL
||
!
zslLexValueGteMin
(
x
->
ele
,
range
))
if
(
x
==
NULL
||
!
zslLexValueGteMin
(
x
->
ele
,
range
))
...
@@ -872,9 +926,8 @@ int zzlIsInLexRange(unsigned char *zl, zlexrangespec *range) {
...
@@ -872,9 +926,8 @@ int zzlIsInLexRange(unsigned char *zl, zlexrangespec *range) {
unsigned
char
*
p
;
unsigned
char
*
p
;
/* Test for ranges that will always be empty. */
/* Test for ranges that will always be empty. */
if
(
sdscmplex
(
range
->
min
,
range
->
max
)
>
1
||
int
cmp
=
sdscmplex
(
range
->
min
,
range
->
max
);
(
sdscmp
(
range
->
min
,
range
->
max
)
==
0
&&
if
(
cmp
>
0
||
(
cmp
==
0
&&
(
range
->
minex
||
range
->
maxex
)))
(
range
->
minex
||
range
->
maxex
)))
return
0
;
return
0
;
p
=
ziplistIndex
(
zl
,
-
2
);
/* Last element. */
p
=
ziplistIndex
(
zl
,
-
2
);
/* Last element. */
...
@@ -1341,13 +1394,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
...
@@ -1341,13 +1394,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
/* Remove and re-insert when score changes. */
/* Remove and re-insert when score changes. */
if
(
score
!=
curscore
)
{
if
(
score
!=
curscore
)
{
zskiplistNode
*
node
;
znode
=
zslUpdateScore
(
zs
->
zsl
,
curscore
,
ele
,
score
);
serverAssert
(
zslDelete
(
zs
->
zsl
,
curscore
,
ele
,
&
node
));
znode
=
zslInsert
(
zs
->
zsl
,
score
,
node
->
ele
);
/* We reused the node->ele SDS string, free the node now
* since zslInsert created a new one. */
node
->
ele
=
NULL
;
zslFreeNode
(
node
);
/* Note that we did not removed the original element from
/* Note that we did not removed the original element from
* the hash table representing the sorted set, so we just
* the hash table representing the sorted set, so we just
* update the score. */
* update the score. */
...
@@ -1591,7 +1638,7 @@ reply_to_client:
...
@@ -1591,7 +1638,7 @@ reply_to_client:
if
(
processed
)
if
(
processed
)
addReplyDouble
(
c
,
score
);
addReplyDouble
(
c
,
score
);
else
else
addReply
(
c
,
shared
.
nullbulk
);
addReply
Null
(
c
);
}
else
{
/* ZADD. */
}
else
{
/* ZADD. */
addReplyLongLong
(
c
,
ch
?
added
+
updated
:
added
);
addReplyLongLong
(
c
,
ch
?
added
+
updated
:
added
);
}
}
...
@@ -2380,7 +2427,7 @@ void zrangeGenericCommand(client *c, int reverse) {
...
@@ -2380,7 +2427,7 @@ void zrangeGenericCommand(client *c, int reverse) {
return
;
return
;
}
}
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
emptymultibulk
))
==
NULL
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
null
[
c
->
resp
]
))
==
NULL
||
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
||
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
/* Sanitize indexes. */
/* Sanitize indexes. */
...
@@ -2392,14 +2439,19 @@ void zrangeGenericCommand(client *c, int reverse) {
...
@@ -2392,14 +2439,19 @@ void zrangeGenericCommand(client *c, int reverse) {
/* Invariant: start >= 0, so this test will be true when end < 0.
/* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */
* The range is empty when start > end or start >= length. */
if
(
start
>
end
||
start
>=
llen
)
{
if
(
start
>
end
||
start
>=
llen
)
{
addReply
(
c
,
shared
.
emptymultibulk
);
addReply
Null
(
c
);
return
;
return
;
}
}
if
(
end
>=
llen
)
end
=
llen
-
1
;
if
(
end
>=
llen
)
end
=
llen
-
1
;
rangelen
=
(
end
-
start
)
+
1
;
rangelen
=
(
end
-
start
)
+
1
;
/* Return the result in form of a multi-bulk reply */
/* Return the result in form of a multi-bulk reply. RESP3 clients
addReplyMultiBulkLen
(
c
,
withscores
?
(
rangelen
*
2
)
:
rangelen
);
* will receive sub arrays with score->element, while RESP2 returned
* a flat array. */
if
(
withscores
&&
c
->
resp
==
2
)
addReplyArrayLen
(
c
,
rangelen
*
2
);
else
addReplyArrayLen
(
c
,
rangelen
);
if
(
zobj
->
encoding
==
OBJ_ENCODING_ZIPLIST
)
{
if
(
zobj
->
encoding
==
OBJ_ENCODING_ZIPLIST
)
{
unsigned
char
*
zl
=
zobj
->
ptr
;
unsigned
char
*
zl
=
zobj
->
ptr
;
...
@@ -2419,13 +2471,13 @@ void zrangeGenericCommand(client *c, int reverse) {
...
@@ -2419,13 +2471,13 @@ void zrangeGenericCommand(client *c, int reverse) {
while
(
rangelen
--
)
{
while
(
rangelen
--
)
{
serverAssertWithInfo
(
c
,
zobj
,
eptr
!=
NULL
&&
sptr
!=
NULL
);
serverAssertWithInfo
(
c
,
zobj
,
eptr
!=
NULL
&&
sptr
!=
NULL
);
serverAssertWithInfo
(
c
,
zobj
,
ziplistGet
(
eptr
,
&
vstr
,
&
vlen
,
&
vlong
));
serverAssertWithInfo
(
c
,
zobj
,
ziplistGet
(
eptr
,
&
vstr
,
&
vlen
,
&
vlong
));
if
(
withscores
&&
c
->
resp
>
2
)
addReplyArrayLen
(
c
,
2
);
if
(
vstr
==
NULL
)
if
(
vstr
==
NULL
)
addReplyBulkLongLong
(
c
,
vlong
);
addReplyBulkLongLong
(
c
,
vlong
);
else
else
addReplyBulkCBuffer
(
c
,
vstr
,
vlen
);
addReplyBulkCBuffer
(
c
,
vstr
,
vlen
);
if
(
withscores
)
addReplyDouble
(
c
,
zzlGetScore
(
sptr
));
if
(
withscores
)
addReplyDouble
(
c
,
zzlGetScore
(
sptr
));
if
(
reverse
)
if
(
reverse
)
zzlPrev
(
zl
,
&
eptr
,
&
sptr
);
zzlPrev
(
zl
,
&
eptr
,
&
sptr
);
...
@@ -2453,9 +2505,9 @@ void zrangeGenericCommand(client *c, int reverse) {
...
@@ -2453,9 +2505,9 @@ void zrangeGenericCommand(client *c, int reverse) {
while
(
rangelen
--
)
{
while
(
rangelen
--
)
{
serverAssertWithInfo
(
c
,
zobj
,
ln
!=
NULL
);
serverAssertWithInfo
(
c
,
zobj
,
ln
!=
NULL
);
ele
=
ln
->
ele
;
ele
=
ln
->
ele
;
if
(
withscores
&&
c
->
resp
>
2
)
addReplyArrayLen
(
c
,
2
);
addReplyBulkCBuffer
(
c
,
ele
,
sdslen
(
ele
));
addReplyBulkCBuffer
(
c
,
ele
,
sdslen
(
ele
));
if
(
withscores
)
if
(
withscores
)
addReplyDouble
(
c
,
ln
->
score
);
addReplyDouble
(
c
,
ln
->
score
);
ln
=
reverse
?
ln
->
backward
:
ln
->
level
[
0
].
forward
;
ln
=
reverse
?
ln
->
backward
:
ln
->
level
[
0
].
forward
;
}
}
}
else
{
}
else
{
...
@@ -2523,7 +2575,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2523,7 +2575,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
}
}
/* Ok, lookup the key and get the range */
/* Ok, lookup the key and get the range */
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
emptymultibulk
))
==
NULL
||
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
null
[
c
->
resp
]
))
==
NULL
||
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
if
(
zobj
->
encoding
==
OBJ_ENCODING_ZIPLIST
)
{
if
(
zobj
->
encoding
==
OBJ_ENCODING_ZIPLIST
)
{
...
@@ -2543,7 +2595,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2543,7 +2595,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
/* No "first" element in the specified interval. */
if
(
eptr
==
NULL
)
{
if
(
eptr
==
NULL
)
{
addReply
(
c
,
shared
.
emptymultibulk
);
addReply
Null
(
c
);
return
;
return
;
}
}
...
@@ -2554,7 +2606,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2554,7 +2606,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* We don't know in advance how many matching elements there are in the
/* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
* length in the output buffer, and will "fix" it later */
replylen
=
addDeferred
MultiBulkLength
(
c
);
replylen
=
add
Reply
Deferred
Len
(
c
);
/* If there is an offset, just traverse the number of elements without
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
* checking the score because that is done in the next loop. */
...
@@ -2576,19 +2628,18 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2576,19 +2628,18 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
if
(
!
zslValueLteMax
(
score
,
&
range
))
break
;
if
(
!
zslValueLteMax
(
score
,
&
range
))
break
;
}
}
/* We know the element exists, so ziplistGet should always succeed */
/* We know the element exists, so ziplistGet should always
* succeed */
serverAssertWithInfo
(
c
,
zobj
,
ziplistGet
(
eptr
,
&
vstr
,
&
vlen
,
&
vlong
));
serverAssertWithInfo
(
c
,
zobj
,
ziplistGet
(
eptr
,
&
vstr
,
&
vlen
,
&
vlong
));
rangelen
++
;
rangelen
++
;
if
(
withscores
&&
c
->
resp
>
2
)
addReplyArrayLen
(
c
,
2
);
if
(
vstr
==
NULL
)
{
if
(
vstr
==
NULL
)
{
addReplyBulkLongLong
(
c
,
vlong
);
addReplyBulkLongLong
(
c
,
vlong
);
}
else
{
}
else
{
addReplyBulkCBuffer
(
c
,
vstr
,
vlen
);
addReplyBulkCBuffer
(
c
,
vstr
,
vlen
);
}
}
if
(
withscores
)
addReplyDouble
(
c
,
score
);
if
(
withscores
)
{
addReplyDouble
(
c
,
score
);
}
/* Move to next node */
/* Move to next node */
if
(
reverse
)
{
if
(
reverse
)
{
...
@@ -2611,14 +2662,14 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2611,14 +2662,14 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
/* No "first" element in the specified interval. */
if
(
ln
==
NULL
)
{
if
(
ln
==
NULL
)
{
addReply
(
c
,
shared
.
emptymultibulk
);
addReply
Null
(
c
);
return
;
return
;
}
}
/* We don't know in advance how many matching elements there are in the
/* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
* length in the output buffer, and will "fix" it later */
replylen
=
addDeferred
MultiBulkLength
(
c
);
replylen
=
add
Reply
Deferred
Len
(
c
);
/* If there is an offset, just traverse the number of elements without
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
* checking the score because that is done in the next loop. */
...
@@ -2639,11 +2690,9 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2639,11 +2690,9 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
}
}
rangelen
++
;
rangelen
++
;
if
(
withscores
&&
c
->
resp
>
2
)
addReplyArrayLen
(
c
,
2
);
addReplyBulkCBuffer
(
c
,
ln
->
ele
,
sdslen
(
ln
->
ele
));
addReplyBulkCBuffer
(
c
,
ln
->
ele
,
sdslen
(
ln
->
ele
));
if
(
withscores
)
addReplyDouble
(
c
,
ln
->
score
);
if
(
withscores
)
{
addReplyDouble
(
c
,
ln
->
score
);
}
/* Move to next node */
/* Move to next node */
if
(
reverse
)
{
if
(
reverse
)
{
...
@@ -2656,11 +2705,8 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2656,11 +2705,8 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
serverPanic
(
"Unknown sorted set encoding"
);
serverPanic
(
"Unknown sorted set encoding"
);
}
}
if
(
withscores
)
{
if
(
withscores
&&
c
->
resp
==
2
)
rangelen
*=
2
;
rangelen
*=
2
;
setDeferredArrayLen
(
c
,
replylen
,
rangelen
);
}
setDeferredMultiBulkLength
(
c
,
replylen
,
rangelen
);
}
}
void
zrangebyscoreCommand
(
client
*
c
)
{
void
zrangebyscoreCommand
(
client
*
c
)
{
...
@@ -2871,7 +2917,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
...
@@ -2871,7 +2917,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
}
}
/* Ok, lookup the key and get the range */
/* Ok, lookup the key and get the range */
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
emptymultibulk
))
==
NULL
||
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
null
[
c
->
resp
]
))
==
NULL
||
checkType
(
c
,
zobj
,
OBJ_ZSET
))
checkType
(
c
,
zobj
,
OBJ_ZSET
))
{
{
zslFreeLexRange
(
&
range
);
zslFreeLexRange
(
&
range
);
...
@@ -2894,7 +2940,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
...
@@ -2894,7 +2940,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
/* No "first" element in the specified interval. */
if
(
eptr
==
NULL
)
{
if
(
eptr
==
NULL
)
{
addReply
(
c
,
shared
.
emptymultibulk
);
addReply
Null
(
c
);
zslFreeLexRange
(
&
range
);
zslFreeLexRange
(
&
range
);
return
;
return
;
}
}
...
@@ -2906,7 +2952,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
...
@@ -2906,7 +2952,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* We don't know in advance how many matching elements there are in the
/* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
* length in the output buffer, and will "fix" it later */
replylen
=
addDeferred
MultiBulkLength
(
c
);
replylen
=
add
Reply
Deferred
Len
(
c
);
/* If there is an offset, just traverse the number of elements without
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
* checking the score because that is done in the next loop. */
...
@@ -2958,7 +3004,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
...
@@ -2958,7 +3004,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
/* No "first" element in the specified interval. */
if
(
ln
==
NULL
)
{
if
(
ln
==
NULL
)
{
addReply
(
c
,
shared
.
emptymultibulk
);
addReply
Null
(
c
);
zslFreeLexRange
(
&
range
);
zslFreeLexRange
(
&
range
);
return
;
return
;
}
}
...
@@ -2966,7 +3012,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
...
@@ -2966,7 +3012,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* We don't know in advance how many matching elements there are in the
/* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
* length in the output buffer, and will "fix" it later */
replylen
=
addDeferred
MultiBulkLength
(
c
);
replylen
=
add
Reply
Deferred
Len
(
c
);
/* If there is an offset, just traverse the number of elements without
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
* checking the score because that is done in the next loop. */
...
@@ -3001,7 +3047,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
...
@@ -3001,7 +3047,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
}
}
zslFreeLexRange
(
&
range
);
zslFreeLexRange
(
&
range
);
setDeferred
MultiBulkLength
(
c
,
replylen
,
rangelen
);
setDeferred
ArrayLen
(
c
,
replylen
,
rangelen
);
}
}
void
zrangebylexCommand
(
client
*
c
)
{
void
zrangebylexCommand
(
client
*
c
)
{
...
@@ -3027,11 +3073,11 @@ void zscoreCommand(client *c) {
...
@@ -3027,11 +3073,11 @@ void zscoreCommand(client *c) {
robj
*
zobj
;
robj
*
zobj
;
double
score
;
double
score
;
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
null
bulk
))
==
NULL
||
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
null
[
c
->
resp
]
))
==
NULL
||
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
if
(
zsetScore
(
zobj
,
c
->
argv
[
2
]
->
ptr
,
&
score
)
==
C_ERR
)
{
if
(
zsetScore
(
zobj
,
c
->
argv
[
2
]
->
ptr
,
&
score
)
==
C_ERR
)
{
addReply
(
c
,
shared
.
nullbulk
);
addReply
Null
(
c
);
}
else
{
}
else
{
addReplyDouble
(
c
,
score
);
addReplyDouble
(
c
,
score
);
}
}
...
@@ -3043,7 +3089,7 @@ void zrankGenericCommand(client *c, int reverse) {
...
@@ -3043,7 +3089,7 @@ void zrankGenericCommand(client *c, int reverse) {
robj
*
zobj
;
robj
*
zobj
;
long
rank
;
long
rank
;
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
null
bulk
))
==
NULL
||
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
null
[
c
->
resp
]
))
==
NULL
||
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
serverAssertWithInfo
(
c
,
ele
,
sdsEncodedObject
(
ele
));
serverAssertWithInfo
(
c
,
ele
,
sdsEncodedObject
(
ele
));
...
@@ -3051,7 +3097,7 @@ void zrankGenericCommand(client *c, int reverse) {
...
@@ -3051,7 +3097,7 @@ void zrankGenericCommand(client *c, int reverse) {
if
(
rank
>=
0
)
{
if
(
rank
>=
0
)
{
addReplyLongLong
(
c
,
rank
);
addReplyLongLong
(
c
,
rank
);
}
else
{
}
else
{
addReply
(
c
,
shared
.
nullbulk
);
addReply
Null
(
c
);
}
}
}
}
...
@@ -3109,11 +3155,11 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
...
@@ -3109,11 +3155,11 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
/* No candidate for zpopping, return empty. */
/* No candidate for zpopping, return empty. */
if
(
!
zobj
)
{
if
(
!
zobj
)
{
addReply
(
c
,
shared
.
emptymultibulk
);
addReply
Null
(
c
);
return
;
return
;
}
}
void
*
arraylen_ptr
=
addDeferred
MultiBulkLength
(
c
);
void
*
arraylen_ptr
=
add
Reply
Deferred
Len
(
c
);
long
arraylen
=
0
;
long
arraylen
=
0
;
/* We emit the key only for the blocking variant. */
/* We emit the key only for the blocking variant. */
...
@@ -3180,7 +3226,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
...
@@ -3180,7 +3226,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
}
}
}
while
(
--
count
);
}
while
(
--
count
);
setDeferred
MultiBulkLength
(
c
,
arraylen_ptr
,
arraylen
+
(
emitkey
!=
0
));
setDeferred
ArrayLen
(
c
,
arraylen_ptr
,
arraylen
+
(
emitkey
!=
0
));
}
}
/* ZPOPMIN key [<count>] */
/* ZPOPMIN key [<count>] */
...
@@ -3235,7 +3281,7 @@ void blockingGenericZpopCommand(client *c, int where) {
...
@@ -3235,7 +3281,7 @@ void blockingGenericZpopCommand(client *c, int where) {
/* If we are inside a MULTI/EXEC and the zset is empty the only thing
/* If we are inside a MULTI/EXEC and the zset is empty the only thing
* we can do is treating it as a timeout (even with timeout 0). */
* we can do is treating it as a timeout (even with timeout 0). */
if
(
c
->
flags
&
CLIENT_MULTI
)
{
if
(
c
->
flags
&
CLIENT_MULTI
)
{
addReply
(
c
,
shared
.
nullmultibulk
);
addReply
NullArray
(
c
);
return
;
return
;
}
}
...
...
src/util.c
View file @
49816941
...
@@ -39,6 +39,7 @@
...
@@ -39,6 +39,7 @@
#include <float.h>
#include <float.h>
#include <stdint.h>
#include <stdint.h>
#include <errno.h>
#include <errno.h>
#include <time.h>
#include "util.h"
#include "util.h"
#include "sha1.h"
#include "sha1.h"
...
@@ -47,7 +48,7 @@
...
@@ -47,7 +48,7 @@
int
stringmatchlen
(
const
char
*
pattern
,
int
patternLen
,
int
stringmatchlen
(
const
char
*
pattern
,
int
patternLen
,
const
char
*
string
,
int
stringLen
,
int
nocase
)
const
char
*
string
,
int
stringLen
,
int
nocase
)
{
{
while
(
patternLen
)
{
while
(
patternLen
&&
stringLen
)
{
switch
(
pattern
[
0
])
{
switch
(
pattern
[
0
])
{
case
'*'
:
case
'*'
:
while
(
pattern
[
1
]
==
'*'
)
{
while
(
pattern
[
1
]
==
'*'
)
{
...
@@ -170,6 +171,22 @@ int stringmatch(const char *pattern, const char *string, int nocase) {
...
@@ -170,6 +171,22 @@ int stringmatch(const char *pattern, const char *string, int nocase) {
return
stringmatchlen
(
pattern
,
strlen
(
pattern
),
string
,
strlen
(
string
),
nocase
);
return
stringmatchlen
(
pattern
,
strlen
(
pattern
),
string
,
strlen
(
string
),
nocase
);
}
}
/* Fuzz stringmatchlen() trying to crash it with bad input. */
int
stringmatchlen_fuzz_test
(
void
)
{
char
str
[
32
];
char
pat
[
32
];
int
cycles
=
10000000
;
int
total_matches
=
0
;
while
(
cycles
--
)
{
int
strlen
=
rand
()
%
sizeof
(
str
);
int
patlen
=
rand
()
%
sizeof
(
pat
);
for
(
int
j
=
0
;
j
<
strlen
;
j
++
)
str
[
j
]
=
rand
()
%
128
;
for
(
int
j
=
0
;
j
<
patlen
;
j
++
)
pat
[
j
]
=
rand
()
%
128
;
total_matches
+=
stringmatchlen
(
pat
,
patlen
,
str
,
strlen
,
0
);
}
return
total_matches
;
}
/* Convert a string representing an amount of memory into the number of
/* Convert a string representing an amount of memory into the number of
* bytes, so for instance memtoll("1Gb") will return 1073741824 that is
* bytes, so for instance memtoll("1Gb") will return 1073741824 that is
* (1024*1024*1024).
* (1024*1024*1024).
...
@@ -346,6 +363,7 @@ int string2ll(const char *s, size_t slen, long long *value) {
...
@@ -346,6 +363,7 @@ int string2ll(const char *s, size_t slen, long long *value) {
int
negative
=
0
;
int
negative
=
0
;
unsigned
long
long
v
;
unsigned
long
long
v
;
/* A zero length string is not a valid number. */
if
(
plen
==
slen
)
if
(
plen
==
slen
)
return
0
;
return
0
;
...
@@ -355,6 +373,8 @@ int string2ll(const char *s, size_t slen, long long *value) {
...
@@ -355,6 +373,8 @@ int string2ll(const char *s, size_t slen, long long *value) {
return
1
;
return
1
;
}
}
/* Handle negative numbers: just set a flag and continue like if it
* was a positive number. Later convert into negative. */
if
(
p
[
0
]
==
'-'
)
{
if
(
p
[
0
]
==
'-'
)
{
negative
=
1
;
negative
=
1
;
p
++
;
plen
++
;
p
++
;
plen
++
;
...
@@ -368,13 +388,11 @@ int string2ll(const char *s, size_t slen, long long *value) {
...
@@ -368,13 +388,11 @@ int string2ll(const char *s, size_t slen, long long *value) {
if
(
p
[
0
]
>=
'1'
&&
p
[
0
]
<=
'9'
)
{
if
(
p
[
0
]
>=
'1'
&&
p
[
0
]
<=
'9'
)
{
v
=
p
[
0
]
-
'0'
;
v
=
p
[
0
]
-
'0'
;
p
++
;
plen
++
;
p
++
;
plen
++
;
}
else
if
(
p
[
0
]
==
'0'
&&
slen
==
1
)
{
*
value
=
0
;
return
1
;
}
else
{
}
else
{
return
0
;
return
0
;
}
}
/* Parse all the other digits, checking for overflow at every step. */
while
(
plen
<
slen
&&
p
[
0
]
>=
'0'
&&
p
[
0
]
<=
'9'
)
{
while
(
plen
<
slen
&&
p
[
0
]
>=
'0'
&&
p
[
0
]
<=
'9'
)
{
if
(
v
>
(
ULLONG_MAX
/
10
))
/* Overflow. */
if
(
v
>
(
ULLONG_MAX
/
10
))
/* Overflow. */
return
0
;
return
0
;
...
@@ -391,6 +409,8 @@ int string2ll(const char *s, size_t slen, long long *value) {
...
@@ -391,6 +409,8 @@ int string2ll(const char *s, size_t slen, long long *value) {
if
(
plen
<
slen
)
if
(
plen
<
slen
)
return
0
;
return
0
;
/* Convert to negative if needed, and do the final overflow check when
* converting from unsigned long long to long long. */
if
(
negative
)
{
if
(
negative
)
{
if
(
v
>
((
unsigned
long
long
)(
-
(
LLONG_MIN
+
1
))
+
1
))
/* Overflow. */
if
(
v
>
((
unsigned
long
long
)(
-
(
LLONG_MIN
+
1
))
+
1
))
/* Overflow. */
return
0
;
return
0
;
...
@@ -602,7 +622,7 @@ void getRandomHexChars(char *p, size_t len) {
...
@@ -602,7 +622,7 @@ void getRandomHexChars(char *p, size_t len) {
* already, this will be detected and handled correctly.
* already, this will be detected and handled correctly.
*
*
* The function does not try to normalize everything, but only the obvious
* The function does not try to normalize everything, but only the obvious
* case of one or more "../" appear
n
ing at the start of "filename"
* case of one or more "../" appearing at the start of "filename"
* relative path. */
* relative path. */
sds
getAbsolutePath
(
char
*
filename
)
{
sds
getAbsolutePath
(
char
*
filename
)
{
char
cwd
[
1024
];
char
cwd
[
1024
];
...
@@ -649,6 +669,24 @@ sds getAbsolutePath(char *filename) {
...
@@ -649,6 +669,24 @@ sds getAbsolutePath(char *filename) {
return
abspath
;
return
abspath
;
}
}
/*
* Gets the proper timezone in a more portable fashion
* i.e timezone variables are linux specific.
*/
unsigned
long
getTimeZone
(
void
)
{
#ifdef __linux__
return
timezone
;
#else
struct
timeval
tv
;
struct
timezone
tz
;
gettimeofday
(
&
tv
,
&
tz
);
return
tz
.
tz_minuteswest
*
60UL
;
#endif
}
/* Return true if the specified path is just a file basename without any
/* Return true if the specified path is just a file basename without any
* relative or absolute path. This function just checks that no / or \
* relative or absolute path. This function just checks that no / or \
* character exists inside the specified path, that's enough in the
* character exists inside the specified path, that's enough in the
...
...
src/util.h
View file @
49816941
...
@@ -40,6 +40,7 @@
...
@@ -40,6 +40,7 @@
int
stringmatchlen
(
const
char
*
p
,
int
plen
,
const
char
*
s
,
int
slen
,
int
nocase
);
int
stringmatchlen
(
const
char
*
p
,
int
plen
,
const
char
*
s
,
int
slen
,
int
nocase
);
int
stringmatch
(
const
char
*
p
,
const
char
*
s
,
int
nocase
);
int
stringmatch
(
const
char
*
p
,
const
char
*
s
,
int
nocase
);
int
stringmatchlen_fuzz_test
(
void
);
long
long
memtoll
(
const
char
*
p
,
int
*
err
);
long
long
memtoll
(
const
char
*
p
,
int
*
err
);
uint32_t
digits10
(
uint64_t
v
);
uint32_t
digits10
(
uint64_t
v
);
uint32_t
sdigits10
(
int64_t
v
);
uint32_t
sdigits10
(
int64_t
v
);
...
@@ -50,6 +51,7 @@ int string2ld(const char *s, size_t slen, long double *dp);
...
@@ -50,6 +51,7 @@ int string2ld(const char *s, size_t slen, long double *dp);
int
d2string
(
char
*
buf
,
size_t
len
,
double
value
);
int
d2string
(
char
*
buf
,
size_t
len
,
double
value
);
int
ld2string
(
char
*
buf
,
size_t
len
,
long
double
value
,
int
humanfriendly
);
int
ld2string
(
char
*
buf
,
size_t
len
,
long
double
value
,
int
humanfriendly
);
sds
getAbsolutePath
(
char
*
filename
);
sds
getAbsolutePath
(
char
*
filename
);
unsigned
long
getTimeZone
(
void
);
int
pathIsBaseName
(
char
*
path
);
int
pathIsBaseName
(
char
*
path
);
#ifdef REDIS_TEST
#ifdef REDIS_TEST
...
...
src/zmalloc.c
View file @
49816941
...
@@ -164,7 +164,7 @@ void *zrealloc(void *ptr, size_t size) {
...
@@ -164,7 +164,7 @@ void *zrealloc(void *ptr, size_t size) {
if
(
!
newptr
)
zmalloc_oom_handler
(
size
);
if
(
!
newptr
)
zmalloc_oom_handler
(
size
);
*
((
size_t
*
)
newptr
)
=
size
;
*
((
size_t
*
)
newptr
)
=
size
;
update_zmalloc_stat_free
(
oldsize
);
update_zmalloc_stat_free
(
oldsize
+
PREFIX_SIZE
);
update_zmalloc_stat_alloc
(
size
+
PREFIX_SIZE
);
update_zmalloc_stat_alloc
(
size
+
PREFIX_SIZE
);
return
(
char
*
)
newptr
+
PREFIX_SIZE
;
return
(
char
*
)
newptr
+
PREFIX_SIZE
;
#endif
#endif
...
@@ -183,7 +183,7 @@ size_t zmalloc_size(void *ptr) {
...
@@ -183,7 +183,7 @@ size_t zmalloc_size(void *ptr) {
return
size
+
PREFIX_SIZE
;
return
size
+
PREFIX_SIZE
;
}
}
size_t
zmalloc_usable
(
void
*
ptr
)
{
size_t
zmalloc_usable
(
void
*
ptr
)
{
return
zmalloc_
usabl
e
(
ptr
)
-
PREFIX_SIZE
;
return
zmalloc_
siz
e
(
ptr
)
-
PREFIX_SIZE
;
}
}
#endif
#endif
...
@@ -438,4 +438,20 @@ size_t zmalloc_get_memory_size(void) {
...
@@ -438,4 +438,20 @@ size_t zmalloc_get_memory_size(void) {
#endif
#endif
}
}
#ifdef REDIS_TEST
#define UNUSED(x) ((void)(x))
int
zmalloc_test
(
int
argc
,
char
**
argv
)
{
void
*
ptr
;
UNUSED
(
argc
);
UNUSED
(
argv
);
printf
(
"Initial used memory: %zu
\n
"
,
zmalloc_used_memory
());
ptr
=
zmalloc
(
123
);
printf
(
"Allocated 123 bytes; used: %zu
\n
"
,
zmalloc_used_memory
());
ptr
=
zrealloc
(
ptr
,
456
);
printf
(
"Reallocated to 456 bytes; used: %zu
\n
"
,
zmalloc_used_memory
());
zfree
(
ptr
);
printf
(
"Freed pointer; used: %zu
\n
"
,
zmalloc_used_memory
());
return
0
;
}
#endif
src/zmalloc.h
View file @
49816941
...
@@ -103,4 +103,8 @@ size_t zmalloc_usable(void *ptr);
...
@@ -103,4 +103,8 @@ size_t zmalloc_usable(void *ptr);
#define zmalloc_usable(p) zmalloc_size(p)
#define zmalloc_usable(p) zmalloc_size(p)
#endif
#endif
#ifdef REDIS_TEST
int
zmalloc_test
(
int
argc
,
char
**
argv
);
#endif
#endif
/* __ZMALLOC_H */
#endif
/* __ZMALLOC_H */
tests/integration/block-repl.tcl
View file @
49816941
...
@@ -49,7 +49,7 @@ start_server {tags {"repl"}} {
...
@@ -49,7 +49,7 @@ start_server {tags {"repl"}} {
set fd
[
open /tmp/repldump2.txt w
]
set fd
[
open /tmp/repldump2.txt w
]
puts -nonewline $fd $csv2
puts -nonewline $fd $csv2
close $fd
close $fd
puts
"Master -
Slave
inconsistency"
puts
"Master -
Replica
inconsistency"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
}
}
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
...
...
tests/integration/psync2-reg.tcl
View file @
49816941
...
@@ -29,7 +29,7 @@ start_server {} {
...
@@ -29,7 +29,7 @@ start_server {} {
wait_for_condition 50 1000
{
wait_for_condition 50 1000
{
[
$R
(
1
)
dbsize
]
== 1 &&
[
$R
(
2
)
dbsize
]
== 1
[
$R
(
1
)
dbsize
]
== 1 &&
[
$R
(
2
)
dbsize
]
== 1
}
else
{
}
else
{
fail
"
Slave
s not replicating from master"
fail
"
Replica
s not replicating from master"
}
}
$R
(
0
)
config set repl-backlog-size 10mb
$R
(
0
)
config set repl-backlog-size 10mb
$R
(
1
)
config set repl-backlog-size 10mb
$R
(
1
)
config set repl-backlog-size 10mb
...
@@ -41,12 +41,12 @@ start_server {} {
...
@@ -41,12 +41,12 @@ start_server {} {
set elapsed
[
expr
{[
clock milliseconds
]
-$cycle_start_time
}]
set elapsed
[
expr
{[
clock milliseconds
]
-$cycle_start_time
}]
if
{
$elapsed
> $duration*1000
}
break
if
{
$elapsed
> $duration*1000
}
break
if
{
rand
()
< .05
}
{
if
{
rand
()
< .05
}
{
test
"PSYNC2 #3899 regression: kill first
slave
"
{
test
"PSYNC2 #3899 regression: kill first
replica
"
{
$R
(
1
)
client kill type master
$R
(
1
)
client kill type master
}
}
}
}
if
{
rand
()
< .05
}
{
if
{
rand
()
< .05
}
{
test
"PSYNC2 #3899 regression: kill chained
slave
"
{
test
"PSYNC2 #3899 regression: kill chained
replica
"
{
$R
(
2
)
client kill type master
$R
(
2
)
client kill type master
}
}
}
}
...
...
tests/integration/psync2.tcl
View file @
49816941
...
@@ -33,9 +33,8 @@ start_server {} {
...
@@ -33,9 +33,8 @@ start_server {} {
set cycle 1
set cycle 1
while
{([
clock seconds
]
-$start_time
)
< $duration
}
{
while
{([
clock seconds
]
-$start_time
)
< $duration
}
{
test
"PSYNC2: --- CYCLE
$cycle
---"
{
test
"PSYNC2: --- CYCLE
$cycle
---"
{}
incr cycle
incr cycle
}
# Create a random replication layout.
# Create a random replication layout.
# Start with switching master
(
this simulates a failover
)
.
# Start with switching master
(
this simulates a failover
)
.
...
@@ -96,7 +95,7 @@ start_server {} {
...
@@ -96,7 +95,7 @@ start_server {} {
if
{
$disconnect
}
{
if
{
$disconnect
}
{
$R
(
$slave
_id
)
client kill type master
$R
(
$slave
_id
)
client kill type master
if
{
$debug
_msg
}
{
if
{
$debug
_msg
}
{
puts
"+++ Breaking link for
slave
#
$slave
_id"
puts
"+++ Breaking link for
replica
#
$slave
_id"
}
}
}
}
}
}
...
@@ -139,6 +138,11 @@ start_server {} {
...
@@ -139,6 +138,11 @@ start_server {} {
}
}
assert
{
$sum
== 4
}
assert
{
$sum
== 4
}
}
}
# Limit anyway the maximum number of cycles. This is useful when the
# test is skipped via --only option of the test suite. In that case
# we don't want to see many seconds of this test being just skipped.
if
{
$cycle
> 50
}
break
}
}
test
"PSYNC2: Bring the master back again for next test"
{
test
"PSYNC2: Bring the master back again for next test"
{
...
@@ -154,7 +158,7 @@ start_server {} {
...
@@ -154,7 +158,7 @@ start_server {} {
wait_for_condition 50 1000
{
wait_for_condition 50 1000
{
[
status $R
(
$master
_id
)
connected_slaves
]
== 4
[
status $R
(
$master
_id
)
connected_slaves
]
== 4
}
else
{
}
else
{
fail
"
Slave
not reconnecting"
fail
"
Replica
not reconnecting"
}
}
}
}
...
@@ -169,13 +173,13 @@ start_server {} {
...
@@ -169,13 +173,13 @@ start_server {} {
wait_for_condition 50 1000
{
wait_for_condition 50 1000
{
[
status $R
(
$master
_id
)
connected_slaves
]
== 4
[
status $R
(
$master
_id
)
connected_slaves
]
== 4
}
else
{
}
else
{
fail
"
Slave
not reconnecting"
fail
"
Replica
not reconnecting"
}
}
set new_sync_count
[
status $R
(
$master
_id
)
sync_full
]
set new_sync_count
[
status $R
(
$master
_id
)
sync_full
]
assert
{
$sync
_count == $new_sync_count
}
assert
{
$sync
_count == $new_sync_count
}
}
}
test
"PSYNC2:
Slave
RDB restart with EVALSHA in backlog issue #4483"
{
test
"PSYNC2:
Replica
RDB restart with EVALSHA in backlog issue #4483"
{
# Pick a random slave
# Pick a random slave
set slave_id
[
expr
{(
$master
_id+1
)
%5
}]
set slave_id
[
expr
{(
$master
_id+1
)
%5
}]
set sync_count
[
status $R
(
$master
_id
)
sync_full
]
set sync_count
[
status $R
(
$master
_id
)
sync_full
]
...
@@ -190,7 +194,7 @@ start_server {} {
...
@@ -190,7 +194,7 @@ start_server {} {
wait_for_condition 50 1000
{
wait_for_condition 50 1000
{
[
$R
(
$master
_id
)
debug digest
]
==
[
$R
(
$slave
_id
)
debug digest
]
[
$R
(
$master
_id
)
debug digest
]
==
[
$R
(
$slave
_id
)
debug digest
]
}
else
{
}
else
{
fail
"
Slave
not reconnecting"
fail
"
Replica
not reconnecting"
}
}
# Prevent the slave from receiving master updates, and at
# Prevent the slave from receiving master updates, and at
...
@@ -224,7 +228,7 @@ start_server {} {
...
@@ -224,7 +228,7 @@ start_server {} {
wait_for_condition 50 1000
{
wait_for_condition 50 1000
{
[
status $R
(
$master
_id
)
connected_slaves
]
== 4
[
status $R
(
$master
_id
)
connected_slaves
]
== 4
}
else
{
}
else
{
fail
"
Slave
not reconnecting"
fail
"
Replica
not reconnecting"
}
}
set new_sync_count
[
status $R
(
$master
_id
)
sync_full
]
set new_sync_count
[
status $R
(
$master
_id
)
sync_full
]
assert
{
$sync
_count == $new_sync_count
}
assert
{
$sync
_count == $new_sync_count
}
...
@@ -234,7 +238,7 @@ start_server {} {
...
@@ -234,7 +238,7 @@ start_server {} {
wait_for_condition 50 1000
{
wait_for_condition 50 1000
{
[
$R
(
$master
_id
)
debug digest
]
==
[
$R
(
$slave
_id
)
debug digest
]
[
$R
(
$master
_id
)
debug digest
]
==
[
$R
(
$slave
_id
)
debug digest
]
}
else
{
}
else
{
fail
"Debug digest mismatch between master and
slave
in post-restart handshake"
fail
"Debug digest mismatch between master and
replica
in post-restart handshake"
}
}
}
}
...
...
tests/integration/replication-2.tcl
View file @
49816941
...
@@ -16,7 +16,7 @@ start_server {tags {"repl"}} {
...
@@ -16,7 +16,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 100
{
wait_for_condition 50 100
{
[
r -1 get foo
]
eq
{
12345
}
[
r -1 get foo
]
eq
{
12345
}
}
else
{
}
else
{
fail
"Write did not reached
slave
"
fail
"Write did not reached
replica
"
}
}
}
}
...
@@ -34,7 +34,7 @@ start_server {tags {"repl"}} {
...
@@ -34,7 +34,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 100
{
wait_for_condition 50 100
{
[
r -1 get foo
]
eq
{
12345
}
[
r -1 get foo
]
eq
{
12345
}
}
else
{
}
else
{
fail
"Write did not reached
slave
"
fail
"Write did not reached
replica
"
}
}
}
}
...
@@ -60,7 +60,7 @@ start_server {tags {"repl"}} {
...
@@ -60,7 +60,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 100
{
wait_for_condition 50 100
{
[
r -1 get foo
]
eq
{
aaabbb
}
[
r -1 get foo
]
eq
{
aaabbb
}
}
else
{
}
else
{
fail
"Write did not reached
slave
"
fail
"Write did not reached
replica
"
}
}
}
}
...
@@ -81,7 +81,7 @@ start_server {tags {"repl"}} {
...
@@ -81,7 +81,7 @@ start_server {tags {"repl"}} {
set fd
[
open /tmp/repldump2.txt w
]
set fd
[
open /tmp/repldump2.txt w
]
puts -nonewline $fd $csv2
puts -nonewline $fd $csv2
close $fd
close $fd
puts
"Master -
Slave
inconsistency"
puts
"Master -
Replica
inconsistency"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
}
}
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
...
...
tests/integration/replication-3.tcl
View file @
49816941
...
@@ -25,7 +25,7 @@ start_server {tags {"repl"}} {
...
@@ -25,7 +25,7 @@ start_server {tags {"repl"}} {
set fd
[
open /tmp/repldump2.txt w
]
set fd
[
open /tmp/repldump2.txt w
]
puts -nonewline $fd $csv2
puts -nonewline $fd $csv2
close $fd
close $fd
puts
"Master -
Slave
inconsistency"
puts
"Master -
Replica
inconsistency"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
}
}
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
...
@@ -98,7 +98,7 @@ start_server {tags {"repl"}} {
...
@@ -98,7 +98,7 @@ start_server {tags {"repl"}} {
set fd
[
open /tmp/repldump2.txt w
]
set fd
[
open /tmp/repldump2.txt w
]
puts -nonewline $fd $csv2
puts -nonewline $fd $csv2
close $fd
close $fd
puts
"Master -
Slave
inconsistency"
puts
"Master -
Replica
inconsistency"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
}
}
...
...
tests/integration/replication-4.tcl
View file @
49816941
...
@@ -47,7 +47,7 @@ start_server {tags {"repl"}} {
...
@@ -47,7 +47,7 @@ start_server {tags {"repl"}} {
set fd
[
open /tmp/repldump2.txt w
]
set fd
[
open /tmp/repldump2.txt w
]
puts -nonewline $fd $csv2
puts -nonewline $fd $csv2
close $fd
close $fd
puts
"Master -
Slave
inconsistency"
puts
"Master -
Replica
inconsistency"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
}
}
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
...
...
tests/integration/replication-psync.tcl
View file @
49816941
...
@@ -60,7 +60,7 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
...
@@ -60,7 +60,7 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
if
(
$reconnect
)
{
if
(
$reconnect
)
{
for
{
set j 0
}
{
$j
< $duration*10
}
{
incr j
}
{
for
{
set j 0
}
{
$j
< $duration*10
}
{
incr j
}
{
after 100
after 100
# catch
{
puts
"MASTER
[
$master
dbsize
]
keys,
SLAVE
[
$slave
dbsize
]
keys"
}
# catch
{
puts
"MASTER
[
$master
dbsize
]
keys,
REPLICA
[
$slave
dbsize
]
keys"
}
if
{(
$j
% 20
)
== 0
}
{
if
{(
$j
% 20
)
== 0
}
{
catch
{
catch
{
...
@@ -96,7 +96,7 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
...
@@ -96,7 +96,7 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
set fd
[
open /tmp/repldump2.txt w
]
set fd
[
open /tmp/repldump2.txt w
]
puts -nonewline $fd $csv2
puts -nonewline $fd $csv2
close $fd
close $fd
puts
"Master -
Slave
inconsistency"
puts
"Master -
Replica
inconsistency"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
}
}
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
...
...
tests/integration/replication.tcl
View file @
49816941
...
@@ -32,7 +32,7 @@ start_server {tags {"repl"}} {
...
@@ -32,7 +32,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 1000
{
wait_for_condition 50 1000
{
[
string match *handshake*
[
$slave
role
]]
[
string match *handshake*
[
$slave
role
]]
}
else
{
}
else
{
fail
"
Slave
does not enter handshake state"
fail
"
Replica
does not enter handshake state"
}
}
}
}
...
@@ -45,7 +45,7 @@ start_server {tags {"repl"}} {
...
@@ -45,7 +45,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 1000
{
wait_for_condition 50 1000
{
[
log_file_matches $slave_log
"*Timeout connecting to the MASTER*"
]
[
log_file_matches $slave_log
"*Timeout connecting to the MASTER*"
]
}
else
{
}
else
{
fail
"
Slave
is not able to detect timeout"
fail
"
Replica
is not able to detect timeout"
}
}
}
}
}
}
...
@@ -66,7 +66,7 @@ start_server {tags {"repl"}} {
...
@@ -66,7 +66,7 @@ start_server {tags {"repl"}} {
[
lindex
[
$A role
]
0
]
eq
{
slave
}
&&
[
lindex
[
$A role
]
0
]
eq
{
slave
}
&&
[
string match
{
*master_link_status:up*
}
[
$A info replication
]]
[
string match
{
*master_link_status:up*
}
[
$A info replication
]]
}
else
{
}
else
{
fail
"Can't turn the instance into a
slave
"
fail
"Can't turn the instance into a
replica
"
}
}
}
}
...
@@ -77,7 +77,7 @@ start_server {tags {"repl"}} {
...
@@ -77,7 +77,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 100
{
wait_for_condition 50 100
{
[
$A debug digest
]
eq
[
$B debug digest
]
[
$A debug digest
]
eq
[
$B debug digest
]
}
else
{
}
else
{
fail
"Master and
slave
have different digest:
[
$A debug digest
]
VS
[
$B debug digest
]
"
fail
"Master and
replica
have different digest:
[
$A debug digest
]
VS
[
$B debug digest
]
"
}
}
}
}
...
@@ -102,10 +102,10 @@ start_server {tags {"repl"}} {
...
@@ -102,10 +102,10 @@ start_server {tags {"repl"}} {
[
lindex
[
$B role
]
0
]
eq
{
slave
}
&&
[
lindex
[
$B role
]
0
]
eq
{
slave
}
&&
[
string match
{
*master_link_status:up*
}
[
$B info replication
]]
[
string match
{
*master_link_status:up*
}
[
$B info replication
]]
}
else
{
}
else
{
fail
"Can't turn the instance into a
slave
"
fail
"Can't turn the instance into a
replica
"
}
}
# Push elements into the
"foo"
list of the new
slave
.
# Push elements into the
"foo"
list of the new
replica
.
# If the client is still attached to the instance, we'll get
# If the client is still attached to the instance, we'll get
# a desync between the two instances.
# a desync between the two instances.
$A rpush foo a b c
$A rpush foo a b c
...
@@ -116,7 +116,7 @@ start_server {tags {"repl"}} {
...
@@ -116,7 +116,7 @@ start_server {tags {"repl"}} {
[
$A lrange foo 0 -1
]
eq
{
a b c
}
&&
[
$A lrange foo 0 -1
]
eq
{
a b c
}
&&
[
$B lrange foo 0 -1
]
eq
{
a b c
}
[
$B lrange foo 0 -1
]
eq
{
a b c
}
}
else
{
}
else
{
fail
"Master and
slave
have different digest:
[
$A debug digest
]
VS
[
$B debug digest
]
"
fail
"Master and
replica
have different digest:
[
$A debug digest
]
VS
[
$B debug digest
]
"
}
}
}
}
}
}
...
@@ -135,7 +135,7 @@ start_server {tags {"repl"}} {
...
@@ -135,7 +135,7 @@ start_server {tags {"repl"}} {
s master_link_status
s master_link_status
}
{
down
}
}
{
down
}
test
{
The role should immediately be changed to
"
slave
"
}
{
test
{
The role should immediately be changed to
"
replica
"
}
{
s role
s role
}
{
slave
}
}
{
slave
}
...
@@ -154,7 +154,7 @@ start_server {tags {"repl"}} {
...
@@ -154,7 +154,7 @@ start_server {tags {"repl"}} {
wait_for_condition 500 100
{
wait_for_condition 500 100
{
[
r 0 get mykey
]
eq
{
bar
}
[
r 0 get mykey
]
eq
{
bar
}
}
else
{
}
else
{
fail
"SET on master did not propagated on
slave
"
fail
"SET on master did not propagated on
replica
"
}
}
}
}
...
@@ -201,7 +201,7 @@ foreach dl {no yes} {
...
@@ -201,7 +201,7 @@ foreach dl {no yes} {
lappend slaves
[
srv 0 client
]
lappend slaves
[
srv 0 client
]
start_server
{}
{
start_server
{}
{
lappend slaves
[
srv 0 client
]
lappend slaves
[
srv 0 client
]
test
"Connect multiple
slave
s at the same time (issue #141), diskless=
$dl
"
{
test
"Connect multiple
replica
s at the same time (issue #141), diskless=
$dl
"
{
# Send SLAVEOF commands to slaves
# Send SLAVEOF commands to slaves
[
lindex $slaves 0
]
slaveof $master_host $master_port
[
lindex $slaves 0
]
slaveof $master_host $master_port
[
lindex $slaves 1
]
slaveof $master_host $master_port
[
lindex $slaves 1
]
slaveof $master_host $master_port
...
@@ -220,7 +220,7 @@ foreach dl {no yes} {
...
@@ -220,7 +220,7 @@ foreach dl {no yes} {
}
}
}
}
if
{
$retry
== 0
}
{
if
{
$retry
== 0
}
{
error
"assertion:
Slave
s not correctly synchronized"
error
"assertion:
Replica
s not correctly synchronized"
}
}
# Wait that slaves acknowledge they are online so
# Wait that slaves acknowledge they are online so
...
@@ -231,7 +231,7 @@ foreach dl {no yes} {
...
@@ -231,7 +231,7 @@ foreach dl {no yes} {
[
lindex
[[
lindex $slaves 1
]
role
]
3
]
eq
{
connected
}
&&
[
lindex
[[
lindex $slaves 1
]
role
]
3
]
eq
{
connected
}
&&
[
lindex
[[
lindex $slaves 2
]
role
]
3
]
eq
{
connected
}
[
lindex
[[
lindex $slaves 2
]
role
]
3
]
eq
{
connected
}
}
else
{
}
else
{
fail
"
Slave
s still not connected after some time"
fail
"
Replica
s still not connected after some time"
}
}
# Stop the write load
# Stop the write load
...
@@ -248,7 +248,7 @@ foreach dl {no yes} {
...
@@ -248,7 +248,7 @@ foreach dl {no yes} {
[
$master
dbsize
]
==
[[
lindex $slaves 1
]
dbsize
]
&&
[
$master
dbsize
]
==
[[
lindex $slaves 1
]
dbsize
]
&&
[
$master
dbsize
]
==
[[
lindex $slaves 2
]
dbsize
]
[
$master
dbsize
]
==
[[
lindex $slaves 2
]
dbsize
]
}
else
{
}
else
{
fail
"Different number of keys between masted and
slave
after too long time."
fail
"Different number of keys between masted and
replica
after too long time."
}
}
# Check digests
# Check digests
...
@@ -266,3 +266,46 @@ foreach dl {no yes} {
...
@@ -266,3 +266,46 @@ foreach dl {no yes} {
}
}
}
}
}
}
start_server
{
tags
{
"repl"
}}
{
set master
[
srv 0 client
]
set master_host
[
srv 0 host
]
set master_port
[
srv 0 port
]
set load_handle0
[
start_write_load $master_host $master_port 3
]
start_server
{}
{
test
"Master stream is correctly processed while the replica has a script in -BUSY state"
{
set slave
[
srv 0 client
]
$slave config set lua-time-limit 500
$slave slaveof $master_host $master_port
# Wait for the slave to be online
wait_for_condition 500 100
{
[
lindex
[
$slave
role
]
3
]
eq
{
connected
}
}
else
{
fail
"Replica still not connected after some time"
}
# Wait some time to make sure the master is sending data
# to the slave.
after 5000
# Stop the ability of the slave to process data by sendig
# a script that will put it in BUSY state.
$slave eval
{
for i=1,3000000000 do end
}
0
# Wait some time again so that more master stream will
# be processed.
after 2000
# Stop the write load
stop_write_load $load_handle0
# number of keys
wait_for_condition 500 100
{
[
$master
debug digest
]
eq
[
$slave
debug digest
]
}
else
{
fail
"Different datasets between replica and master"
}
}
}
}
tests/sentinel/tests/00-base.tcl
View file @
49816941
...
@@ -17,7 +17,7 @@ test "Basic failover works if the master is down" {
...
@@ -17,7 +17,7 @@ test "Basic failover works if the master is down" {
wait_for_condition 1000 50
{
wait_for_condition 1000 50
{
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
}
else
{
}
else
{
fail
"At least one Sentinel did not receive
d
failover info"
fail
"At least one Sentinel did not receive failover info"
}
}
}
}
restart_instance redis $master_id
restart_instance redis $master_id
...
@@ -108,7 +108,7 @@ test "Failover works if we configure for absolute agreement" {
...
@@ -108,7 +108,7 @@ test "Failover works if we configure for absolute agreement" {
wait_for_condition 1000 50
{
wait_for_condition 1000 50
{
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
}
else
{
}
else
{
fail
"At least one Sentinel did not receive
d
failover info"
fail
"At least one Sentinel did not receive failover info"
}
}
}
}
restart_instance redis $master_id
restart_instance redis $master_id
...
...
tests/sentinel/tests/01-conf-update.tcl
View file @
49816941
...
@@ -16,7 +16,7 @@ test "We can failover with Sentinel 1 crashed" {
...
@@ -16,7 +16,7 @@ test "We can failover with Sentinel 1 crashed" {
wait_for_condition 1000 50
{
wait_for_condition 1000 50
{
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
}
else
{
}
else
{
fail
"Sentinel
$id
did not receive
d
failover info"
fail
"Sentinel
$id
did not receive failover info"
}
}
}
}
}
}
...
@@ -30,7 +30,7 @@ test "After Sentinel 1 is restarted, its config gets updated" {
...
@@ -30,7 +30,7 @@ test "After Sentinel 1 is restarted, its config gets updated" {
wait_for_condition 1000 50
{
wait_for_condition 1000 50
{
[
lindex
[
S 1 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
[
lindex
[
S 1 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
}
else
{
}
else
{
fail
"Restarted Sentinel did not receive
d
failover info"
fail
"Restarted Sentinel did not receive failover info"
}
}
}
}
...
...
tests/sentinel/tests/02-slaves-reconf.tcl
View file @
49816941
...
@@ -36,7 +36,7 @@ proc 02_crash_and_failover {} {
...
@@ -36,7 +36,7 @@ proc 02_crash_and_failover {} {
wait_for_condition 1000 50
{
wait_for_condition 1000 50
{
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
}
else
{
}
else
{
fail
"At least one Sentinel did not receive
d
failover info"
fail
"At least one Sentinel did not receive failover info"
}
}
}
}
restart_instance redis $master_id
restart_instance redis $master_id
...
...
tests/sentinel/tests/05-manual.tcl
View file @
49816941
...
@@ -12,7 +12,7 @@ test "Manual failover works" {
...
@@ -12,7 +12,7 @@ test "Manual failover works" {
wait_for_condition 1000 50
{
wait_for_condition 1000 50
{
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
[
lindex
[
S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
1
]
!= $old_port
}
else
{
}
else
{
fail
"At least one Sentinel did not receive
d
failover info"
fail
"At least one Sentinel did not receive failover info"
}
}
}
}
set addr
[
S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
set addr
[
S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
]
...
...
Prev
1
2
3
4
5
6
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