Unverified Commit ec2d1807 authored by Oran Agra's avatar Oran Agra Committed by GitHub
Browse files

Merge 6.2 RC2

parents b8c67ce4 049f2f08
...@@ -99,6 +99,57 @@ void disableTracking(client *c) { ...@@ -99,6 +99,57 @@ void disableTracking(client *c) {
} }
} }
static int stringCheckPrefix(unsigned char *s1, size_t s1_len, unsigned char *s2, size_t s2_len) {
size_t min_length = s1_len < s2_len ? s1_len : s2_len;
return memcmp(s1,s2,min_length) == 0;
}
/* Check if any of the provided prefixes collide with one another or
* with an existing prefix for the client. A collision is defined as two
* prefixes that will emit an invalidation for the same key. If no prefix
* collision is found, 1 is return, otherwise 0 is returned and the client
* has an error emitted describing the error. */
int checkPrefixCollisionsOrReply(client *c, robj **prefixes, size_t numprefix) {
for (size_t i = 0; i < numprefix; i++) {
/* Check input list has no overlap with existing prefixes. */
if (c->client_tracking_prefixes) {
raxIterator ri;
raxStart(&ri,c->client_tracking_prefixes);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
if (stringCheckPrefix(ri.key,ri.key_len,
prefixes[i]->ptr,sdslen(prefixes[i]->ptr)))
{
sds collision = sdsnewlen(ri.key,ri.key_len);
addReplyErrorFormat(c,
"Prefix '%s' overlaps with an existing prefix '%s'. "
"Prefixes for a single client must not overlap.",
(unsigned char *)prefixes[i]->ptr,
(unsigned char *)collision);
sdsfree(collision);
raxStop(&ri);
return 0;
}
}
raxStop(&ri);
}
/* Check input has no overlap with itself. */
for (size_t j = i + 1; j < numprefix; j++) {
if (stringCheckPrefix(prefixes[i]->ptr,sdslen(prefixes[i]->ptr),
prefixes[j]->ptr,sdslen(prefixes[j]->ptr)))
{
addReplyErrorFormat(c,
"Prefix '%s' overlaps with another provided prefix '%s'. "
"Prefixes for a single client must not overlap.",
(unsigned char *)prefixes[i]->ptr,
(unsigned char *)prefixes[j]->ptr);
return i;
}
}
}
return -1;
}
/* Set the client 'c' to track the prefix 'prefix'. If the client 'c' is /* Set the client 'c' to track the prefix 'prefix'. If the client 'c' is
* already registered for the specified prefix, no operation is performed. */ * already registered for the specified prefix, no operation is performed. */
void enableBcastTrackingForPrefix(client *c, char *prefix, size_t plen) { void enableBcastTrackingForPrefix(client *c, char *prefix, size_t plen) {
...@@ -350,19 +401,22 @@ void trackingInvalidateKey(client *c, robj *keyobj) { ...@@ -350,19 +401,22 @@ void trackingInvalidateKey(client *c, robj *keyobj) {
} }
/* This function is called when one or all the Redis databases are /* This function is called when one or all the Redis databases are
* flushed (dbid == -1 in case of FLUSHALL). Caching keys are not * flushed. Caching keys are not specific for each DB but are global:
* specific for each DB but are global: currently what we do is send a * currently what we do is send a special notification to clients with
* special notification to clients with tracking enabled, sending a * tracking enabled, sending a RESP NULL, which means, "all the keys",
* RESP NULL, which means, "all the keys", in order to avoid flooding * in order to avoid flooding clients with many invalidation messages
* clients with many invalidation messages for all the keys they may * for all the keys they may hold.
* hold.
*/ */
void freeTrackingRadixTree(void *rt) { void freeTrackingRadixTreeCallback(void *rt) {
raxFree(rt); raxFree(rt);
} }
void freeTrackingRadixTree(rax *rt) {
raxFreeWithCallback(rt,freeTrackingRadixTreeCallback);
}
/* A RESP NULL is sent to indicate that all keys are invalid */ /* A RESP NULL is sent to indicate that all keys are invalid */
void trackingInvalidateKeysOnFlush(int dbid) { void trackingInvalidateKeysOnFlush(int async) {
if (server.tracking_clients) { if (server.tracking_clients) {
listNode *ln; listNode *ln;
listIter li; listIter li;
...@@ -376,8 +430,12 @@ void trackingInvalidateKeysOnFlush(int dbid) { ...@@ -376,8 +430,12 @@ void trackingInvalidateKeysOnFlush(int dbid) {
} }
/* In case of FLUSHALL, reclaim all the memory used by tracking. */ /* In case of FLUSHALL, reclaim all the memory used by tracking. */
if (dbid == -1 && TrackingTable) { if (TrackingTable) {
raxFreeWithCallback(TrackingTable,freeTrackingRadixTree); if (async) {
freeTrackingRadixTreeAsync(TrackingTable);
} else {
freeTrackingRadixTree(TrackingTable);
}
TrackingTable = raxNew(); TrackingTable = raxNew();
TrackingTableTotalItems = 0; TrackingTableTotalItems = 0;
} }
......
#define REDIS_VERSION "6.1.240" #define REDIS_VERSION "6.1.241"
#define REDIS_VERSION_NUM 0x000601f0 #define REDIS_VERSION_NUM 0x000601f1
...@@ -431,19 +431,21 @@ unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, uns ...@@ -431,19 +431,21 @@ unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, uns
/* Encode the length of the previous entry and write it to "p". This only /* Encode the length of the previous entry and write it to "p". This only
* uses the larger encoding (required in __ziplistCascadeUpdate). */ * uses the larger encoding (required in __ziplistCascadeUpdate). */
int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) { int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) {
uint32_t u32;
if (p != NULL) { if (p != NULL) {
p[0] = ZIP_BIG_PREVLEN; p[0] = ZIP_BIG_PREVLEN;
memcpy(p+1,&len,sizeof(len)); u32 = len;
memcpy(p+1,&u32,sizeof(u32));
memrev32ifbe(p+1); memrev32ifbe(p+1);
} }
return 1+sizeof(len); return 1 + sizeof(uint32_t);
} }
/* Encode the length of the previous entry and write it to "p". Return the /* Encode the length of the previous entry and write it to "p". Return the
* number of bytes needed to encode this length if "p" is NULL. */ * number of bytes needed to encode this length if "p" is NULL. */
unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) { unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
if (p == NULL) { if (p == NULL) {
return (len < ZIP_BIG_PREVLEN) ? 1 : sizeof(len)+1; return (len < ZIP_BIG_PREVLEN) ? 1 : sizeof(uint32_t) + 1;
} else { } else {
if (len < ZIP_BIG_PREVLEN) { if (len < ZIP_BIG_PREVLEN) {
p[0] = len; p[0] = len;
...@@ -1711,7 +1713,7 @@ int ziplistTest(int argc, char **argv) { ...@@ -1711,7 +1713,7 @@ int ziplistTest(int argc, char **argv) {
if (p == NULL) { if (p == NULL) {
printf("No entry\n"); printf("No entry\n");
} else { } else {
printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", p-zl); printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", (long)(p-zl));
return 1; return 1;
} }
printf("\n"); printf("\n");
...@@ -1761,7 +1763,7 @@ int ziplistTest(int argc, char **argv) { ...@@ -1761,7 +1763,7 @@ int ziplistTest(int argc, char **argv) {
if (p == NULL) { if (p == NULL) {
printf("No entry\n"); printf("No entry\n");
} else { } else {
printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", p-zl); printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", (long)(p-zl));
return 1; return 1;
} }
printf("\n"); printf("\n");
......
...@@ -580,15 +580,18 @@ size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) { ...@@ -580,15 +580,18 @@ 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_smap_bytes_by_field(char *field, long pid) {
#if defined(__APPLE__) #if defined(__APPLE__)
struct proc_regioninfo pri; struct proc_regioninfo pri;
if (proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, PROC_PIDREGIONINFO_SIZE) == if (pid == -1) pid = getpid();
PROC_PIDREGIONINFO_SIZE) { if (proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri,
if (!strcmp(field, "Private_Dirty:")) { PROC_PIDREGIONINFO_SIZE) == PROC_PIDREGIONINFO_SIZE)
return (size_t)pri.pri_pages_dirtied * 4096; {
} else if (!strcmp(field, "Rss:")) { int pagesize = getpagesize();
return (size_t)pri.pri_pages_resident * 4096; if (!strcmp(field, "Private_Dirty:")) {
} else if (!strcmp(field, "AnonHugePages:")) { return (size_t)pri.pri_pages_dirtied * pagesize;
} else if (!strcmp(field, "Rss:")) {
return (size_t)pri.pri_pages_resident * pagesize;
} else if (!strcmp(field, "AnonHugePages:")) {
return 0; return 0;
} }
} }
return 0; return 0;
#endif #endif
......
...@@ -57,6 +57,11 @@ proc CI {n field} { ...@@ -57,6 +57,11 @@ proc CI {n field} {
get_info_field [R $n cluster info] $field get_info_field [R $n cluster info] $field
} }
# Return the value of the specified INFO field.
proc s {n field} {
get_info_field [R $n info] $field
}
# Assuming nodes are reest, this function performs slots allocation. # Assuming nodes are reest, this function performs slots allocation.
# Only the first 'n' nodes are used. # Only the first 'n' nodes are used.
proc cluster_allocate_slots {n} { proc cluster_allocate_slots {n} {
......
...@@ -15,6 +15,7 @@ set replica [Rn 1] ...@@ -15,6 +15,7 @@ set replica [Rn 1]
test "Cant read from replica without READONLY" { test "Cant read from replica without READONLY" {
$primary SET a 1 $primary SET a 1
wait_for_ofs_sync $primary $replica
catch {$replica GET a} err catch {$replica GET a} err
assert {[string range $err 0 4] eq {MOVED}} assert {[string range $err 0 4] eq {MOVED}}
} }
...@@ -28,6 +29,7 @@ test "Can preform HSET primary and HGET from replica" { ...@@ -28,6 +29,7 @@ test "Can preform HSET primary and HGET from replica" {
$primary HSET h a 1 $primary HSET h a 1
$primary HSET h b 2 $primary HSET h b 2
$primary HSET h c 3 $primary HSET h c 3
wait_for_ofs_sync $primary $replica
assert {[$replica HGET h a] eq {1}} assert {[$replica HGET h a] eq {1}}
assert {[$replica HGET h b] eq {2}} assert {[$replica HGET h b] eq {2}}
assert {[$replica HGET h c] eq {3}} assert {[$replica HGET h c] eq {3}}
......
...@@ -22,6 +22,8 @@ test "Right to restore backups when fail to diskless load " { ...@@ -22,6 +22,8 @@ test "Right to restore backups when fail to diskless load " {
$replica READONLY $replica READONLY
$replica config set repl-diskless-load swapdb $replica config set repl-diskless-load swapdb
$replica config set appendonly no
$replica config set save ""
$replica config rewrite $replica config rewrite
$master config set repl-backlog-size 1024 $master config set repl-backlog-size 1024
$master config set repl-diskless-sync yes $master config set repl-diskless-sync yes
...@@ -38,7 +40,8 @@ test "Right to restore backups when fail to diskless load " { ...@@ -38,7 +40,8 @@ test "Right to restore backups when fail to diskless load " {
assert_equal {1} [$replica get $slot0_key] assert_equal {1} [$replica get $slot0_key]
assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1] assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1]
# Kill the replica # Save an RDB and kill the replica
$replica save
kill_instance redis $replica_id kill_instance redis $replica_id
# Delete the key from master # Delete the key from master
...@@ -60,13 +63,12 @@ test "Right to restore backups when fail to diskless load " { ...@@ -60,13 +63,12 @@ test "Right to restore backups when fail to diskless load " {
restart_instance redis $replica_id restart_instance redis $replica_id
$replica READONLY $replica READONLY
# Start full sync # Start full sync, wait till after db is flushed (backed up)
wait_for_condition 500 10 { wait_for_condition 500 10 {
[string match "*sync*" [$replica role]] [s $replica_id loading] eq 1
} else { } else {
fail "Fail to full sync" fail "Fail to full sync"
} }
after 100
# Kill master, abort full sync # Kill master, abort full sync
kill_instance redis $master_id kill_instance redis $master_id
...@@ -74,4 +76,4 @@ test "Right to restore backups when fail to diskless load " { ...@@ -74,4 +76,4 @@ test "Right to restore backups when fail to diskless load " {
# Replica keys and keys to slots map still both are right # Replica keys and keys to slots map still both are right
assert_equal {1} [$replica get $slot0_key] assert_equal {1} [$replica get $slot0_key]
assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1] assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1]
} }
\ No newline at end of file
# Check cluster info stats
source "../tests/includes/init-tests.tcl"
test "Create a primary with a replica" {
create_cluster 2 0
}
test "Cluster should start ok" {
assert_cluster_state ok
}
set primary1 [Rn 0]
set primary2 [Rn 1]
proc cmdstat {instace cmd} {
return [cmdrstat $cmd $instace]
}
proc errorstat {instace cmd} {
return [errorrstat $cmd $instace]
}
test "errorstats: rejected call due to MOVED Redirection" {
$primary1 config resetstat
$primary2 config resetstat
assert_match {} [errorstat $primary1 MOVED]
assert_match {} [errorstat $primary2 MOVED]
# we know that one will have a MOVED reply and one will succeed
catch {$primary1 set key b} replyP1
catch {$primary2 set key b} replyP2
# sort servers so we know which one failed
if {$replyP1 eq {OK}} {
assert_match {MOVED*} $replyP2
set pok $primary1
set perr $primary2
} else {
assert_match {MOVED*} $replyP1
set pok $primary2
set perr $primary1
}
assert_match {} [errorstat $pok MOVED]
assert_match {*count=1*} [errorstat $perr MOVED]
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat $perr set]
}
...@@ -198,3 +198,94 @@ test {client freed during loading} { ...@@ -198,3 +198,94 @@ test {client freed during loading} {
exec kill [srv 0 pid] exec kill [srv 0 pid]
} }
} }
# Our COW metrics (Private_Dirty) work only on Linux
set system_name [string tolower [exec uname -s]]
if {$system_name eq {linux}} {
start_server {overrides {save ""}} {
test {Test child sending COW info} {
# make sure that rdb_last_cow_size and current_cow_size are zero (the test using new server),
# so that the comparisons during the test will be valid
assert {[s current_cow_size] == 0}
assert {[s rdb_last_cow_size] == 0}
# using a 200us delay, the bgsave is empirically taking about 10 seconds.
# we need it to take more than some 5 seconds, since redis only report COW once a second.
r config set rdb-key-save-delay 200
r config set loglevel debug
# populate the db with 10k keys of 4k each
set rd [redis_deferring_client 0]
set size 4096
set cmd_count 10000
for {set k 0} {$k < $cmd_count} {incr k} {
$rd set key$k [string repeat A $size]
}
for {set k 0} {$k < $cmd_count} {incr k} {
catch { $rd read }
}
$rd close
# start background rdb save
r bgsave
# on each iteration, we will write some key to the server to trigger copy-on-write, and
# wait to see that it reflected in INFO.
set iteration 1
while 1 {
# take a sample before writing new data to the server
set cow_size [s current_cow_size]
if {$::verbose} {
puts "COW info before copy-on-write: $cow_size"
}
# trigger copy-on-write
r setrange key$iteration 0 [string repeat B $size]
# wait to see that current_cow_size value updated (as long as the child is in progress)
wait_for_condition 80 100 {
[s rdb_bgsave_in_progress] == 0 ||
[s current_cow_size] >= $cow_size + $size
} else {
if {$::verbose} {
puts "COW info on fail: [s current_cow_size]"
puts [exec tail -n 100 < [srv 0 stdout]]
}
fail "COW info wasn't reported"
}
# for no accurate, stop after 2 iterations
if {!$::accurate && $iteration == 2} {
break
}
# stop iterating if the bgsave completed
if { [s rdb_bgsave_in_progress] == 0 } {
break
}
incr iteration 1
}
# make sure we saw report of current_cow_size
if {$iteration < 2 && $::verbose} {
puts [exec tail -n 100 < [srv 0 stdout]]
}
assert_morethan_equal $iteration 2
# if bgsave completed, check that rdb_last_cow_size (fork exit report)
# is at least 90% of last rdb_active_cow_size.
if { [s rdb_bgsave_in_progress] == 0 } {
set final_cow [s rdb_last_cow_size]
set cow_size [expr $cow_size * 0.9]
if {$final_cow < $cow_size && $::verbose} {
puts [exec tail -n 100 < [srv 0 stdout]]
}
assert_morethan_equal $final_cow $cow_size
}
}
}
} ;# system_name
...@@ -51,18 +51,31 @@ void timerHandler(RedisModuleCtx *ctx, void *data) { ...@@ -51,18 +51,31 @@ void timerHandler(RedisModuleCtx *ctx, void *data) {
RedisModule_Replicate(ctx,"INCR","c","timer"); RedisModule_Replicate(ctx,"INCR","c","timer");
times++; times++;
if (times < 10) if (times < 3)
RedisModule_CreateTimer(ctx,100,timerHandler,NULL); RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
else else
times = 0; times = 0;
} }
int propagateTestTimerCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleTimerID timer_id =
RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
REDISMODULE_NOT_USED(timer_id);
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
/* The thread entry point. */ /* The thread entry point. */
void *threadMain(void *arg) { void *threadMain(void *arg) {
REDISMODULE_NOT_USED(arg); REDISMODULE_NOT_USED(arg);
RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(NULL); RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(NULL);
RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */ RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */
for (int i = 0; i < 10; i++) { for (int i = 0; i < 3; i++) {
RedisModule_ThreadSafeContextLock(ctx); RedisModule_ThreadSafeContextLock(ctx);
RedisModule_Replicate(ctx,"INCR","c","a-from-thread"); RedisModule_Replicate(ctx,"INCR","c","a-from-thread");
RedisModule_Replicate(ctx,"INCR","c","b-from-thread"); RedisModule_Replicate(ctx,"INCR","c","b-from-thread");
...@@ -72,15 +85,11 @@ void *threadMain(void *arg) { ...@@ -72,15 +85,11 @@ void *threadMain(void *arg) {
return NULL; return NULL;
} }
int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) int propagateTestThreadCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{ {
REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argc);
RedisModuleTimerID timer_id =
RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
REDISMODULE_NOT_USED(timer_id);
pthread_t tid; pthread_t tid;
if (pthread_create(&tid,NULL,threadMain,NULL) != 0) if (pthread_create(&tid,NULL,threadMain,NULL) != 0)
return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread"); return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
...@@ -90,7 +99,7 @@ int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc ...@@ -90,7 +99,7 @@ int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int propagateTest2Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) int propagateTestSimpleCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{ {
REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argc);
...@@ -102,7 +111,7 @@ int propagateTest2Command(RedisModuleCtx *ctx, RedisModuleString **argv, int arg ...@@ -102,7 +111,7 @@ int propagateTest2Command(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int propagateTest3Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) int propagateTestMixedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{ {
REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argc);
...@@ -129,18 +138,23 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -129,18 +138,23 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_Init(ctx,"propagate-test",1,REDISMODULE_APIVER_1) if (RedisModule_Init(ctx,"propagate-test",1,REDISMODULE_APIVER_1)
== REDISMODULE_ERR) return REDISMODULE_ERR; == REDISMODULE_ERR) return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test", if (RedisModule_CreateCommand(ctx,"propagate-test.timer",
propagateTestCommand, propagateTestTimerCommand,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.thread",
propagateTestThreadCommand,
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test-2", if (RedisModule_CreateCommand(ctx,"propagate-test.simple",
propagateTest2Command, propagateTestSimpleCommand,
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test-3", if (RedisModule_CreateCommand(ctx,"propagate-test.mixed",
propagateTest3Command, propagateTestMixedCommand,
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
......
...@@ -214,20 +214,19 @@ proc ::redis::redis_multi_bulk_read {id fd} { ...@@ -214,20 +214,19 @@ proc ::redis::redis_multi_bulk_read {id fd} {
proc ::redis::redis_read_map {id fd} { proc ::redis::redis_read_map {id fd} {
set count [redis_read_line $fd] set count [redis_read_line $fd]
if {$count == -1} return {} if {$count == -1} return {}
set l {} set d {}
set err {} set err {}
for {set i 0} {$i < $count} {incr i} { for {set i 0} {$i < $count} {incr i} {
if {[catch { if {[catch {
set t {} set k [redis_read_reply $id $fd] ; # key
lappend t [redis_read_reply $id $fd] ; # key set v [redis_read_reply $id $fd] ; # value
lappend t [redis_read_reply $id $fd] ; # value dict set d $k $v
lappend l $t
} e] && $err eq {}} { } e] && $err eq {}} {
set err $e set err $e
} }
} }
if {$err ne {}} {return -code error $err} if {$err ne {}} {return -code error $err}
return $l return $d
} }
proc ::redis::redis_read_line fd { proc ::redis::redis_read_line fd {
......
...@@ -50,11 +50,17 @@ proc kill_server config { ...@@ -50,11 +50,17 @@ proc kill_server config {
tags {"leaks"} { tags {"leaks"} {
test "Check for memory leaks (pid $pid)" { test "Check for memory leaks (pid $pid)" {
set output {0 leaks} set output {0 leaks}
catch {exec leaks $pid} output catch {exec leaks $pid} output option
if {[string match {*process does not exist*} $output] || # In a few tests we kill the server process, so leaks will not find it.
[string match {*cannot examine*} $output]} { # It'll exits with exit code >1 on error, so we ignore these.
# In a few tests we kill the server process. if {[dict exists $option -errorcode]} {
set output "0 leaks" set details [dict get $option -errorcode]
if {[lindex $details 0] eq "CHILDSTATUS"} {
set status [lindex $details 2]
if {$status > 1} {
set output "0 leaks"
}
}
} }
set output set output
} {*0 leaks*} } {*0 leaks*}
......
...@@ -31,36 +31,48 @@ proc assert_match {pattern value} { ...@@ -31,36 +31,48 @@ proc assert_match {pattern value} {
} }
} }
proc assert_failed {expected_err detail} {
if {$detail ne ""} {
set detail "(detail: $detail)"
} else {
set detail "(context: [info frame -2])"
}
error "assertion:$expected_err $detail"
}
proc assert_equal {value expected {detail ""}} { proc assert_equal {value expected {detail ""}} {
if {$expected ne $value} { if {$expected ne $value} {
if {$detail ne ""} { assert_failed "Expected '$value' to be equal to '$expected'" $detail
set detail "(detail: $detail)"
} else {
set detail "(context: [info frame -1])"
}
error "assertion:Expected '$value' to be equal to '$expected' $detail"
} }
} }
proc assert_lessthan {value expected {detail ""}} { proc assert_lessthan {value expected {detail ""}} {
if {!($value < $expected)} { if {!($value < $expected)} {
if {$detail ne ""} { assert_failed "Expected '$value' to be less than '$expected'" $detail
set detail "(detail: $detail)" }
} else { }
set detail "(context: [info frame -1])"
} proc assert_lessthan_equal {value expected {detail ""}} {
error "assertion:Expected '$value' to be lessthan to '$expected' $detail" if {!($value <= $expected)} {
assert_failed "Expected '$value' to be less than or equal to '$expected'" $detail
}
}
proc assert_morethan {value expected {detail ""}} {
if {!($value > $expected)} {
assert_failed "Expected '$value' to be more than '$expected'" $detail
}
}
proc assert_morethan_equal {value expected {detail ""}} {
if {!($value >= $expected)} {
assert_failed "Expected '$value' to be more than or equal to '$expected'" $detail
} }
} }
proc assert_range {value min max {detail ""}} { proc assert_range {value min max {detail ""}} {
if {!($value <= $max && $value >= $min)} { if {!($value <= $max && $value >= $min)} {
if {$detail ne ""} { assert_failed "Expected '$value' to be between to '$min' and '$max'" $detail
set detail "(detail: $detail)"
} else {
set detail "(context: [info frame -1])"
}
error "assertion:Expected '$value' to be between to '$min' and '$max' $detail"
} }
} }
......
...@@ -561,6 +561,12 @@ proc cmdrstat {cmd r} { ...@@ -561,6 +561,12 @@ proc cmdrstat {cmd r} {
} }
} }
proc errorrstat {cmd r} {
if {[regexp "\r\nerrorstat_$cmd:(.*?)\r\n" [$r info errorstats] _ value]} {
set _ $value
}
}
proc generate_fuzzy_traffic_on_key {key duration} { proc generate_fuzzy_traffic_on_key {key duration} {
# Commands per type, blocking commands removed # Commands per type, blocking commands removed
# TODO: extract these from help.h or elsewhere, and improve to include other types # TODO: extract these from help.h or elsewhere, and improve to include other types
......
...@@ -18,6 +18,7 @@ set ::all_tests { ...@@ -18,6 +18,7 @@ set ::all_tests {
unit/protocol unit/protocol
unit/keyspace unit/keyspace
unit/scan unit/scan
unit/info
unit/type/string unit/type/string
unit/type/incr unit/type/incr
unit/type/list unit/type/list
......
...@@ -25,3 +25,44 @@ start_server {tags {"auth"} overrides {requirepass foobar}} { ...@@ -25,3 +25,44 @@ start_server {tags {"auth"} overrides {requirepass foobar}} {
r incr foo r incr foo
} {101} } {101}
} }
start_server {tags {"auth_binary_password"}} {
test {AUTH fails when binary password is wrong} {
r config set requirepass "abc\x00def"
catch {r auth abc} err
set _ $err
} {WRONGPASS*}
test {AUTH succeeds when binary password is correct} {
r config set requirepass "abc\x00def"
r auth "abc\x00def"
} {OK}
start_server {tags {"masterauth"}} {
set master [srv -1 client]
set master_host [srv -1 host]
set master_port [srv -1 port]
set slave [srv 0 client]
test {MASTERAUTH test with binary password} {
$master config set requirepass "abc\x00def"
# Configure the replica with masterauth
set loglines [count_log_lines 0]
$slave slaveof $master_host $master_port
$slave config set masterauth "abc"
# Verify replica is not able to sync with master
wait_for_log_messages 0 {"*Unable to AUTH to MASTER*"} $loglines 1000 10
assert_equal {down} [s 0 master_link_status]
# Test replica with the correct masterauth
$slave config set masterauth "abc\x00def"
wait_for_condition 50 100 {
[s 0 master_link_status] eq {up}
} else {
fail "Can't turn the instance into a replica"
}
}
}
}
...@@ -74,6 +74,49 @@ start_server {tags {"geo"}} { ...@@ -74,6 +74,49 @@ start_server {tags {"geo"}} {
r geoadd nyc -73.9454966 40.747533 "lic market" r geoadd nyc -73.9454966 40.747533 "lic market"
} {0} } {0}
test {GEOADD update with CH option} {
assert_equal 1 [r geoadd nyc CH 40.747533 -73.9454966 "lic market"]
lassign [lindex [r geopos nyc "lic market"] 0] x1 y1
assert {abs($x1) - 40.747 < 0.001}
assert {abs($y1) - 73.945 < 0.001}
} {}
test {GEOADD update with NX option} {
assert_equal 0 [r geoadd nyc NX -73.9454966 40.747533 "lic market"]
lassign [lindex [r geopos nyc "lic market"] 0] x1 y1
assert {abs($x1) - 40.747 < 0.001}
assert {abs($y1) - 73.945 < 0.001}
} {}
test {GEOADD update with XX option} {
assert_equal 0 [r geoadd nyc XX -83.9454966 40.747533 "lic market"]
lassign [lindex [r geopos nyc "lic market"] 0] x1 y1
assert {abs($x1) - 83.945 < 0.001}
assert {abs($y1) - 40.747 < 0.001}
} {}
test {GEOADD update with CH NX option} {
r geoadd nyc CH NX -73.9454966 40.747533 "lic market"
} {0}
test {GEOADD update with CH XX option} {
r geoadd nyc CH XX -73.9454966 40.747533 "lic market"
} {1}
test {GEOADD update with XX NX option will return syntax error} {
catch {
r geoadd nyc xx nx -73.9454966 40.747533 "lic market"
} err
set err
} {ERR*syntax*}
test {GEOADD update with invalid option} {
catch {
r geoadd nyc ch xx foo -73.9454966 40.747533 "lic market"
} err
set err
} {ERR*syntax*}
test {GEOADD invalid coordinates} { test {GEOADD invalid coordinates} {
catch { catch {
r geoadd nyc -73.9454966 40.747533 "lic market" \ r geoadd nyc -73.9454966 40.747533 "lic market" \
...@@ -135,6 +178,19 @@ start_server {tags {"geo"}} { ...@@ -135,6 +178,19 @@ start_server {tags {"geo"}} {
r georadius nyc -73.9798091 40.7598464 10 km COUNT 3 r georadius nyc -73.9798091 40.7598464 10 km COUNT 3
} {{central park n/q/r} 4545 {union square}} } {{central park n/q/r} 4545 {union square}}
test {GEORADIUS with ANY not sorted by default} {
r georadius nyc -73.9798091 40.7598464 10 km COUNT 3 ANY
} {{wtc one} {union square} {central park n/q/r}}
test {GEORADIUS with ANY sorted by ASC} {
r georadius nyc -73.9798091 40.7598464 10 km COUNT 3 ANY ASC
} {{central park n/q/r} {union square} {wtc one}}
test {GEORADIUS with ANY but no COUNT} {
catch {r georadius nyc -73.9798091 40.7598464 10 km ANY ASC} e
set e
} {ERR*ANY*requires*COUNT*}
test {GEORADIUS with COUNT but missing integer argument} { test {GEORADIUS with COUNT but missing integer argument} {
catch {r georadius nyc -73.9798091 40.7598464 10 km COUNT} e catch {r georadius nyc -73.9798091 40.7598464 10 km COUNT} e
set e set e
......
proc cmdstat {cmd} {
return [cmdrstat $cmd r]
}
proc errorstat {cmd} {
return [errorrstat $cmd r]
}
start_server {tags {"info"}} {
start_server {} {
test {errorstats: failed call authentication error} {
r config resetstat
assert_match {} [errorstat ERR]
assert_equal [s total_error_replies] 0
catch {r auth k} e
assert_match {ERR AUTH*} $e
assert_match {*count=1*} [errorstat ERR]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
assert_equal [s total_error_replies] 1
r config resetstat
assert_match {} [errorstat ERR]
}
test {errorstats: failed call within MULTI/EXEC} {
r config resetstat
assert_match {} [errorstat ERR]
assert_equal [s total_error_replies] 0
r multi
r set a b
r auth a
catch {r exec} e
assert_match {ERR AUTH*} $e
assert_match {*count=1*} [errorstat ERR]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat set]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat exec]
assert_equal [s total_error_replies] 1
# MULTI/EXEC command errors should still be pinpointed to him
catch {r exec} e
assert_match {ERR EXEC without MULTI} $e
assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat exec]
assert_match {*count=2*} [errorstat ERR]
assert_equal [s total_error_replies] 2
}
test {errorstats: failed call within LUA} {
r config resetstat
assert_match {} [errorstat ERR]
assert_equal [s total_error_replies] 0
catch {r eval {redis.pcall('XGROUP', 'CREATECONSUMER', 's1', 'mygroup', 'consumer') return } 0} e
assert_match {*count=1*} [errorstat ERR]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat eval]
# EVAL command errors should still be pinpointed to him
catch {r eval a} e
assert_match {ERR wrong*} $e
assert_match {*calls=1,*,rejected_calls=1,failed_calls=0} [cmdstat eval]
assert_match {*count=2*} [errorstat ERR]
assert_equal [s total_error_replies] 2
}
test {errorstats: failed call NOGROUP error} {
r config resetstat
assert_match {} [errorstat NOGROUP]
r del mystream
r XADD mystream * f v
catch {r XGROUP CREATECONSUMER mystream mygroup consumer} e
assert_match {NOGROUP*} $e
assert_match {*count=1*} [errorstat NOGROUP]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup]
r config resetstat
assert_match {} [errorstat NOGROUP]
}
test {errorstats: rejected call unknown command} {
r config resetstat
assert_equal [s total_error_replies] 0
assert_match {} [errorstat ERR]
catch {r asdf} e
assert_match {ERR unknown*} $e
assert_match {*count=1*} [errorstat ERR]
assert_equal [s total_error_replies] 1
r config resetstat
assert_match {} [errorstat ERR]
}
test {errorstats: rejected call within MULTI/EXEC} {
r config resetstat
assert_equal [s total_error_replies] 0
assert_match {} [errorstat ERR]
r multi
catch {r set} e
assert_match {ERR wrong number of arguments*} $e
catch {r exec} e
assert_match {EXECABORT*} $e
assert_match {*count=1*} [errorstat ERR]
assert_equal [s total_error_replies] 1
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat multi]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat exec]
assert_equal [s total_error_replies] 1
r config resetstat
assert_match {} [errorstat ERR]
}
test {errorstats: rejected call due to wrong arity} {
r config resetstat
assert_equal [s total_error_replies] 0
assert_match {} [errorstat ERR]
catch {r set k} e
assert_match {ERR wrong number of arguments*} $e
assert_match {*count=1*} [errorstat ERR]
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set]
# ensure that after a rejected command, valid ones are counted properly
r set k1 v1
r set k2 v2
assert_match {calls=2,*,rejected_calls=1,failed_calls=0} [cmdstat set]
assert_equal [s total_error_replies] 1
}
test {errorstats: rejected call by OOM error} {
r config resetstat
assert_equal [s total_error_replies] 0
assert_match {} [errorstat OOM]
r config set maxmemory 1
catch {r set a b} e
assert_match {OOM*} $e
assert_match {*count=1*} [errorstat OOM]
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set]
assert_equal [s total_error_replies] 1
r config resetstat
assert_match {} [errorstat OOM]
}
test {errorstats: rejected call by authorization error} {
r config resetstat
assert_equal [s total_error_replies] 0
assert_match {} [errorstat NOPERM]
r ACL SETUSER alice on >p1pp0 ~cached:* +get +info +config
r auth alice p1pp0
catch {r set a b} e
assert_match {NOPERM*} $e
assert_match {*count=1*} [errorstat NOPERM]
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set]
assert_equal [s total_error_replies] 1
r config resetstat
assert_match {} [errorstat NOPERM]
}
}
}
...@@ -38,7 +38,7 @@ start_server {tags {"memefficiency"}} { ...@@ -38,7 +38,7 @@ start_server {tags {"memefficiency"}} {
run_solo {defrag} { run_solo {defrag} {
start_server {tags {"defrag"} overrides {appendonly yes auto-aof-rewrite-percentage 0 save ""}} { start_server {tags {"defrag"} overrides {appendonly yes auto-aof-rewrite-percentage 0 save ""}} {
if {[string match {*jemalloc*} [s mem_allocator]]} { if {[string match {*jemalloc*} [s mem_allocator]] && [r debug mallctl arenas.page] <= 8192} {
test "Active defrag" { test "Active defrag" {
r config set hz 100 r config set hz 100
r config set activedefrag no r config set activedefrag no
......
...@@ -14,25 +14,103 @@ tags "modules" { ...@@ -14,25 +14,103 @@ tags "modules" {
# Start the replication process... # Start the replication process...
$replica replicaof $master_host $master_port $replica replicaof $master_host $master_port
wait_for_sync $replica wait_for_sync $replica
after 1000 after 1000
$master propagate-test
wait_for_condition 5000 10 { test {module propagates from timer} {
([$replica get timer] eq "10") && \ set repl [attach_to_replication_stream]
([$replica get a-from-thread] eq "10")
} else { $master propagate-test.timer
fail "The two counters don't match the expected value."
wait_for_condition 5000 10 {
[$replica get timer] eq "3"
} else {
fail "The two counters don't match the expected value."
}
assert_replication_stream $repl {
{select *}
{multi}
{incr timer}
{exec}
{multi}
{incr timer}
{exec}
{multi}
{incr timer}
{exec}
}
close_replication_stream $repl
} }
$master propagate-test-2 test {module propagates from thread} {
$master propagate-test-3 set repl [attach_to_replication_stream]
$master multi
$master propagate-test-2 $master propagate-test.thread
$master propagate-test-3
$master exec wait_for_condition 5000 10 {
wait_for_ofs_sync $master $replica [$replica get a-from-thread] eq "3"
} else {
fail "The two counters don't match the expected value."
}
assert_replication_stream $repl {
{select *}
{incr a-from-thread}
{incr b-from-thread}
{incr a-from-thread}
{incr b-from-thread}
{incr a-from-thread}
{incr b-from-thread}
}
close_replication_stream $repl
}
test {module propagates from from command} {
set repl [attach_to_replication_stream]
$master propagate-test.simple
$master propagate-test.mixed
# Note the 'after-call' propagation below is out of order (known limitation)
assert_replication_stream $repl {
{select *}
{multi}
{incr counter-1}
{incr counter-2}
{exec}
{multi}
{incr using-call}
{incr after-call}
{incr counter-1}
{incr counter-2}
{exec}
}
close_replication_stream $repl
}
test {module propagates from from multi-exec} {
set repl [attach_to_replication_stream]
$master multi
$master propagate-test.simple
$master propagate-test.mixed
$master exec
wait_for_ofs_sync $master $replica
# Note the 'after-call' propagation below is out of order (known limitation)
assert_replication_stream $repl {
{select *}
{multi}
{incr counter-1}
{incr counter-2}
{incr using-call}
{incr after-call}
{incr counter-1}
{incr counter-2}
{exec}
}
close_replication_stream $repl
}
assert_equal [s -1 unexpected_error_replies] 0 assert_equal [s -1 unexpected_error_replies] 0
} }
} }
...@@ -47,11 +125,11 @@ tags "modules aof" { ...@@ -47,11 +125,11 @@ tags "modules aof" {
r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite. r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
waitForBgrewriteaof r waitForBgrewriteaof r
r propagate-test-2 r propagate-test.simple
r propagate-test-3 r propagate-test.mixed
r multi r multi
r propagate-test-2 r propagate-test.simple
r propagate-test-3 r propagate-test.mixed
r exec r exec
# Load the AOF # Load the AOF
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment