Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/scan_algorithm.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key) {
start_index = 0;
ln = tair_hash_obj->expire_index->header->level[0].forward;
while (ln && keys_per_loop) {
m_zskiplistNode *next = ln->level[0].forward;
field = ln->member;
if (fieldExpireIfNeeded(ctx, dbid, key, tair_hash_obj, field, 0)) {
g_expire_algorithm.stat_passive_expired_field[dbid]++;
Expand All @@ -250,7 +251,7 @@ void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key) {
} else {
break;
}
ln = ln->level[0].forward;
ln = next;
}

if (may_delkey) {
Expand All @@ -265,7 +266,6 @@ void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key) {
}

if (start_index) {
m_zslDeleteRangeByRank(tair_hash_obj->expire_index, 1, start_index);
delEmptyTairHashIfNeeded(ctx, NULL, key, tair_hash_obj);
}
m_listDelNode(keys, node);
Expand Down
17 changes: 15 additions & 2 deletions src/slabapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,14 @@ void slab_expireDelete(tairhash_zskiplist *zsl, RedisModuleString *key, long lon

if (update_findNode) { // update min value
int smallest_subscript = slab_minExpireTimeIndex(find_slab);
find_node->expire_min = find_slab->expires[smallest_subscript], find_node->key_min = find_slab->keys[smallest_subscript];
long long new_expire_min = find_slab->expires[smallest_subscript];
RedisModuleString *new_key_min = find_slab->keys[smallest_subscript];
if (new_expire_min != find_node->expire_min || RedisModule_StringCompare(new_key_min, find_node->key_min) != 0) {
Slab *slab = find_node->slab;
int ret = tairhash_zslDelete(zsl, find_node->key_min, find_node->expire_min);
assert(ret == 1);
find_node = tairhash_zslInsertNode(zsl, slab, new_key_min, new_expire_min);
}
}
slab_mergeIfNeed(zsl, find_node); // if need merge
return;
Expand Down Expand Up @@ -268,7 +275,13 @@ void slab_deleteSlabExpire(tairhash_zskiplist *zsl, tairhash_zskiplistNode *zsl_
}
}
slab->num_keys = effective_num;
zsl_node->expire_min = slab->expires[min_index], zsl_node->key_min = slab->keys[min_index];
long long new_expire_min = slab->expires[min_index];
RedisModuleString *new_key_min = slab->keys[min_index];
if (new_expire_min != zsl_node->expire_min || RedisModule_StringCompare(new_key_min, zsl_node->key_min) != 0) {
int ret = tairhash_zslDelete(zsl, zsl_node->key_min, zsl_node->expire_min);
assert(ret == 1);
zsl_node = tairhash_zslInsertNode(zsl, slab, new_key_min, new_expire_min);
}
slab_mergeIfNeed(zsl, zsl_node);
return;
}
Expand Down
149 changes: 148 additions & 1 deletion tests/tairhash.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -2083,4 +2083,151 @@ start_server {tags {"tairhash"} overrides {bind 0.0.0.0}} {
# }
}
}
}
}

start_server {tags {"slab_bug_reproduce"}} {
test "Test Slab consistency after extensive delete" {
r module load $testmodule

set key "test_slab_key"
# 1. Fill multiple slabs (Slab size is 512)
# Insert 1500 items. Should create ~3-4 slabs.
for {set i 0} {$i < 1500} {incr i} {
r exhset $key field_$i val ex $i
}

# 2. Delete items from the beginning (Smallest expires/keys)
# This forces the First Slab's Min Value to increase repeatedly.
# This is where the potential order invariant violation happens.
for {set i 0} {$i < 1000} {incr i} {
r exhdel $key field_$i
}

# 3. Verify the remaining items are accessible.
# If the First Slab's Min increased but 'leapfrogged' the Second Slab,
# the Second Slab might become unreachable or data could be corrupted.
for {set i 1000} {$i < 1500} {incr i} {
assert_equal [r exhget $key field_$i] "val"
}

assert_equal [r exhlen $key] 500
}

test "Test Slab Split and Merge random operations" {
r del $key
# Random operations to trigger split/merge and potential race/logic errors
for {set i 0} {$i < 2000} {incr i} {
set action [expr {int(rand()*10)}]
set f_id [expr {int(rand()*1000)}]
set expire [expr {int(rand()*100000)}]

if {$action < 7} {
# 70% Set
r exhset $key f_$f_id val ex $expire
} else {
# 30% Del
r exhdel $key f_$f_id
}
}
# Just ensure it didn't crash
r ping
}

test "Test Slab churn with updates and deletes" {
set key "test_slab_churn"
r del $key

# insert enough items to span multiple slabs
for {set i 0} {$i < 1200} {incr i} {
r exhset $key field_$i val ex 10
}

# update half of them to a later expire, delete some
for {set i 0} {$i < 600} {incr i} {
r exhset $key field_$i val ex 20
}
for {set i 600} {$i < 900} {incr i} {
r exhdel $key field_$i
}

assert_equal 900 [r exhlen $key]

# verify remaining fields exist
for {set i 0} {$i < 600} {incr i} {
assert_equal "val" [r exhget $key field_$i]
}
for {set i 900} {$i < 1200} {incr i} {
assert_equal "val" [r exhget $key field_$i]
}
}

test "Test Slab delete non-existing fields" {
set key "test_slab_del_missing"
r del $key
for {set i 0} {$i < 300} {incr i} {
r exhset $key field_$i val ex 5
}

# delete existing and non-existing fields mixed
for {set i 0} {$i < 600} {incr i} {
r exhdel $key field_$i
}

assert_equal 0 [r exhlen $key]
assert_equal 0 [r exists $key]
}

test "Test Slab expire boundary and fast TTL" {
set key "test_slab_fast_ttl"
r del $key

# very small ttl
r exhset $key field1 val PX 1
r exhset $key field2 val PX 2
r exhset $key field3 val EX 1

after 3000

assert_equal 0 [r exhlen $key]
assert_equal "" [r exhget $key field1]
assert_equal "" [r exhget $key field2]
assert_equal "" [r exhget $key field3]
}

test "Test Slab same field repeated set/del" {
set key "test_slab_repeat"
r del $key

for {set i 0} {$i < 500} {incr i} {
r exhset $key field val ex [expr {($i % 3) + 1}]
if {$i % 2 == 0} {
r exhdel $key field
}
}

# final set to ensure field exists
r exhset $key field val ex 2
assert_equal 1 [r exhexists $key field]
}

test "Test Slab scan during expirations" {
set key "test_slab_scan"
r del $key

for {set i 0} {$i < 800} {incr i} {
r exhset $key field_$i val ex 1
}

after 1500

# scan should not crash even if most fields expired
set cur 0
while 1 {
set res [r exhscan $key $cur]
set cur [lindex $res 0]
if {$cur == 0} break
}

assert_equal 0 [r exhlen $key]
}
}
Loading