Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
2968d8e3
Unverified
Commit
2968d8e3
authored
Dec 12, 2019
by
Salvatore Sanfilippo
Committed by
GitHub
Dec 12, 2019
Browse files
Merge branch 'unstable' into ModuleSecurity
parents
ff682d79
c6fb9d09
Changes
170
Show whitespace changes
Inline
Side-by-side
src/stream.h
View file @
2968d8e3
...
@@ -88,7 +88,7 @@ typedef struct streamNACK {
...
@@ -88,7 +88,7 @@ typedef struct streamNACK {
/* Stream propagation informations, passed to functions in order to propagate
/* Stream propagation informations, passed to functions in order to propagate
* XCLAIM commands to AOF and slaves. */
* XCLAIM commands to AOF and slaves. */
typedef
struct
sreamPropInfo
{
typedef
struct
s
t
reamPropInfo
{
robj
*
keyname
;
robj
*
keyname
;
robj
*
groupname
;
robj
*
groupname
;
}
streamPropInfo
;
}
streamPropInfo
;
...
@@ -98,6 +98,7 @@ struct client;
...
@@ -98,6 +98,7 @@ struct client;
stream
*
streamNew
(
void
);
stream
*
streamNew
(
void
);
void
freeStream
(
stream
*
s
);
void
freeStream
(
stream
*
s
);
unsigned
long
streamLength
(
const
robj
*
subject
);
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
streamIteratorStart
(
streamIterator
*
si
,
stream
*
s
,
streamID
*
start
,
streamID
*
end
,
int
rev
);
void
streamIteratorStart
(
streamIterator
*
si
,
stream
*
s
,
streamID
*
start
,
streamID
*
end
,
int
rev
);
int
streamIteratorGetID
(
streamIterator
*
si
,
streamID
*
id
,
int64_t
*
numfields
);
int
streamIteratorGetID
(
streamIterator
*
si
,
streamID
*
id
,
int64_t
*
numfields
);
...
...
src/t_hash.c
View file @
2968d8e3
...
@@ -621,7 +621,7 @@ void hincrbyfloatCommand(client *c) {
...
@@ -621,7 +621,7 @@ void hincrbyfloatCommand(client *c) {
}
}
char
buf
[
MAX_LONG_DOUBLE_CHARS
];
char
buf
[
MAX_LONG_DOUBLE_CHARS
];
int
len
=
ld2string
(
buf
,
sizeof
(
buf
),
value
,
1
);
int
len
=
ld2string
(
buf
,
sizeof
(
buf
),
value
,
LD_STR_HUMAN
);
new
=
sdsnewlen
(
buf
,
len
);
new
=
sdsnewlen
(
buf
,
len
);
hashTypeSet
(
o
,
c
->
argv
[
2
]
->
ptr
,
new
,
HASH_SET_TAKE_VALUE
);
hashTypeSet
(
o
,
c
->
argv
[
2
]
->
ptr
,
new
,
HASH_SET_TAKE_VALUE
);
addReplyBulkCBuffer
(
c
,
buf
,
len
);
addReplyBulkCBuffer
(
c
,
buf
,
len
);
...
@@ -772,8 +772,8 @@ void genericHgetallCommand(client *c, int flags) {
...
@@ -772,8 +772,8 @@ void genericHgetallCommand(client *c, int flags) {
hashTypeIterator
*
hi
;
hashTypeIterator
*
hi
;
int
length
,
count
=
0
;
int
length
,
count
=
0
;
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
[
c
->
resp
]))
==
NULL
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
emptymap
[
c
->
resp
]))
||
checkType
(
c
,
o
,
OBJ_HASH
))
return
;
==
NULL
||
checkType
(
c
,
o
,
OBJ_HASH
))
return
;
/* We return a map if the user requested keys and values, like in the
/* We return a map if the user requested keys and values, like in the
* HGETALL case. Otherwise to use a flat array makes more sense. */
* HGETALL case. Otherwise to use a flat array makes more sense. */
...
...
src/t_list.c
View file @
2968d8e3
...
@@ -402,7 +402,7 @@ void lrangeCommand(client *c) {
...
@@ -402,7 +402,7 @@ void lrangeCommand(client *c) {
if
((
getLongFromObjectOrReply
(
c
,
c
->
argv
[
2
],
&
start
,
NULL
)
!=
C_OK
)
||
if
((
getLongFromObjectOrReply
(
c
,
c
->
argv
[
2
],
&
start
,
NULL
)
!=
C_OK
)
||
(
getLongFromObjectOrReply
(
c
,
c
->
argv
[
3
],
&
end
,
NULL
)
!=
C_OK
))
return
;
(
getLongFromObjectOrReply
(
c
,
c
->
argv
[
3
],
&
end
,
NULL
)
!=
C_OK
))
return
;
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
[
c
->
resp
]
))
==
NULL
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
emptyarray
))
==
NULL
||
checkType
(
c
,
o
,
OBJ_LIST
))
return
;
||
checkType
(
c
,
o
,
OBJ_LIST
))
return
;
llen
=
listTypeLength
(
o
);
llen
=
listTypeLength
(
o
);
...
@@ -414,7 +414,7 @@ void lrangeCommand(client *c) {
...
@@ -414,7 +414,7 @@ void lrangeCommand(client *c) {
/* 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
Null
(
c
);
addReply
(
c
,
shared
.
emptyarray
);
return
;
return
;
}
}
if
(
end
>=
llen
)
end
=
llen
-
1
;
if
(
end
>=
llen
)
end
=
llen
-
1
;
...
@@ -606,7 +606,7 @@ void rpoplpushCommand(client *c) {
...
@@ -606,7 +606,7 @@ void rpoplpushCommand(client *c) {
* Blocking POP operations
* Blocking POP operations
*----------------------------------------------------------------------------*/
*----------------------------------------------------------------------------*/
/* This is a helper function for handleClientsBlockedOn
List
s(). It's work
/* This is a helper function for handleClientsBlockedOn
Key
s(). It's work
* is to serve a specific client (receiver) that is blocked on 'key'
* is to serve a specific client (receiver) that is blocked on 'key'
* in the context of the specified 'db', doing the following:
* in the context of the specified 'db', doing the following:
*
*
...
...
src/t_set.c
View file @
2968d8e3
...
@@ -418,10 +418,10 @@ void spopWithCountCommand(client *c) {
...
@@ -418,10 +418,10 @@ void spopWithCountCommand(client *c) {
if
((
set
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
[
c
->
resp
]))
if
((
set
=
lookupKeyWriteOrReply
(
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
set
ASAP to avoid special
* cases later. */
* cases later. */
if
(
count
==
0
)
{
if
(
count
==
0
)
{
addReply
Null
(
c
);
addReply
(
c
,
shared
.
emptyset
[
c
->
resp
]
);
return
;
return
;
}
}
...
@@ -632,13 +632,13 @@ void srandmemberWithCountCommand(client *c) {
...
@@ -632,13 +632,13 @@ void srandmemberWithCountCommand(client *c) {
uniq
=
0
;
uniq
=
0
;
}
}
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
null
[
c
->
resp
]))
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
emptyset
[
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
Null
(
c
);
addReply
(
c
,
shared
.
emptyset
[
c
->
resp
]
);
return
;
return
;
}
}
...
@@ -813,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
...
@@ -813,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
}
}
addReply
(
c
,
shared
.
czero
);
addReply
(
c
,
shared
.
czero
);
}
else
{
}
else
{
addReply
Null
(
c
);
addReply
(
c
,
shared
.
emptyset
[
c
->
resp
]
);
}
}
return
;
return
;
}
}
...
...
src/t_stream.c
View file @
2968d8e3
...
@@ -67,6 +67,12 @@ void freeStream(stream *s) {
...
@@ -67,6 +67,12 @@ void freeStream(stream *s) {
zfree
(
s
);
zfree
(
s
);
}
}
/* Return the length of a stream. */
unsigned
long
streamLength
(
const
robj
*
subject
)
{
stream
*
s
=
subject
->
ptr
;
return
s
->
length
;
}
/* Generate the next stream item ID given the previous one. If the current
/* Generate the next stream item ID given the previous one. If the current
* milliseconds Unix time is greater than the previous one, just use this
* milliseconds Unix time is greater than the previous one, just use this
* as time part and start with sequence part of zero. Otherwise we use the
* as time part and start with sequence part of zero. Otherwise we use the
...
@@ -173,9 +179,19 @@ int streamCompareID(streamID *a, streamID *b) {
...
@@ -173,9 +179,19 @@ int streamCompareID(streamID *a, streamID *b) {
* C_ERR if an ID was given via 'use_id', but adding it failed since the
* C_ERR if an ID was given via 'use_id', but adding it failed since the
* current top ID is greater or equal. */
* current top ID is greater or equal. */
int
streamAppendItem
(
stream
*
s
,
robj
**
argv
,
int64_t
numfields
,
streamID
*
added_id
,
streamID
*
use_id
)
{
int
streamAppendItem
(
stream
*
s
,
robj
**
argv
,
int64_t
numfields
,
streamID
*
added_id
,
streamID
*
use_id
)
{
/* If an ID was given, check that it's greater than the last entry ID
* or return an error. */
/* Generate the new entry ID. */
if
(
use_id
&&
streamCompareID
(
use_id
,
&
s
->
last_id
)
<=
0
)
return
C_ERR
;
streamID
id
;
if
(
use_id
)
id
=
*
use_id
;
else
streamNextID
(
&
s
->
last_id
,
&
id
);
/* Check that the new ID is greater than the last entry ID
* or return an error. Automatically generated IDs might
* overflow (and wrap-around) when incrementing the sequence
part. */
if
(
streamCompareID
(
&
id
,
&
s
->
last_id
)
<=
0
)
return
C_ERR
;
/* Add the new entry. */
/* Add the new entry. */
raxIterator
ri
;
raxIterator
ri
;
...
@@ -192,13 +208,6 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
...
@@ -192,13 +208,6 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
}
}
raxStop
(
&
ri
);
raxStop
(
&
ri
);
/* Generate the new entry ID. */
streamID
id
;
if
(
use_id
)
id
=
*
use_id
;
else
streamNextID
(
&
s
->
last_id
,
&
id
);
/* We have to add the key into the radix tree in lexicographic order,
/* We have to add the key into the radix tree in lexicographic order,
* to do so we consider the ID as a single 128 bit number written in
* to do so we consider the ID as a single 128 bit number written in
* big endian, so that the most significant bytes are the first ones. */
* big endian, so that the most significant bytes are the first ones. */
...
@@ -242,17 +251,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
...
@@ -242,17 +251,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
* the current node is full. */
* the current node is full. */
if
(
lp
!=
NULL
)
{
if
(
lp
!=
NULL
)
{
if
(
server
.
stream_node_max_bytes
&&
if
(
server
.
stream_node_max_bytes
&&
lp_bytes
>
server
.
stream_node_max_bytes
)
lp_bytes
>
=
server
.
stream_node_max_bytes
)
{
{
lp
=
NULL
;
lp
=
NULL
;
}
else
if
(
server
.
stream_node_max_entries
)
{
}
else
if
(
server
.
stream_node_max_entries
)
{
int64_t
count
=
lpGetInteger
(
lpFirst
(
lp
));
int64_t
count
=
lpGetInteger
(
lpFirst
(
lp
));
if
(
count
>
server
.
stream_node_max_entries
)
lp
=
NULL
;
if
(
count
>
=
server
.
stream_node_max_entries
)
lp
=
NULL
;
}
}
}
}
int
flags
=
STREAM_ITEM_FLAG_NONE
;
int
flags
=
STREAM_ITEM_FLAG_NONE
;
if
(
lp
==
NULL
||
lp_bytes
>
server
.
stream_node_max_bytes
)
{
if
(
lp
==
NULL
||
lp_bytes
>
=
server
.
stream_node_max_bytes
)
{
master_id
=
id
;
master_id
=
id
;
streamEncodeID
(
rax_key
,
&
id
);
streamEncodeID
(
rax_key
,
&
id
);
/* Create the listpack having the master entry ID and fields. */
/* Create the listpack having the master entry ID and fields. */
...
@@ -1070,26 +1079,6 @@ robj *streamTypeLookupWriteOrCreate(client *c, robj *key) {
...
@@ -1070,26 +1079,6 @@ robj *streamTypeLookupWriteOrCreate(client *c, robj *key) {
return
o
;
return
o
;
}
}
/* Helper function to convert a string to an unsigned long long value.
* The function attempts to use the faster string2ll() function inside
* Redis: if it fails, strtoull() is used instead. The function returns
* 1 if the conversion happened successfully or 0 if the number is
* invalid or out of range. */
int
string2ull
(
const
char
*
s
,
unsigned
long
long
*
value
)
{
long
long
ll
;
if
(
string2ll
(
s
,
strlen
(
s
),
&
ll
))
{
if
(
ll
<
0
)
return
0
;
/* Negative values are out of range. */
*
value
=
ll
;
return
1
;
}
errno
=
0
;
char
*
endptr
=
NULL
;
*
value
=
strtoull
(
s
,
&
endptr
,
10
);
if
(
errno
==
EINVAL
||
errno
==
ERANGE
||
!
(
*
s
!=
'\0'
&&
*
endptr
==
'\0'
))
return
0
;
/* strtoull() failed. */
return
1
;
/* Conversion done! */
}
/* Parse a stream ID in the format given by clients to Redis, that is
/* Parse a stream ID in the format given by clients to Redis, that is
* <ms>-<seq>, and converts it into a streamID structure. If
* <ms>-<seq>, and converts it into a streamID structure. If
* the specified ID is invalid C_ERR is returned and an error is reported
* the specified ID is invalid C_ERR is returned and an error is reported
...
@@ -1217,6 +1206,14 @@ void xaddCommand(client *c) {
...
@@ -1217,6 +1206,14 @@ void xaddCommand(client *c) {
return
;
return
;
}
}
/* Return ASAP if minimal ID (0-0) was given so we avoid possibly creating
* a new stream and have streamAppendItem fail, leaving an empty key in the
* database. */
if
(
id_given
&&
id
.
ms
==
0
&&
id
.
seq
==
0
)
{
addReplyError
(
c
,
"The ID specified in XADD must be greater than 0-0"
);
return
;
}
/* Lookup the stream at key. */
/* Lookup the stream at key. */
robj
*
o
;
robj
*
o
;
stream
*
s
;
stream
*
s
;
...
...
src/t_zset.c
View file @
2968d8e3
...
@@ -1357,9 +1357,8 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
...
@@ -1357,9 +1357,8 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
/* Optimize: check if the element is too large or the list
/* Optimize: check if the element is too large or the list
* becomes too long *before* executing zzlInsert. */
* becomes too long *before* executing zzlInsert. */
zobj
->
ptr
=
zzlInsert
(
zobj
->
ptr
,
ele
,
score
);
zobj
->
ptr
=
zzlInsert
(
zobj
->
ptr
,
ele
,
score
);
if
(
zzlLength
(
zobj
->
ptr
)
>
server
.
zset_max_ziplist_entries
)
if
(
zzlLength
(
zobj
->
ptr
)
>
server
.
zset_max_ziplist_entries
||
zsetConvert
(
zobj
,
OBJ_ENCODING_SKIPLIST
);
sdslen
(
ele
)
>
server
.
zset_max_ziplist_value
)
if
(
sdslen
(
ele
)
>
server
.
zset_max_ziplist_value
)
zsetConvert
(
zobj
,
OBJ_ENCODING_SKIPLIST
);
zsetConvert
(
zobj
,
OBJ_ENCODING_SKIPLIST
);
if
(
newscore
)
*
newscore
=
score
;
if
(
newscore
)
*
newscore
=
score
;
*
flags
|=
ZADD_ADDED
;
*
flags
|=
ZADD_ADDED
;
...
@@ -2427,7 +2426,7 @@ void zrangeGenericCommand(client *c, int reverse) {
...
@@ -2427,7 +2426,7 @@ void zrangeGenericCommand(client *c, int reverse) {
return
;
return
;
}
}
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
null
[
c
->
resp
]
))
==
NULL
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
emptyarray
))
==
NULL
||
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
||
checkType
(
c
,
zobj
,
OBJ_ZSET
))
return
;
/* Sanitize indexes. */
/* Sanitize indexes. */
...
@@ -2439,7 +2438,7 @@ void zrangeGenericCommand(client *c, int reverse) {
...
@@ -2439,7 +2438,7 @@ 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
Null
(
c
);
addReply
(
c
,
shared
.
emptyarray
);
return
;
return
;
}
}
if
(
end
>=
llen
)
end
=
llen
-
1
;
if
(
end
>=
llen
)
end
=
llen
-
1
;
...
@@ -2575,7 +2574,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2575,7 +2574,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
.
null
[
c
->
resp
]
))
==
NULL
||
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
emptyarray
))
==
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
)
{
...
@@ -2595,7 +2594,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2595,7 +2594,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
Null
(
c
);
addReply
(
c
,
shared
.
emptyarray
);
return
;
return
;
}
}
...
@@ -2662,7 +2661,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
...
@@ -2662,7 +2661,7 @@ 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
Null
(
c
);
addReply
(
c
,
shared
.
emptyarray
);
return
;
return
;
}
}
...
@@ -2920,7 +2919,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
...
@@ -2920,7 +2919,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
.
null
[
c
->
resp
]
))
==
NULL
||
if
((
zobj
=
lookupKeyReadOrReply
(
c
,
key
,
shared
.
emptyarray
))
==
NULL
||
checkType
(
c
,
zobj
,
OBJ_ZSET
))
checkType
(
c
,
zobj
,
OBJ_ZSET
))
{
{
zslFreeLexRange
(
&
range
);
zslFreeLexRange
(
&
range
);
...
@@ -2943,7 +2942,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
...
@@ -2943,7 +2942,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
Null
(
c
);
addReply
(
c
,
shared
.
emptyarray
);
zslFreeLexRange
(
&
range
);
zslFreeLexRange
(
&
range
);
return
;
return
;
}
}
...
@@ -3007,7 +3006,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
...
@@ -3007,7 +3006,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
Null
(
c
);
addReply
(
c
,
shared
.
emptyarray
);
zslFreeLexRange
(
&
range
);
zslFreeLexRange
(
&
range
);
return
;
return
;
}
}
...
@@ -3161,7 +3160,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
...
@@ -3161,7 +3160,7 @@ 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
Null
(
c
);
addReply
(
c
,
shared
.
emptyarray
);
return
;
return
;
}
}
...
...
src/tls.c
0 → 100644
View file @
2968d8e3
/*
* Copyright (c) 2019, Redis Labs
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "server.h"
#include "connhelpers.h"
#include "adlist.h"
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#define REDIS_TLS_PROTO_TLSv1 (1<<0)
#define REDIS_TLS_PROTO_TLSv1_1 (1<<1)
#define REDIS_TLS_PROTO_TLSv1_2 (1<<2)
#define REDIS_TLS_PROTO_TLSv1_3 (1<<3)
/* Use safe defaults */
#ifdef TLS1_3_VERSION
#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2|REDIS_TLS_PROTO_TLSv1_3)
#else
#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2)
#endif
extern
ConnectionType
CT_Socket
;
SSL_CTX
*
redis_tls_ctx
;
static
int
parseProtocolsConfig
(
const
char
*
str
)
{
int
i
,
count
=
0
;
int
protocols
=
0
;
if
(
!
str
)
return
REDIS_TLS_PROTO_DEFAULT
;
sds
*
tokens
=
sdssplitlen
(
str
,
strlen
(
str
),
" "
,
1
,
&
count
);
if
(
!
tokens
)
{
serverLog
(
LL_WARNING
,
"Invalid tls-protocols configuration string"
);
return
-
1
;
}
for
(
i
=
0
;
i
<
count
;
i
++
)
{
if
(
!
strcasecmp
(
tokens
[
i
],
"tlsv1"
))
protocols
|=
REDIS_TLS_PROTO_TLSv1
;
else
if
(
!
strcasecmp
(
tokens
[
i
],
"tlsv1.1"
))
protocols
|=
REDIS_TLS_PROTO_TLSv1_1
;
else
if
(
!
strcasecmp
(
tokens
[
i
],
"tlsv1.2"
))
protocols
|=
REDIS_TLS_PROTO_TLSv1_2
;
else
if
(
!
strcasecmp
(
tokens
[
i
],
"tlsv1.3"
))
{
#ifdef TLS1_3_VERSION
protocols
|=
REDIS_TLS_PROTO_TLSv1_3
;
#else
serverLog
(
LL_WARNING
,
"TLSv1.3 is specified in tls-protocols but not supported by OpenSSL."
);
protocols
=
-
1
;
break
;
#endif
}
else
{
serverLog
(
LL_WARNING
,
"Invalid tls-protocols specified. "
"Use a combination of 'TLSv1', 'TLSv1.1', 'TLSv1.2' and 'TLSv1.3'."
);
protocols
=
-
1
;
break
;
}
}
sdsfreesplitres
(
tokens
,
count
);
return
protocols
;
}
/* list of connections with pending data already read from the socket, but not
* served to the reader yet. */
static
list
*
pending_list
=
NULL
;
void
tlsInit
(
void
)
{
ERR_load_crypto_strings
();
SSL_load_error_strings
();
SSL_library_init
();
if
(
!
RAND_poll
())
{
serverLog
(
LL_WARNING
,
"OpenSSL: Failed to seed random number generator."
);
}
pending_list
=
listCreate
();
/* Server configuration */
server
.
tls_auth_clients
=
1
;
/* Secure by default */
}
/* Attempt to configure/reconfigure TLS. This operation is atomic and will
* leave the SSL_CTX unchanged if fails.
*/
int
tlsConfigure
(
redisTLSContextConfig
*
ctx_config
)
{
char
errbuf
[
256
];
SSL_CTX
*
ctx
=
NULL
;
if
(
!
ctx_config
->
cert_file
)
{
serverLog
(
LL_WARNING
,
"No tls-cert-file configured!"
);
goto
error
;
}
if
(
!
ctx_config
->
key_file
)
{
serverLog
(
LL_WARNING
,
"No tls-key-file configured!"
);
goto
error
;
}
if
(
!
ctx_config
->
ca_cert_file
&&
!
ctx_config
->
ca_cert_dir
)
{
serverLog
(
LL_WARNING
,
"Either tls-ca-cert-file or tls-ca-cert-dir must be configured!"
);
goto
error
;
}
ctx
=
SSL_CTX_new
(
SSLv23_method
());
SSL_CTX_set_options
(
ctx
,
SSL_OP_NO_SSLv2
|
SSL_OP_NO_SSLv3
);
SSL_CTX_set_options
(
ctx
,
SSL_OP_SINGLE_DH_USE
);
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
SSL_CTX_set_options
(
ctx
,
SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
);
#endif
int
protocols
=
parseProtocolsConfig
(
ctx_config
->
protocols
);
if
(
protocols
==
-
1
)
goto
error
;
if
(
!
(
protocols
&
REDIS_TLS_PROTO_TLSv1
))
SSL_CTX_set_options
(
ctx
,
SSL_OP_NO_TLSv1
);
if
(
!
(
protocols
&
REDIS_TLS_PROTO_TLSv1_1
))
SSL_CTX_set_options
(
ctx
,
SSL_OP_NO_TLSv1_1
);
#ifdef SSL_OP_NO_TLSv1_2
if
(
!
(
protocols
&
REDIS_TLS_PROTO_TLSv1_2
))
SSL_CTX_set_options
(
ctx
,
SSL_OP_NO_TLSv1_2
);
#endif
#ifdef SSL_OP_NO_TLSv1_3
if
(
!
(
protocols
&
REDIS_TLS_PROTO_TLSv1_3
))
SSL_CTX_set_options
(
ctx
,
SSL_OP_NO_TLSv1_3
);
#endif
#ifdef SSL_OP_NO_COMPRESSION
SSL_CTX_set_options
(
ctx
,
SSL_OP_NO_COMPRESSION
);
#endif
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
SSL_CTX_set_options
(
ssl
->
ctx
,
SSL_OP_NO_CLIENT_RENEGOTIATION
);
#endif
if
(
ctx_config
->
prefer_server_ciphers
)
SSL_CTX_set_options
(
ctx
,
SSL_OP_CIPHER_SERVER_PREFERENCE
);
SSL_CTX_set_mode
(
ctx
,
SSL_MODE_ENABLE_PARTIAL_WRITE
|
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
);
SSL_CTX_set_verify
(
ctx
,
SSL_VERIFY_PEER
|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT
,
NULL
);
SSL_CTX_set_ecdh_auto
(
ctx
,
1
);
if
(
SSL_CTX_use_certificate_file
(
ctx
,
ctx_config
->
cert_file
,
SSL_FILETYPE_PEM
)
<=
0
)
{
ERR_error_string_n
(
ERR_get_error
(),
errbuf
,
sizeof
(
errbuf
));
serverLog
(
LL_WARNING
,
"Failed to load certificate: %s: %s"
,
ctx_config
->
cert_file
,
errbuf
);
goto
error
;
}
if
(
SSL_CTX_use_PrivateKey_file
(
ctx
,
ctx_config
->
key_file
,
SSL_FILETYPE_PEM
)
<=
0
)
{
ERR_error_string_n
(
ERR_get_error
(),
errbuf
,
sizeof
(
errbuf
));
serverLog
(
LL_WARNING
,
"Failed to load private key: %s: %s"
,
ctx_config
->
key_file
,
errbuf
);
goto
error
;
}
if
(
SSL_CTX_load_verify_locations
(
ctx
,
ctx_config
->
ca_cert_file
,
ctx_config
->
ca_cert_dir
)
<=
0
)
{
ERR_error_string_n
(
ERR_get_error
(),
errbuf
,
sizeof
(
errbuf
));
serverLog
(
LL_WARNING
,
"Failed to configure CA certificate(s) file/directory: %s"
,
errbuf
);
goto
error
;
}
if
(
ctx_config
->
dh_params_file
)
{
FILE
*
dhfile
=
fopen
(
ctx_config
->
dh_params_file
,
"r"
);
DH
*
dh
=
NULL
;
if
(
!
dhfile
)
{
serverLog
(
LL_WARNING
,
"Failed to load %s: %s"
,
ctx_config
->
dh_params_file
,
strerror
(
errno
));
goto
error
;
}
dh
=
PEM_read_DHparams
(
dhfile
,
NULL
,
NULL
,
NULL
);
fclose
(
dhfile
);
if
(
!
dh
)
{
serverLog
(
LL_WARNING
,
"%s: failed to read DH params."
,
ctx_config
->
dh_params_file
);
goto
error
;
}
if
(
SSL_CTX_set_tmp_dh
(
ctx
,
dh
)
<=
0
)
{
ERR_error_string_n
(
ERR_get_error
(),
errbuf
,
sizeof
(
errbuf
));
serverLog
(
LL_WARNING
,
"Failed to load DH params file: %s: %s"
,
ctx_config
->
dh_params_file
,
errbuf
);
DH_free
(
dh
);
goto
error
;
}
DH_free
(
dh
);
}
if
(
ctx_config
->
ciphers
&&
!
SSL_CTX_set_cipher_list
(
ctx
,
ctx_config
->
ciphers
))
{
serverLog
(
LL_WARNING
,
"Failed to configure ciphers: %s"
,
ctx_config
->
ciphers
);
goto
error
;
}
#ifdef TLS1_3_VERSION
if
(
ctx_config
->
ciphersuites
&&
!
SSL_CTX_set_ciphersuites
(
ctx
,
ctx_config
->
ciphersuites
))
{
serverLog
(
LL_WARNING
,
"Failed to configure ciphersuites: %s"
,
ctx_config
->
ciphersuites
);
goto
error
;
}
#endif
SSL_CTX_free
(
redis_tls_ctx
);
redis_tls_ctx
=
ctx
;
return
C_OK
;
error:
if
(
ctx
)
SSL_CTX_free
(
ctx
);
return
C_ERR
;
}
#ifdef TLS_DEBUGGING
#define TLSCONN_DEBUG(fmt, ...) \
serverLog(LL_DEBUG, "TLSCONN: " fmt, __VA_ARGS__)
#else
#define TLSCONN_DEBUG(fmt, ...)
#endif
ConnectionType
CT_TLS
;
/* Normal socket connections have a simple events/handler correlation.
*
* With TLS connections we need to handle cases where during a logical read
* or write operation, the SSL library asks to block for the opposite
* socket operation.
*
* When this happens, we need to do two things:
* 1. Make sure we register for the even.
* 2. Make sure we know which handler needs to execute when the
* event fires. That is, if we notify the caller of a write operation
* that it blocks, and SSL asks for a read, we need to trigger the
* write handler again on the next read event.
*
*/
typedef
enum
{
WANT_READ
=
1
,
WANT_WRITE
}
WantIOType
;
#define TLS_CONN_FLAG_READ_WANT_WRITE (1<<0)
#define TLS_CONN_FLAG_WRITE_WANT_READ (1<<1)
#define TLS_CONN_FLAG_FD_SET (1<<2)
typedef
struct
tls_connection
{
connection
c
;
int
flags
;
SSL
*
ssl
;
char
*
ssl_error
;
listNode
*
pending_list_node
;
}
tls_connection
;
connection
*
connCreateTLS
(
void
)
{
tls_connection
*
conn
=
zcalloc
(
sizeof
(
tls_connection
));
conn
->
c
.
type
=
&
CT_TLS
;
conn
->
c
.
fd
=
-
1
;
conn
->
ssl
=
SSL_new
(
redis_tls_ctx
);
return
(
connection
*
)
conn
;
}
connection
*
connCreateAcceptedTLS
(
int
fd
,
int
require_auth
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
connCreateTLS
();
conn
->
c
.
fd
=
fd
;
conn
->
c
.
state
=
CONN_STATE_ACCEPTING
;
if
(
!
require_auth
)
{
/* We still verify certificates if provided, but don't require them.
*/
SSL_set_verify
(
conn
->
ssl
,
SSL_VERIFY_PEER
,
NULL
);
}
SSL_set_fd
(
conn
->
ssl
,
conn
->
c
.
fd
);
SSL_set_accept_state
(
conn
->
ssl
);
return
(
connection
*
)
conn
;
}
static
void
tlsEventHandler
(
struct
aeEventLoop
*
el
,
int
fd
,
void
*
clientData
,
int
mask
);
/* Process the return code received from OpenSSL>
* Update the want parameter with expected I/O.
* Update the connection's error state if a real error has occured.
* Returns an SSL error code, or 0 if no further handling is required.
*/
static
int
handleSSLReturnCode
(
tls_connection
*
conn
,
int
ret_value
,
WantIOType
*
want
)
{
if
(
ret_value
<=
0
)
{
int
ssl_err
=
SSL_get_error
(
conn
->
ssl
,
ret_value
);
switch
(
ssl_err
)
{
case
SSL_ERROR_WANT_WRITE
:
*
want
=
WANT_WRITE
;
return
0
;
case
SSL_ERROR_WANT_READ
:
*
want
=
WANT_READ
;
return
0
;
case
SSL_ERROR_SYSCALL
:
conn
->
c
.
last_errno
=
errno
;
if
(
conn
->
ssl_error
)
zfree
(
conn
->
ssl_error
);
conn
->
ssl_error
=
errno
?
zstrdup
(
strerror
(
errno
))
:
NULL
;
break
;
default:
/* Error! */
conn
->
c
.
last_errno
=
0
;
if
(
conn
->
ssl_error
)
zfree
(
conn
->
ssl_error
);
conn
->
ssl_error
=
zmalloc
(
512
);
ERR_error_string_n
(
ERR_get_error
(),
conn
->
ssl_error
,
512
);
break
;
}
return
ssl_err
;
}
return
0
;
}
void
registerSSLEvent
(
tls_connection
*
conn
,
WantIOType
want
)
{
int
mask
=
aeGetFileEvents
(
server
.
el
,
conn
->
c
.
fd
);
switch
(
want
)
{
case
WANT_READ
:
if
(
mask
&
AE_WRITABLE
)
aeDeleteFileEvent
(
server
.
el
,
conn
->
c
.
fd
,
AE_WRITABLE
);
if
(
!
(
mask
&
AE_READABLE
))
aeCreateFileEvent
(
server
.
el
,
conn
->
c
.
fd
,
AE_READABLE
,
tlsEventHandler
,
conn
);
break
;
case
WANT_WRITE
:
if
(
mask
&
AE_READABLE
)
aeDeleteFileEvent
(
server
.
el
,
conn
->
c
.
fd
,
AE_READABLE
);
if
(
!
(
mask
&
AE_WRITABLE
))
aeCreateFileEvent
(
server
.
el
,
conn
->
c
.
fd
,
AE_WRITABLE
,
tlsEventHandler
,
conn
);
break
;
default:
serverAssert
(
0
);
break
;
}
}
void
updateSSLEvent
(
tls_connection
*
conn
)
{
int
mask
=
aeGetFileEvents
(
server
.
el
,
conn
->
c
.
fd
);
int
need_read
=
conn
->
c
.
read_handler
||
(
conn
->
flags
&
TLS_CONN_FLAG_WRITE_WANT_READ
);
int
need_write
=
conn
->
c
.
write_handler
||
(
conn
->
flags
&
TLS_CONN_FLAG_READ_WANT_WRITE
);
if
(
need_read
&&
!
(
mask
&
AE_READABLE
))
aeCreateFileEvent
(
server
.
el
,
conn
->
c
.
fd
,
AE_READABLE
,
tlsEventHandler
,
conn
);
if
(
!
need_read
&&
(
mask
&
AE_READABLE
))
aeDeleteFileEvent
(
server
.
el
,
conn
->
c
.
fd
,
AE_READABLE
);
if
(
need_write
&&
!
(
mask
&
AE_WRITABLE
))
aeCreateFileEvent
(
server
.
el
,
conn
->
c
.
fd
,
AE_WRITABLE
,
tlsEventHandler
,
conn
);
if
(
!
need_write
&&
(
mask
&
AE_WRITABLE
))
aeDeleteFileEvent
(
server
.
el
,
conn
->
c
.
fd
,
AE_WRITABLE
);
}
static
void
tlsHandleEvent
(
tls_connection
*
conn
,
int
mask
)
{
int
ret
;
TLSCONN_DEBUG
(
"tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d"
,
fd
,
conn
->
c
.
state
,
mask
,
conn
->
c
.
read_handler
!=
NULL
,
conn
->
c
.
write_handler
!=
NULL
,
conn
->
flags
);
ERR_clear_error
();
switch
(
conn
->
c
.
state
)
{
case
CONN_STATE_CONNECTING
:
if
(
connGetSocketError
((
connection
*
)
conn
))
{
conn
->
c
.
last_errno
=
errno
;
conn
->
c
.
state
=
CONN_STATE_ERROR
;
}
else
{
if
(
!
(
conn
->
flags
&
TLS_CONN_FLAG_FD_SET
))
{
SSL_set_fd
(
conn
->
ssl
,
conn
->
c
.
fd
);
conn
->
flags
|=
TLS_CONN_FLAG_FD_SET
;
}
ret
=
SSL_connect
(
conn
->
ssl
);
if
(
ret
<=
0
)
{
WantIOType
want
=
0
;
if
(
!
handleSSLReturnCode
(
conn
,
ret
,
&
want
))
{
registerSSLEvent
(
conn
,
want
);
/* Avoid hitting UpdateSSLEvent, which knows nothing
* of what SSL_connect() wants and instead looks at our
* R/W handlers.
*/
return
;
}
/* If not handled, it's an error */
conn
->
c
.
state
=
CONN_STATE_ERROR
;
}
else
{
conn
->
c
.
state
=
CONN_STATE_CONNECTED
;
}
}
if
(
!
callHandler
((
connection
*
)
conn
,
conn
->
c
.
conn_handler
))
return
;
conn
->
c
.
conn_handler
=
NULL
;
break
;
case
CONN_STATE_ACCEPTING
:
ret
=
SSL_accept
(
conn
->
ssl
);
if
(
ret
<=
0
)
{
WantIOType
want
=
0
;
if
(
!
handleSSLReturnCode
(
conn
,
ret
,
&
want
))
{
/* Avoid hitting UpdateSSLEvent, which knows nothing
* of what SSL_connect() wants and instead looks at our
* R/W handlers.
*/
registerSSLEvent
(
conn
,
want
);
return
;
}
/* If not handled, it's an error */
conn
->
c
.
state
=
CONN_STATE_ERROR
;
}
else
{
conn
->
c
.
state
=
CONN_STATE_CONNECTED
;
}
if
(
!
callHandler
((
connection
*
)
conn
,
conn
->
c
.
conn_handler
))
return
;
conn
->
c
.
conn_handler
=
NULL
;
break
;
case
CONN_STATE_CONNECTED
:
{
int
call_read
=
((
mask
&
AE_READABLE
)
&&
conn
->
c
.
read_handler
)
||
((
mask
&
AE_WRITABLE
)
&&
(
conn
->
flags
&
TLS_CONN_FLAG_READ_WANT_WRITE
));
int
call_write
=
((
mask
&
AE_WRITABLE
)
&&
conn
->
c
.
write_handler
)
||
((
mask
&
AE_READABLE
)
&&
(
conn
->
flags
&
TLS_CONN_FLAG_WRITE_WANT_READ
));
/* Normally we execute the readable event first, and the writable
* event laster. This is useful as sometimes we may be able
* to serve the reply of a query immediately after processing the
* query.
*
* However if WRITE_BARRIER is set in the mask, our application is
* asking us to do the reverse: never fire the writable event
* after the readable. In such a case, we invert the calls.
* This is useful when, for instance, we want to do things
* in the beforeSleep() hook, like fsynching a file to disk,
* before replying to a client. */
int
invert
=
conn
->
c
.
flags
&
CONN_FLAG_WRITE_BARRIER
;
if
(
!
invert
&&
call_read
)
{
conn
->
flags
&=
~
TLS_CONN_FLAG_READ_WANT_WRITE
;
if
(
!
callHandler
((
connection
*
)
conn
,
conn
->
c
.
read_handler
))
return
;
}
/* Fire the writable event. */
if
(
call_write
)
{
conn
->
flags
&=
~
TLS_CONN_FLAG_WRITE_WANT_READ
;
if
(
!
callHandler
((
connection
*
)
conn
,
conn
->
c
.
write_handler
))
return
;
}
/* If we have to invert the call, fire the readable event now
* after the writable one. */
if
(
invert
&&
call_read
)
{
conn
->
flags
&=
~
TLS_CONN_FLAG_READ_WANT_WRITE
;
if
(
!
callHandler
((
connection
*
)
conn
,
conn
->
c
.
read_handler
))
return
;
}
/* If SSL has pending that, already read from the socket, we're at
* risk of not calling the read handler again, make sure to add it
* to a list of pending connection that should be handled anyway. */
if
((
mask
&
AE_READABLE
))
{
if
(
SSL_pending
(
conn
->
ssl
)
>
0
)
{
if
(
!
conn
->
pending_list_node
)
{
listAddNodeTail
(
pending_list
,
conn
);
conn
->
pending_list_node
=
listLast
(
pending_list
);
}
}
else
if
(
conn
->
pending_list_node
)
{
listDelNode
(
pending_list
,
conn
->
pending_list_node
);
conn
->
pending_list_node
=
NULL
;
}
}
break
;
}
default:
break
;
}
updateSSLEvent
(
conn
);
}
static
void
tlsEventHandler
(
struct
aeEventLoop
*
el
,
int
fd
,
void
*
clientData
,
int
mask
)
{
UNUSED
(
el
);
UNUSED
(
fd
);
tls_connection
*
conn
=
clientData
;
tlsHandleEvent
(
conn
,
mask
);
}
static
void
connTLSClose
(
connection
*
conn_
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
conn_
;
if
(
conn
->
ssl
)
{
SSL_free
(
conn
->
ssl
);
conn
->
ssl
=
NULL
;
}
if
(
conn
->
ssl_error
)
{
zfree
(
conn
->
ssl_error
);
conn
->
ssl_error
=
NULL
;
}
if
(
conn
->
pending_list_node
)
{
listDelNode
(
pending_list
,
conn
->
pending_list_node
);
conn
->
pending_list_node
=
NULL
;
}
CT_Socket
.
close
(
conn_
);
}
static
int
connTLSAccept
(
connection
*
_conn
,
ConnectionCallbackFunc
accept_handler
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
_conn
;
int
ret
;
if
(
conn
->
c
.
state
!=
CONN_STATE_ACCEPTING
)
return
C_ERR
;
ERR_clear_error
();
/* Try to accept */
conn
->
c
.
conn_handler
=
accept_handler
;
ret
=
SSL_accept
(
conn
->
ssl
);
if
(
ret
<=
0
)
{
WantIOType
want
=
0
;
if
(
!
handleSSLReturnCode
(
conn
,
ret
,
&
want
))
{
registerSSLEvent
(
conn
,
want
);
/* We'll fire back */
return
C_OK
;
}
else
{
conn
->
c
.
state
=
CONN_STATE_ERROR
;
return
C_ERR
;
}
}
conn
->
c
.
state
=
CONN_STATE_CONNECTED
;
if
(
!
callHandler
((
connection
*
)
conn
,
conn
->
c
.
conn_handler
))
return
C_OK
;
conn
->
c
.
conn_handler
=
NULL
;
return
C_OK
;
}
static
int
connTLSConnect
(
connection
*
conn_
,
const
char
*
addr
,
int
port
,
const
char
*
src_addr
,
ConnectionCallbackFunc
connect_handler
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
conn_
;
if
(
conn
->
c
.
state
!=
CONN_STATE_NONE
)
return
C_ERR
;
ERR_clear_error
();
/* Initiate Socket connection first */
if
(
CT_Socket
.
connect
(
conn_
,
addr
,
port
,
src_addr
,
connect_handler
)
==
C_ERR
)
return
C_ERR
;
/* Return now, once the socket is connected we'll initiate
* TLS connection from the event handler.
*/
return
C_OK
;
}
static
int
connTLSWrite
(
connection
*
conn_
,
const
void
*
data
,
size_t
data_len
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
conn_
;
int
ret
,
ssl_err
;
if
(
conn
->
c
.
state
!=
CONN_STATE_CONNECTED
)
return
-
1
;
ERR_clear_error
();
ret
=
SSL_write
(
conn
->
ssl
,
data
,
data_len
);
if
(
ret
<=
0
)
{
WantIOType
want
=
0
;
if
(
!
(
ssl_err
=
handleSSLReturnCode
(
conn
,
ret
,
&
want
)))
{
if
(
want
==
WANT_READ
)
conn
->
flags
|=
TLS_CONN_FLAG_WRITE_WANT_READ
;
updateSSLEvent
(
conn
);
errno
=
EAGAIN
;
return
-
1
;
}
else
{
if
(
ssl_err
==
SSL_ERROR_ZERO_RETURN
||
((
ssl_err
==
SSL_ERROR_SYSCALL
&&
!
errno
)))
{
conn
->
c
.
state
=
CONN_STATE_CLOSED
;
return
0
;
}
else
{
conn
->
c
.
state
=
CONN_STATE_ERROR
;
return
-
1
;
}
}
}
return
ret
;
}
static
int
connTLSRead
(
connection
*
conn_
,
void
*
buf
,
size_t
buf_len
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
conn_
;
int
ret
;
int
ssl_err
;
if
(
conn
->
c
.
state
!=
CONN_STATE_CONNECTED
)
return
-
1
;
ERR_clear_error
();
ret
=
SSL_read
(
conn
->
ssl
,
buf
,
buf_len
);
if
(
ret
<=
0
)
{
WantIOType
want
=
0
;
if
(
!
(
ssl_err
=
handleSSLReturnCode
(
conn
,
ret
,
&
want
)))
{
if
(
want
==
WANT_WRITE
)
conn
->
flags
|=
TLS_CONN_FLAG_READ_WANT_WRITE
;
updateSSLEvent
(
conn
);
errno
=
EAGAIN
;
return
-
1
;
}
else
{
if
(
ssl_err
==
SSL_ERROR_ZERO_RETURN
||
((
ssl_err
==
SSL_ERROR_SYSCALL
)
&&
!
errno
))
{
conn
->
c
.
state
=
CONN_STATE_CLOSED
;
return
0
;
}
else
{
conn
->
c
.
state
=
CONN_STATE_ERROR
;
return
-
1
;
}
}
}
return
ret
;
}
static
const
char
*
connTLSGetLastError
(
connection
*
conn_
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
conn_
;
if
(
conn
->
ssl_error
)
return
conn
->
ssl_error
;
return
NULL
;
}
int
connTLSSetWriteHandler
(
connection
*
conn
,
ConnectionCallbackFunc
func
,
int
barrier
)
{
conn
->
write_handler
=
func
;
if
(
barrier
)
conn
->
flags
|=
CONN_FLAG_WRITE_BARRIER
;
else
conn
->
flags
&=
~
CONN_FLAG_WRITE_BARRIER
;
updateSSLEvent
((
tls_connection
*
)
conn
);
return
C_OK
;
}
int
connTLSSetReadHandler
(
connection
*
conn
,
ConnectionCallbackFunc
func
)
{
conn
->
read_handler
=
func
;
updateSSLEvent
((
tls_connection
*
)
conn
);
return
C_OK
;
}
static
void
setBlockingTimeout
(
tls_connection
*
conn
,
long
long
timeout
)
{
anetBlock
(
NULL
,
conn
->
c
.
fd
);
anetSendTimeout
(
NULL
,
conn
->
c
.
fd
,
timeout
);
anetRecvTimeout
(
NULL
,
conn
->
c
.
fd
,
timeout
);
}
static
void
unsetBlockingTimeout
(
tls_connection
*
conn
)
{
anetNonBlock
(
NULL
,
conn
->
c
.
fd
);
anetSendTimeout
(
NULL
,
conn
->
c
.
fd
,
0
);
anetRecvTimeout
(
NULL
,
conn
->
c
.
fd
,
0
);
}
static
int
connTLSBlockingConnect
(
connection
*
conn_
,
const
char
*
addr
,
int
port
,
long
long
timeout
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
conn_
;
int
ret
;
if
(
conn
->
c
.
state
!=
CONN_STATE_NONE
)
return
C_ERR
;
/* Initiate socket blocking connect first */
if
(
CT_Socket
.
blocking_connect
(
conn_
,
addr
,
port
,
timeout
)
==
C_ERR
)
return
C_ERR
;
/* Initiate TLS connection now. We set up a send/recv timeout on the socket,
* which means the specified timeout will not be enforced accurately. */
SSL_set_fd
(
conn
->
ssl
,
conn
->
c
.
fd
);
setBlockingTimeout
(
conn
,
timeout
);
if
((
ret
=
SSL_connect
(
conn
->
ssl
))
<=
0
)
{
conn
->
c
.
state
=
CONN_STATE_ERROR
;
return
C_ERR
;
}
unsetBlockingTimeout
(
conn
);
conn
->
c
.
state
=
CONN_STATE_CONNECTED
;
return
C_OK
;
}
static
ssize_t
connTLSSyncWrite
(
connection
*
conn_
,
char
*
ptr
,
ssize_t
size
,
long
long
timeout
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
conn_
;
setBlockingTimeout
(
conn
,
timeout
);
SSL_clear_mode
(
conn
->
ssl
,
SSL_MODE_ENABLE_PARTIAL_WRITE
);
int
ret
=
SSL_write
(
conn
->
ssl
,
ptr
,
size
);
SSL_set_mode
(
conn
->
ssl
,
SSL_MODE_ENABLE_PARTIAL_WRITE
);
unsetBlockingTimeout
(
conn
);
return
ret
;
}
static
ssize_t
connTLSSyncRead
(
connection
*
conn_
,
char
*
ptr
,
ssize_t
size
,
long
long
timeout
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
conn_
;
setBlockingTimeout
(
conn
,
timeout
);
int
ret
=
SSL_read
(
conn
->
ssl
,
ptr
,
size
);
unsetBlockingTimeout
(
conn
);
return
ret
;
}
static
ssize_t
connTLSSyncReadLine
(
connection
*
conn_
,
char
*
ptr
,
ssize_t
size
,
long
long
timeout
)
{
tls_connection
*
conn
=
(
tls_connection
*
)
conn_
;
ssize_t
nread
=
0
;
setBlockingTimeout
(
conn
,
timeout
);
size
--
;
while
(
size
)
{
char
c
;
if
(
SSL_read
(
conn
->
ssl
,
&
c
,
1
)
<=
0
)
{
nread
=
-
1
;
goto
exit
;
}
if
(
c
==
'\n'
)
{
*
ptr
=
'\0'
;
if
(
nread
&&
*
(
ptr
-
1
)
==
'\r'
)
*
(
ptr
-
1
)
=
'\0'
;
goto
exit
;
}
else
{
*
ptr
++
=
c
;
*
ptr
=
'\0'
;
nread
++
;
}
size
--
;
}
exit:
unsetBlockingTimeout
(
conn
);
return
nread
;
}
ConnectionType
CT_TLS
=
{
.
ae_handler
=
tlsEventHandler
,
.
accept
=
connTLSAccept
,
.
connect
=
connTLSConnect
,
.
blocking_connect
=
connTLSBlockingConnect
,
.
read
=
connTLSRead
,
.
write
=
connTLSWrite
,
.
close
=
connTLSClose
,
.
set_write_handler
=
connTLSSetWriteHandler
,
.
set_read_handler
=
connTLSSetReadHandler
,
.
get_last_error
=
connTLSGetLastError
,
.
sync_write
=
connTLSSyncWrite
,
.
sync_read
=
connTLSSyncRead
,
.
sync_readline
=
connTLSSyncReadLine
,
};
int
tlsHasPendingData
()
{
if
(
!
pending_list
)
return
0
;
return
listLength
(
pending_list
)
>
0
;
}
void
tlsProcessPendingData
()
{
listIter
li
;
listNode
*
ln
;
listRewind
(
pending_list
,
&
li
);
while
((
ln
=
listNext
(
&
li
)))
{
tls_connection
*
conn
=
listNodeValue
(
ln
);
tlsHandleEvent
(
conn
,
AE_READABLE
);
}
}
#else
/* USE_OPENSSL */
void
tlsInit
(
void
)
{
}
int
tlsConfigure
(
redisTLSContextConfig
*
ctx_config
)
{
UNUSED
(
ctx_config
);
return
C_OK
;
}
connection
*
connCreateTLS
(
void
)
{
return
NULL
;
}
connection
*
connCreateAcceptedTLS
(
int
fd
,
int
require_auth
)
{
UNUSED
(
fd
);
UNUSED
(
require_auth
);
return
NULL
;
}
int
tlsHasPendingData
()
{
return
0
;
}
void
tlsProcessPendingData
()
{
}
#endif
src/tracking.c
View file @
2968d8e3
...
@@ -60,6 +60,7 @@
...
@@ -60,6 +60,7 @@
* use the most significant bits instead of the full 24 bits. */
* use the most significant bits instead of the full 24 bits. */
#define TRACKING_TABLE_SIZE (1<<24)
#define TRACKING_TABLE_SIZE (1<<24)
rax
**
TrackingTable
=
NULL
;
rax
**
TrackingTable
=
NULL
;
unsigned
long
TrackingTableUsedSlots
=
0
;
robj
*
TrackingChannelName
;
robj
*
TrackingChannelName
;
/* Remove the tracking state from the client 'c'. Note that there is not much
/* Remove the tracking state from the client 'c'. Note that there is not much
...
@@ -109,8 +110,10 @@ void trackingRememberKeys(client *c) {
...
@@ -109,8 +110,10 @@ void trackingRememberKeys(client *c) {
sds
sdskey
=
c
->
argv
[
idx
]
->
ptr
;
sds
sdskey
=
c
->
argv
[
idx
]
->
ptr
;
uint64_t
hash
=
crc64
(
0
,
uint64_t
hash
=
crc64
(
0
,
(
unsigned
char
*
)
sdskey
,
sdslen
(
sdskey
))
&
(
TRACKING_TABLE_SIZE
-
1
);
(
unsigned
char
*
)
sdskey
,
sdslen
(
sdskey
))
&
(
TRACKING_TABLE_SIZE
-
1
);
if
(
TrackingTable
[
hash
]
==
NULL
)
if
(
TrackingTable
[
hash
]
==
NULL
)
{
TrackingTable
[
hash
]
=
raxNew
();
TrackingTable
[
hash
]
=
raxNew
();
TrackingTableUsedSlots
++
;
}
raxTryInsert
(
TrackingTable
[
hash
],
raxTryInsert
(
TrackingTable
[
hash
],
(
unsigned
char
*
)
&
c
->
id
,
sizeof
(
c
->
id
),
NULL
,
NULL
);
(
unsigned
char
*
)
&
c
->
id
,
sizeof
(
c
->
id
),
NULL
,
NULL
);
}
}
...
@@ -151,37 +154,59 @@ void sendTrackingMessage(client *c, long long hash) {
...
@@ -151,37 +154,59 @@ void sendTrackingMessage(client *c, long long hash) {
}
}
}
}
/* This function is called from signalModifiedKey() or other places in Redis
/* Invalidates a caching slot: this is actually the low level implementation
* when a key changes value. In the context of keys tracking, our task here is
* of the API that Redis calls externally, that is trackingInvalidateKey(). */
* to send a notification to every client that may have keys about such . */
void
trackingInvalidateSlot
(
uint64_t
slot
)
{
void
trackingInvalidateKey
(
robj
*
keyobj
)
{
if
(
TrackingTable
==
NULL
||
TrackingTable
[
slot
]
==
NULL
)
return
;
sds
sdskey
=
keyobj
->
ptr
;
uint64_t
hash
=
crc64
(
0
,
(
unsigned
char
*
)
sdskey
,
sdslen
(
sdskey
))
&
(
TRACKING_TABLE_SIZE
-
1
);
if
(
TrackingTable
==
NULL
||
TrackingTable
[
hash
]
==
NULL
)
return
;
raxIterator
ri
;
raxIterator
ri
;
raxStart
(
&
ri
,
TrackingTable
[
hash
]);
raxStart
(
&
ri
,
TrackingTable
[
slot
]);
raxSeek
(
&
ri
,
"^"
,
NULL
,
0
);
raxSeek
(
&
ri
,
"^"
,
NULL
,
0
);
while
(
raxNext
(
&
ri
))
{
while
(
raxNext
(
&
ri
))
{
uint64_t
id
;
uint64_t
id
;
memcpy
(
&
id
,
ri
.
key
,
ri
.
key_len
);
memcpy
(
&
id
,
ri
.
key
,
sizeof
(
id
)
);
client
*
c
=
lookupClientByID
(
id
);
client
*
c
=
lookupClientByID
(
id
);
if
(
c
==
NULL
||
!
(
c
->
flags
&
CLIENT_TRACKING
))
continue
;
if
(
c
==
NULL
||
!
(
c
->
flags
&
CLIENT_TRACKING
))
continue
;
sendTrackingMessage
(
c
,
hash
);
sendTrackingMessage
(
c
,
slot
);
}
}
raxStop
(
&
ri
);
raxStop
(
&
ri
);
/* Free the tracking table: we'll create the radix tree and populate it
/* Free the tracking table: we'll create the radix tree and populate it
* again if more keys will be modified in this hash slot. */
* again if more keys will be modified in this caching slot. */
raxFree
(
TrackingTable
[
hash
]);
raxFree
(
TrackingTable
[
slot
]);
TrackingTable
[
hash
]
=
NULL
;
TrackingTable
[
slot
]
=
NULL
;
TrackingTableUsedSlots
--
;
}
}
void
trackingInvalidateKeysOnFlush
(
int
dbid
)
{
/* This function is called from signalModifiedKey() or other places in Redis
UNUSED
(
dbid
);
* when a key changes value. In the context of keys tracking, our task here is
if
(
server
.
tracking_clients
==
0
)
return
;
* to send a notification to every client that may have keys about such caching
* slot. */
void
trackingInvalidateKey
(
robj
*
keyobj
)
{
if
(
TrackingTable
==
NULL
||
TrackingTableUsedSlots
==
0
)
return
;
sds
sdskey
=
keyobj
->
ptr
;
uint64_t
hash
=
crc64
(
0
,
(
unsigned
char
*
)
sdskey
,
sdslen
(
sdskey
))
&
(
TRACKING_TABLE_SIZE
-
1
);
trackingInvalidateSlot
(
hash
);
}
/* This function is called when one or all the Redis databases are flushed
* (dbid == -1 in case of FLUSHALL). Caching slots are not specific for
* each DB but are global: currently what we do is sending a special
* notification to clients with tracking enabled, invalidating the caching
* slot "-1", which means, "all the keys", in order to avoid flooding clients
* with many invalidation messages for all the keys they may hold.
*
* However trying to flush the tracking table here is very costly:
* we need scanning 16 million caching slots in the table to check
* if they are used, this introduces a big delay. So what we do is to really
* flush the table in the case of FLUSHALL. When a FLUSHDB is called instead
* we just send the invalidation message to all the clients, but don't
* flush the table: it will slowly get garbage collected as more keys
* are modified in the used caching slots. */
void
trackingInvalidateKeysOnFlush
(
int
dbid
)
{
if
(
server
.
tracking_clients
)
{
listNode
*
ln
;
listNode
*
ln
;
listIter
li
;
listIter
li
;
listRewind
(
server
.
clients
,
&
li
);
listRewind
(
server
.
clients
,
&
li
);
...
@@ -191,4 +216,81 @@ void trackingInvalidateKeysOnFlush(int dbid) {
...
@@ -191,4 +216,81 @@ void trackingInvalidateKeysOnFlush(int dbid) {
sendTrackingMessage
(
c
,
-
1
);
sendTrackingMessage
(
c
,
-
1
);
}
}
}
}
}
/* In case of FLUSHALL, reclaim all the memory used by tracking. */
if
(
dbid
==
-
1
&&
TrackingTable
)
{
for
(
int
j
=
0
;
j
<
TRACKING_TABLE_SIZE
&&
TrackingTableUsedSlots
>
0
;
j
++
)
{
if
(
TrackingTable
[
j
]
!=
NULL
)
{
raxFree
(
TrackingTable
[
j
]);
TrackingTable
[
j
]
=
NULL
;
TrackingTableUsedSlots
--
;
}
}
/* If there are no clients with tracking enabled, we can even
* reclaim the memory used by the table itself. The code assumes
* the table is allocated only if there is at least one client alive
* with tracking enabled. */
if
(
server
.
tracking_clients
==
0
)
{
zfree
(
TrackingTable
);
TrackingTable
=
NULL
;
}
}
}
/* Tracking forces Redis to remember information about which client may have
* keys about certian caching slots. In workloads where there are a lot of
* reads, but keys are hardly modified, the amount of information we have
* to remember server side could be a lot: for each 16 millions of caching
* slots we may end with a radix tree containing many entries.
*
* So Redis allows the user to configure a maximum fill rate for the
* invalidation table. This function makes sure that we don't go over the
* specified fill rate: if we are over, we can just evict informations about
* random caching slots, and send invalidation messages to clients like if
* the key was modified. */
void
trackingLimitUsedSlots
(
void
)
{
static
unsigned
int
timeout_counter
=
0
;
if
(
server
.
tracking_table_max_fill
==
0
)
return
;
/* No limits set. */
unsigned
int
max_slots
=
(
TRACKING_TABLE_SIZE
/
100
)
*
server
.
tracking_table_max_fill
;
if
(
TrackingTableUsedSlots
<=
max_slots
)
{
timeout_counter
=
0
;
return
;
/* Limit not reached. */
}
/* We have to invalidate a few slots to reach the limit again. The effort
* we do here is proportional to the number of times we entered this
* function and found that we are still over the limit. */
int
effort
=
100
*
(
timeout_counter
+
1
);
/* Let's start at a random position, and perform linear probing, in order
* to improve cache locality. However once we are able to find an used
* slot, jump again randomly, in order to avoid creating big holes in the
* table (that will make this funciton use more resourced later). */
while
(
effort
>
0
)
{
unsigned
int
idx
=
rand
()
%
TRACKING_TABLE_SIZE
;
do
{
effort
--
;
idx
=
(
idx
+
1
)
%
TRACKING_TABLE_SIZE
;
if
(
TrackingTable
[
idx
]
!=
NULL
)
{
trackingInvalidateSlot
(
idx
);
if
(
TrackingTableUsedSlots
<=
max_slots
)
{
timeout_counter
=
0
;
return
;
/* Return ASAP: we are again under the limit. */
}
else
{
break
;
/* Jump to next random position. */
}
}
}
while
(
effort
>
0
);
}
timeout_counter
++
;
}
/* This is just used in order to access the amount of used slots in the
* tracking table. */
unsigned
long
long
trackingGetUsedSlots
(
void
)
{
return
TrackingTableUsedSlots
;
}
}
src/util.c
View file @
2968d8e3
...
@@ -423,6 +423,26 @@ int string2ll(const char *s, size_t slen, long long *value) {
...
@@ -423,6 +423,26 @@ int string2ll(const char *s, size_t slen, long long *value) {
return
1
;
return
1
;
}
}
/* Helper function to convert a string to an unsigned long long value.
* The function attempts to use the faster string2ll() function inside
* Redis: if it fails, strtoull() is used instead. The function returns
* 1 if the conversion happened successfully or 0 if the number is
* invalid or out of range. */
int
string2ull
(
const
char
*
s
,
unsigned
long
long
*
value
)
{
long
long
ll
;
if
(
string2ll
(
s
,
strlen
(
s
),
&
ll
))
{
if
(
ll
<
0
)
return
0
;
/* Negative values are out of range. */
*
value
=
ll
;
return
1
;
}
errno
=
0
;
char
*
endptr
=
NULL
;
*
value
=
strtoull
(
s
,
&
endptr
,
10
);
if
(
errno
==
EINVAL
||
errno
==
ERANGE
||
!
(
*
s
!=
'\0'
&&
*
endptr
==
'\0'
))
return
0
;
/* strtoull() failed. */
return
1
;
/* Conversion done! */
}
/* Convert a string into a long. Returns 1 if the string could be parsed into a
/* Convert a string into a long. Returns 1 if the string could be parsed into a
* (non-overflowing) long, 0 otherwise. The value will be set to the parsed
* (non-overflowing) long, 0 otherwise. The value will be set to the parsed
* value when appropriate. */
* value when appropriate. */
...
@@ -468,6 +488,27 @@ int string2ld(const char *s, size_t slen, long double *dp) {
...
@@ -468,6 +488,27 @@ int string2ld(const char *s, size_t slen, long double *dp) {
return
1
;
return
1
;
}
}
/* Convert a string into a double. Returns 1 if the string could be parsed
* into a (non-overflowing) double, 0 otherwise. The value will be set to
* the parsed value when appropriate.
*
* Note that this function demands that the string strictly represents
* a double: no spaces or other characters before or after the string
* representing the number are accepted. */
int
string2d
(
const
char
*
s
,
size_t
slen
,
double
*
dp
)
{
errno
=
0
;
char
*
eptr
;
*
dp
=
strtod
(
s
,
&
eptr
);
if
(
slen
==
0
||
isspace
(((
const
char
*
)
s
)[
0
])
||
(
size_t
)(
eptr
-
(
char
*
)
s
)
!=
slen
||
(
errno
==
ERANGE
&&
(
*
dp
==
HUGE_VAL
||
*
dp
==
-
HUGE_VAL
||
*
dp
==
0
))
||
isnan
(
*
dp
))
return
0
;
return
1
;
}
/* Convert a double to a string representation. Returns the number of bytes
/* Convert a double to a string representation. Returns the number of bytes
* required. The representation should always be parsable by strtod(3).
* required. The representation should always be parsable by strtod(3).
* This function does not support human-friendly formatting like ld2string
* This function does not support human-friendly formatting like ld2string
...
@@ -510,15 +551,17 @@ int d2string(char *buf, size_t len, double value) {
...
@@ -510,15 +551,17 @@ int d2string(char *buf, size_t len, double value) {
return
len
;
return
len
;
}
}
/* Convert a long double into a string. If humanfriendly is non-zero
/* Create a string object from a long double.
* it does not use exponential format and trims trailing zeroes at the end,
* If mode is humanfriendly it does not use exponential format and trims trailing
* however this results in loss of precision. Otherwise exp format is used
* zeroes at the end (may result in loss of precision).
* and the output of snprintf() is not modified.
* If mode is default exp format is used and the output of snprintf()
* is not modified (may result in loss of precision).
* If mode is hex hexadecimal format is used (no loss of precision)
*
*
* The function returns the length of the string or zero if there was not
* The function returns the length of the string or zero if there was not
* enough buffer room to store it. */
* enough buffer room to store it. */
int
ld2string
(
char
*
buf
,
size_t
len
,
long
double
value
,
int
humanfriendly
)
{
int
ld2string
(
char
*
buf
,
size_t
len
,
long
double
value
,
ld2string_mode
mode
)
{
size_t
l
;
size_t
l
=
0
;
if
(
isinf
(
value
))
{
if
(
isinf
(
value
))
{
/* Libc in odd systems (Hi Solaris!) will format infinite in a
/* Libc in odd systems (Hi Solaris!) will format infinite in a
...
@@ -531,13 +574,23 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
...
@@ -531,13 +574,23 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
memcpy
(
buf
,
"-inf"
,
4
);
memcpy
(
buf
,
"-inf"
,
4
);
l
=
4
;
l
=
4
;
}
}
}
else
if
(
humanfriendly
)
{
}
else
{
switch
(
mode
)
{
case
LD_STR_AUTO
:
l
=
snprintf
(
buf
,
len
,
"%.17Lg"
,
value
);
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
break
;
case
LD_STR_HEX
:
l
=
snprintf
(
buf
,
len
,
"%La"
,
value
);
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
break
;
case
LD_STR_HUMAN
:
/* We use 17 digits precision since with 128 bit floats that precision
/* We use 17 digits precision since with 128 bit floats that precision
* after rounding is able to represent most small decimal numbers in a
* after rounding is able to represent most small decimal numbers in a
* way that is "non surprising" for the user (that is, most small
* way that is "non surprising" for the user (that is, most small
* decimal numbers will be represented in a way that when converted
* decimal numbers will be represented in a way that when converted
* back into a string are exactly the same as what the user typed.) */
* back into a string are exactly the same as what the user typed.) */
l
=
snprintf
(
buf
,
len
,
"%.17Lf"
,
value
);
l
=
snprintf
(
buf
,
len
,
"%.17Lf"
,
value
);
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
/* Now remove trailing zeroes after the '.' */
/* Now remove trailing zeroes after the '.' */
if
(
strchr
(
buf
,
'.'
)
!=
NULL
)
{
if
(
strchr
(
buf
,
'.'
)
!=
NULL
)
{
...
@@ -548,9 +601,9 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
...
@@ -548,9 +601,9 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
}
}
if
(
*
p
==
'.'
)
l
--
;
if
(
*
p
==
'.'
)
l
--
;
}
}
}
else
{
break
;
l
=
snprintf
(
buf
,
len
,
"%.17Lg"
,
value
);
default:
return
0
;
/* Invalid mode. */
if
(
l
+
1
>
len
)
return
0
;
/* No room. */
}
}
}
buf
[
l
]
=
'\0'
;
buf
[
l
]
=
'\0'
;
return
l
;
return
l
;
...
...
src/util.h
View file @
2968d8e3
...
@@ -38,6 +38,13 @@
...
@@ -38,6 +38,13 @@
* This should be the size of the buffer given to ld2string */
* This should be the size of the buffer given to ld2string */
#define MAX_LONG_DOUBLE_CHARS 5*1024
#define MAX_LONG_DOUBLE_CHARS 5*1024
/* long double to string convertion options */
typedef
enum
{
LD_STR_AUTO
,
/* %.17Lg */
LD_STR_HUMAN
,
/* %.17Lf + Trimming of trailing zeros */
LD_STR_HEX
/* %La */
}
ld2string_mode
;
int
stringmatchlen
(
const
char
*
p
,
int
plen
,
const
char
*
s
,
int
slen
,
int
nocase
);
int
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
);
int
stringmatchlen_fuzz_test
(
void
);
...
@@ -46,10 +53,12 @@ uint32_t digits10(uint64_t v);
...
@@ -46,10 +53,12 @@ uint32_t digits10(uint64_t v);
uint32_t
sdigits10
(
int64_t
v
);
uint32_t
sdigits10
(
int64_t
v
);
int
ll2string
(
char
*
s
,
size_t
len
,
long
long
value
);
int
ll2string
(
char
*
s
,
size_t
len
,
long
long
value
);
int
string2ll
(
const
char
*
s
,
size_t
slen
,
long
long
*
value
);
int
string2ll
(
const
char
*
s
,
size_t
slen
,
long
long
*
value
);
int
string2ull
(
const
char
*
s
,
unsigned
long
long
*
value
);
int
string2l
(
const
char
*
s
,
size_t
slen
,
long
*
value
);
int
string2l
(
const
char
*
s
,
size_t
slen
,
long
*
value
);
int
string2ld
(
const
char
*
s
,
size_t
slen
,
long
double
*
dp
);
int
string2ld
(
const
char
*
s
,
size_t
slen
,
long
double
*
dp
);
int
string2d
(
const
char
*
s
,
size_t
slen
,
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
,
ld2string_mode
mode
);
sds
getAbsolutePath
(
char
*
filename
);
sds
getAbsolutePath
(
char
*
filename
);
unsigned
long
getTimeZone
(
void
);
unsigned
long
getTimeZone
(
void
);
int
pathIsBaseName
(
char
*
path
);
int
pathIsBaseName
(
char
*
path
);
...
...
src/zmalloc.c
View file @
2968d8e3
...
@@ -294,6 +294,26 @@ size_t zmalloc_get_rss(void) {
...
@@ -294,6 +294,26 @@ size_t zmalloc_get_rss(void) {
return
t_info
.
resident_size
;
return
t_info
.
resident_size
;
}
}
#elif defined(__FreeBSD__)
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <unistd.h>
size_t
zmalloc_get_rss
(
void
)
{
struct
kinfo_proc
info
;
size_t
infolen
=
sizeof
(
info
);
int
mib
[
4
];
mib
[
0
]
=
CTL_KERN
;
mib
[
1
]
=
KERN_PROC
;
mib
[
2
]
=
KERN_PROC_PID
;
mib
[
3
]
=
getpid
();
if
(
sysctl
(
mib
,
4
,
&
info
,
&
infolen
,
NULL
,
0
)
==
0
)
return
(
size_t
)
info
.
ki_rssize
;
return
0L
;
}
#else
#else
size_t
zmalloc_get_rss
(
void
)
{
size_t
zmalloc_get_rss
(
void
)
{
/* If we can't get the RSS in an OS-specific way for this system just
/* If we can't get the RSS in an OS-specific way for this system just
...
@@ -306,6 +326,7 @@ size_t zmalloc_get_rss(void) {
...
@@ -306,6 +326,7 @@ size_t zmalloc_get_rss(void) {
#endif
#endif
#if defined(USE_JEMALLOC)
#if defined(USE_JEMALLOC)
int
zmalloc_get_allocator_info
(
size_t
*
allocated
,
int
zmalloc_get_allocator_info
(
size_t
*
allocated
,
size_t
*
active
,
size_t
*
active
,
size_t
*
resident
)
{
size_t
*
resident
)
{
...
@@ -327,13 +348,52 @@ int zmalloc_get_allocator_info(size_t *allocated,
...
@@ -327,13 +348,52 @@ int zmalloc_get_allocator_info(size_t *allocated,
je_mallctl
(
"stats.allocated"
,
allocated
,
&
sz
,
NULL
,
0
);
je_mallctl
(
"stats.allocated"
,
allocated
,
&
sz
,
NULL
,
0
);
return
1
;
return
1
;
}
}
void
set_jemalloc_bg_thread
(
int
enable
)
{
/* let jemalloc do purging asynchronously, required when there's no traffic
* after flushdb */
char
val
=
!!
enable
;
je_mallctl
(
"background_thread"
,
NULL
,
0
,
&
val
,
1
);
}
int
jemalloc_purge
()
{
/* return all unused (reserved) pages to the OS */
char
tmp
[
32
];
unsigned
narenas
=
0
;
size_t
sz
=
sizeof
(
unsigned
);
if
(
!
je_mallctl
(
"arenas.narenas"
,
&
narenas
,
&
sz
,
NULL
,
0
))
{
sprintf
(
tmp
,
"arena.%d.purge"
,
narenas
);
if
(
!
je_mallctl
(
tmp
,
NULL
,
0
,
NULL
,
0
))
return
0
;
}
return
-
1
;
}
#else
#else
int
zmalloc_get_allocator_info
(
size_t
*
allocated
,
int
zmalloc_get_allocator_info
(
size_t
*
allocated
,
size_t
*
active
,
size_t
*
active
,
size_t
*
resident
)
{
size_t
*
resident
)
{
*
allocated
=
*
resident
=
*
active
=
0
;
*
allocated
=
*
resident
=
*
active
=
0
;
return
1
;
return
1
;
}
}
void
set_jemalloc_bg_thread
(
int
enable
)
{
((
void
)(
enable
));
}
int
jemalloc_purge
()
{
return
0
;
}
#endif
#if defined(__APPLE__)
/* For proc_pidinfo() used later in zmalloc_get_smap_bytes_by_field().
* Note that this file cannot be included in zmalloc.h because it includes
* a Darwin queue.h file where there is a "LIST_HEAD" macro (!) defined
* conficting with Redis user code. */
#include <libproc.h>
#endif
#endif
/* Get the sum of the specified field (converted form kb to bytes) in
/* Get the sum of the specified field (converted form kb to bytes) in
...
@@ -375,7 +435,28 @@ size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
...
@@ -375,7 +435,28 @@ size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
return
bytes
;
return
bytes
;
}
}
#else
#else
/* Get sum of the specified field from libproc api call.
* As there are per page value basis we need to convert
* them accordingly.
*
* Note that AnonHugePages is a no-op as THP feature
* is not supported in this platform
*/
size_t
zmalloc_get_smap_bytes_by_field
(
char
*
field
,
long
pid
)
{
size_t
zmalloc_get_smap_bytes_by_field
(
char
*
field
,
long
pid
)
{
#if defined(__APPLE__)
struct
proc_regioninfo
pri
;
if
(
proc_pidinfo
(
pid
,
PROC_PIDREGIONINFO
,
0
,
&
pri
,
PROC_PIDREGIONINFO_SIZE
)
==
PROC_PIDREGIONINFO_SIZE
)
{
if
(
!
strcmp
(
field
,
"Private_Dirty:"
))
{
return
(
size_t
)
pri
.
pri_pages_dirtied
*
4096
;
}
else
if
(
!
strcmp
(
field
,
"Rss:"
))
{
return
(
size_t
)
pri
.
pri_pages_resident
*
4096
;
}
else
if
(
!
strcmp
(
field
,
"AnonHugePages:"
))
{
return
0
;
}
}
return
0
;
#endif
((
void
)
field
);
((
void
)
field
);
((
void
)
pid
);
((
void
)
pid
);
return
0
;
return
0
;
...
...
src/zmalloc.h
View file @
2968d8e3
...
@@ -86,6 +86,8 @@ size_t zmalloc_used_memory(void);
...
@@ -86,6 +86,8 @@ size_t zmalloc_used_memory(void);
void
zmalloc_set_oom_handler
(
void
(
*
oom_handler
)(
size_t
));
void
zmalloc_set_oom_handler
(
void
(
*
oom_handler
)(
size_t
));
size_t
zmalloc_get_rss
(
void
);
size_t
zmalloc_get_rss
(
void
);
int
zmalloc_get_allocator_info
(
size_t
*
allocated
,
size_t
*
active
,
size_t
*
resident
);
int
zmalloc_get_allocator_info
(
size_t
*
allocated
,
size_t
*
active
,
size_t
*
resident
);
void
set_jemalloc_bg_thread
(
int
enable
);
int
jemalloc_purge
();
size_t
zmalloc_get_private_dirty
(
long
pid
);
size_t
zmalloc_get_private_dirty
(
long
pid
);
size_t
zmalloc_get_smap_bytes_by_field
(
char
*
field
,
long
pid
);
size_t
zmalloc_get_smap_bytes_by_field
(
char
*
field
,
long
pid
);
size_t
zmalloc_get_memory_size
(
void
);
size_t
zmalloc_get_memory_size
(
void
);
...
...
tests/cluster/run.tcl
View file @
2968d8e3
...
@@ -8,6 +8,7 @@ source ../instances.tcl
...
@@ -8,6 +8,7 @@ source ../instances.tcl
source ../../support/cluster.tcl
;
# Redis Cluster client.
source ../../support/cluster.tcl
;
# Redis Cluster client.
set ::instances_count 20
;
# How many instances we use at max.
set ::instances_count 20
;
# How many instances we use at max.
set ::tlsdir
"../../tls"
proc main
{}
{
proc main
{}
{
parse_options
parse_options
...
...
tests/cluster/tests/04-resharding.tcl
View file @
2968d8e3
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
# are preseved across iterations.
# are preseved across iterations.
source
"../tests/includes/init-tests.tcl"
source
"../tests/includes/init-tests.tcl"
source
"../../../tests/support/cli.tcl"
test
"Create a 5 nodes cluster"
{
test
"Create a 5 nodes cluster"
{
create_cluster 5 5
create_cluster 5 5
...
@@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" {
...
@@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" {
--cluster-to $target
\
--cluster-to $target
\
--cluster-slots 100
\
--cluster-slots 100
\
--cluster-yes
\
--cluster-yes
\
{*}
[
rediscli_tls_config
"../../../tests"
]
\
|
[
info nameofexecutable
]
\
|
[
info nameofexecutable
]
\
../tests/helpers/onlydots.tcl
\
../tests/helpers/onlydots.tcl
\
&
]
0
]
&
]
0
]
...
...
tests/cluster/tests/12-replica-migration-2.tcl
View file @
2968d8e3
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
# other masters have slaves.
# other masters have slaves.
source
"../tests/includes/init-tests.tcl"
source
"../tests/includes/init-tests.tcl"
source
"../../../tests/support/cli.tcl"
# Create a cluster with 5 master and 15 slaves, to make sure there are no
# Create a cluster with 5 master and 15 slaves, to make sure there are no
# empty masters and make rebalancing simpler to handle during the test.
# empty masters and make rebalancing simpler to handle during the test.
...
@@ -33,7 +34,9 @@ test "Resharding all the master #0 slots away from it" {
...
@@ -33,7 +34,9 @@ test "Resharding all the master #0 slots away from it" {
set output
[
exec
\
set output
[
exec
\
../../../src/redis-cli --cluster rebalance
\
../../../src/redis-cli --cluster rebalance
\
127.0.0.1:
[
get_instance_attrib redis 0 port
]
\
127.0.0.1:
[
get_instance_attrib redis 0 port
]
\
{*}
[
rediscli_tls_config
"../../../tests"
]
\
--cluster-weight $
{
master0_id
}
=0 >@ stdout
]
--cluster-weight $
{
master0_id
}
=0 >@ stdout
]
}
}
test
"Master #0 should lose its replicas"
{
test
"Master #0 should lose its replicas"
{
...
@@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" {
...
@@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" {
set output
[
exec
\
set output
[
exec
\
../../../src/redis-cli --cluster rebalance
\
../../../src/redis-cli --cluster rebalance
\
127.0.0.1:
[
get_instance_attrib redis 0 port
]
\
127.0.0.1:
[
get_instance_attrib redis 0 port
]
\
{*}
[
rediscli_tls_config
"../../../tests"
]
\
--cluster-weight $
{
master0_id
}
=.01
\
--cluster-weight $
{
master0_id
}
=.01
\
--cluster-use-empty-masters >@ stdout
]
--cluster-use-empty-masters >@ stdout
]
}
}
...
...
tests/helpers/bg_block_op.tcl
View file @
2968d8e3
source tests/support/redis.tcl
source tests/support/redis.tcl
source tests/support/util.tcl
source tests/support/util.tcl
set ::tlsdir
"tests/tls"
# This function sometimes writes sometimes blocking-reads from lists/sorted
# This function sometimes writes sometimes blocking-reads from lists/sorted
# sets. There are multiple processes like this executing at the same time
# sets. There are multiple processes like this executing at the same time
# so that we have some chance to trap some corner condition if there is
# so that we have some chance to trap some corner condition if there is
...
@@ -8,8 +10,8 @@ source tests/support/util.tcl
...
@@ -8,8 +10,8 @@ source tests/support/util.tcl
# space to just a few elements, and balance the operations so that it is
# space to just a few elements, and balance the operations so that it is
# unlikely that lists and zsets just get more data without ever causing
# unlikely that lists and zsets just get more data without ever causing
# blocking.
# blocking.
proc bg_block_op
{
host port db ops
}
{
proc bg_block_op
{
host port db ops
tls
}
{
set r
[
redis $host $port
]
set r
[
redis $host $port
0 $tls
]
$r select $db
$r select $db
for
{
set j 0
}
{
$j
< $ops
}
{
incr j
}
{
for
{
set j 0
}
{
$j
< $ops
}
{
incr j
}
{
...
@@ -49,4 +51,4 @@ proc bg_block_op {host port db ops} {
...
@@ -49,4 +51,4 @@ proc bg_block_op {host port db ops} {
}
}
}
}
bg_block_op
[
lindex $argv 0
]
[
lindex $argv 1
]
[
lindex $argv 2
]
[
lindex $argv 3
]
bg_block_op
[
lindex $argv 0
]
[
lindex $argv 1
]
[
lindex $argv 2
]
[
lindex $argv 3
]
[
lindex $argv 4
]
tests/helpers/bg_complex_data.tcl
View file @
2968d8e3
source tests/support/redis.tcl
source tests/support/redis.tcl
source tests/support/util.tcl
source tests/support/util.tcl
proc bg_complex_data
{
host port db ops
}
{
set ::tlsdir
"tests/tls"
set r
[
redis $host $port
]
proc bg_complex_data
{
host port db ops tls
}
{
set r
[
redis $host $port 0 $tls
]
$r select $db
$r select $db
createComplexDataset $r $ops
createComplexDataset $r $ops
}
}
bg_complex_data
[
lindex $argv 0
]
[
lindex $argv 1
]
[
lindex $argv 2
]
[
lindex $argv 3
]
bg_complex_data
[
lindex $argv 0
]
[
lindex $argv 1
]
[
lindex $argv 2
]
[
lindex $argv 3
]
[
lindex $argv 4
]
tests/helpers/gen_write_load.tcl
View file @
2968d8e3
source tests/support/redis.tcl
source tests/support/redis.tcl
proc gen_write_load
{
host port seconds
}
{
set ::tlsdir
"tests/tls"
proc gen_write_load
{
host port seconds tls
}
{
set start_time
[
clock seconds
]
set start_time
[
clock seconds
]
set r
[
redis $host $port 1
]
set r
[
redis $host $port 1
$tls
]
$r select 9
$r select 9
while 1
{
while 1
{
$r set
[
expr rand
()]
[
expr rand
()]
$r set
[
expr rand
()]
[
expr rand
()]
...
@@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} {
...
@@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} {
}
}
}
}
gen_write_load
[
lindex $argv 0
]
[
lindex $argv 1
]
[
lindex $argv 2
]
gen_write_load
[
lindex $argv 0
]
[
lindex $argv 1
]
[
lindex $argv 2
]
[
lindex $argv 3
]
tests/instances.tcl
View file @
2968d8e3
...
@@ -17,6 +17,7 @@ source ../support/test.tcl
...
@@ -17,6 +17,7 @@ source ../support/test.tcl
set ::verbose 0
set ::verbose 0
set ::valgrind 0
set ::valgrind 0
set ::tls 0
set ::pause_on_error 0
set ::pause_on_error 0
set ::simulate_error 0
set ::simulate_error 0
set ::failed 0
set ::failed 0
...
@@ -69,7 +70,19 @@ proc spawn_instance {type base_port count {conf {}}} {
...
@@ -69,7 +70,19 @@ proc spawn_instance {type base_port count {conf {}}} {
# Write the instance config file.
# Write the instance config file.
set cfgfile
[
file join $dirname $type.conf
]
set cfgfile
[
file join $dirname $type.conf
]
set cfg
[
open $cfgfile w
]
set cfg
[
open $cfgfile w
]
if
{
$::tls
}
{
puts $cfg
"tls-port
$port
"
puts $cfg
"tls-replication yes"
puts $cfg
"tls-cluster yes"
puts $cfg
"port 0"
puts $cfg
[
format
"tls-cert-file %s/../../tls/redis.crt"
[
pwd
]]
puts $cfg
[
format
"tls-key-file %s/../../tls/redis.key"
[
pwd
]]
puts $cfg
[
format
"tls-dh-params-file %s/../../tls/redis.dh"
[
pwd
]]
puts $cfg
[
format
"tls-ca-cert-file %s/../../tls/ca.crt"
[
pwd
]]
puts $cfg
"loglevel debug"
}
else
{
puts $cfg
"port
$port
"
puts $cfg
"port
$port
"
}
puts $cfg
"dir ./
$dirname
"
puts $cfg
"dir ./
$dirname
"
puts $cfg
"logfile log.txt"
puts $cfg
"logfile log.txt"
# Add additional config files
# Add additional config files
...
@@ -88,7 +101,7 @@ proc spawn_instance {type base_port count {conf {}}} {
...
@@ -88,7 +101,7 @@ proc spawn_instance {type base_port count {conf {}}} {
}
}
# Push the instance into the right list
# Push the instance into the right list
set link
[
redis 127.0.0.1 $port
]
set link
[
redis 127.0.0.1 $port
0 $::tls
]
$link reconnect 1
$link reconnect 1
lappend ::$
{
type
}
_instances
[
list
\
lappend ::$
{
type
}
_instances
[
list
\
pid $pid
\
pid $pid
\
...
@@ -148,6 +161,13 @@ proc parse_options {} {
...
@@ -148,6 +161,13 @@ proc parse_options {} {
set ::simulate_error 1
set ::simulate_error 1
}
elseif
{
$opt
eq
{
--valgrind
}}
{
}
elseif
{
$opt
eq
{
--valgrind
}}
{
set ::valgrind 1
set ::valgrind 1
}
elseif
{
$opt
eq
{
--tls
}}
{
package require tls 1.6
::tls::init
\
-cafile
"
$::tlsdir
/ca.crt"
\
-certfile
"
$::tlsdir
/redis.crt"
\
-keyfile
"
$::tlsdir
/redis.key"
set ::tls 1
}
elseif
{
$opt
eq
"--help"
}
{
}
elseif
{
$opt
eq
"--help"
}
{
puts
"Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts
"Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts
"
\n
Options:"
puts
"
\n
Options:"
...
@@ -492,7 +512,7 @@ proc restart_instance {type id} {
...
@@ -492,7 +512,7 @@ proc restart_instance {type id} {
}
}
# Connect with it with a fresh link
# Connect with it with a fresh link
set link
[
redis 127.0.0.1 $port
]
set link
[
redis 127.0.0.1 $port
0 $::tls
]
$link reconnect 1
$link reconnect 1
set_instance_attrib $type $id link $link
set_instance_attrib $type $id link $link
...
...
tests/integration/aof-race.tcl
View file @
2968d8e3
...
@@ -13,8 +13,9 @@ tags {"aof"} {
...
@@ -13,8 +13,9 @@ tags {"aof"} {
# cleaned after a child responsible for an AOF rewrite exited. This buffer
# cleaned after a child responsible for an AOF rewrite exited. This buffer
# was subsequently appended to the new AOF, resulting in duplicate commands.
# was subsequently appended to the new AOF, resulting in duplicate commands.
start_server_aof
[
list dir $server_path
]
{
start_server_aof
[
list dir $server_path
]
{
set client
[
redis
[
srv host
]
[
srv port
]]
set client
[
redis
[
srv host
]
[
srv port
]
0 $::tls
]
set bench
[
open
"|src/redis-benchmark -q -p
[
srv port
]
-c 20 -n 20000 incr foo"
"r+"
]
set bench
[
open
"|src/redis-benchmark -q -s
[
srv unixsocket
]
-c 20 -n 20000 incr foo"
"r+"
]
after 100
after 100
# Benchmark should be running by now: start background rewrite
# Benchmark should be running by now: start background rewrite
...
@@ -29,7 +30,7 @@ tags {"aof"} {
...
@@ -29,7 +30,7 @@ tags {"aof"} {
# Restart server to replay AOF
# Restart server to replay AOF
start_server_aof
[
list dir $server_path
]
{
start_server_aof
[
list dir $server_path
]
{
set client
[
redis
[
srv host
]
[
srv port
]]
set client
[
redis
[
srv host
]
[
srv port
]
0 $::tls
]
assert_equal 20000
[
$client
get foo
]
assert_equal 20000
[
$client
get foo
]
}
}
}
}
Prev
1
2
3
4
5
6
7
8
9
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment