@@ -202,10 +202,12 @@ impl RocksDbOwnerDropTestGate {
202202}
203203
204204#[ cfg( test) ]
205- fn rocks_db_owner_drop_test_gate_registry ( )
206- -> & ' static Mutex < Option < ( std:: path:: PathBuf , Weak < RocksDbOwnerDropTestGate > ) > > {
207- static GATE : OnceLock < Mutex < Option < ( std:: path:: PathBuf , Weak < RocksDbOwnerDropTestGate > ) > > > =
208- OnceLock :: new ( ) ;
205+ type RocksDbOwnerDropGateRegistry =
206+ Mutex < Option < ( std:: path:: PathBuf , Weak < RocksDbOwnerDropTestGate > ) > > ;
207+
208+ #[ cfg( test) ]
209+ fn rocks_db_owner_drop_test_gate_registry ( ) -> & ' static RocksDbOwnerDropGateRegistry {
210+ static GATE : OnceLock < RocksDbOwnerDropGateRegistry > = OnceLock :: new ( ) ;
209211 GATE . get_or_init ( || Mutex :: new ( None ) )
210212}
211213
@@ -1008,19 +1010,23 @@ macro_rules! get_db_and_cfs {
10081010}
10091011
10101012#[ cfg( test) ]
1013+ #[ allow( clippy:: unwrap_used) ]
10111014mod lifecycle_tests {
1012- use std:: sync:: { Arc , mpsc} ;
1015+ use std:: sync:: { Arc , Mutex , OnceLock , mpsc} ;
10131016 use std:: thread;
10141017 use std:: time:: Duration ;
10151018
10161019 use super :: {
10171020 ColumnFamilyIndex , Redis , RocksDbOwnerDropTestGate , install_rocks_db_owner_drop_test_gate,
10181021 } ;
1022+ use crate :: ScoreMember ;
10191023 use crate :: data_compaction_filter:: {
1020- CompactionFilterTestGate , install_compaction_filter_test_gate,
1024+ CompactionFilterTestGate , compaction_filter_test_serial_mutex,
1025+ install_compaction_filter_test_gate,
10211026 } ;
10221027 use crate :: format_base_key:: BaseMetaKey ;
1023- use crate :: format_base_meta_value:: ParsedSetsMetaValue ;
1028+ use crate :: format_base_meta_value:: { ParsedBaseMetaValue , ParsedSetsMetaValue } ;
1029+ use crate :: storage_define:: SUFFIX_RESERVE_LENGTH ;
10241030 use crate :: { BgTaskHandler , StorageOptions , safe_cleanup_test_db, unique_test_db_path} ;
10251031 use kstd:: lock_mgr:: LockMgr ;
10261032 use rocksdb:: IteratorMode ;
@@ -1047,8 +1053,27 @@ mod lifecycle_tests {
10471053 redis
10481054 }
10491055
1056+ fn lifecycle_test_mutex ( ) -> & ' static Mutex < ( ) > {
1057+ static MUTEX : OnceLock < Mutex < ( ) > > = OnceLock :: new ( ) ;
1058+ MUTEX . get_or_init ( || Mutex :: new ( ( ) ) )
1059+ }
1060+
1061+ fn compaction_filter_key_prefix ( key : & [ u8 ] ) -> Vec < u8 > {
1062+ let mut prefix = BaseMetaKey :: new ( key)
1063+ . encode ( )
1064+ . expect ( "test key prefix should encode" ) ;
1065+ prefix. truncate ( prefix. len ( ) - SUFFIX_RESERVE_LENGTH ) ;
1066+ prefix. to_vec ( )
1067+ }
1068+
10501069 #[ test]
10511070 fn del_compaction_removes_old_generation_and_preserves_recreated_set ( ) {
1071+ let _lifecycle_test_guard = lifecycle_test_mutex ( )
1072+ . lock ( )
1073+ . expect ( "lifecycle compaction tests must run serially" ) ;
1074+ let _compaction_filter_test_guard = compaction_filter_test_serial_mutex ( )
1075+ . lock ( )
1076+ . expect ( "compaction filter gate tests must run serially" ) ;
10521077 let path = unique_test_db_path ( ) ;
10531078 safe_cleanup_test_db ( & path) ;
10541079
@@ -1094,7 +1119,7 @@ mod lifecycle_tests {
10941119 assert_eq ! ( parsed_tombstone. etime( ) , 0 ) ;
10951120 assert ! ( parsed_tombstone. version( ) > original_version) ;
10961121
1097- let gate = CompactionFilterTestGate :: new ( & [ ] ) ;
1122+ let gate = CompactionFilterTestGate :: new ( & compaction_filter_key_prefix ( key ) ) ;
10981123 let _gate_guard = install_compaction_filter_test_gate ( Arc :: clone ( & gate) ) ;
10991124 let compactor = Arc :: clone ( & redis) ;
11001125 let compaction_thread = thread:: spawn ( move || {
@@ -1135,8 +1160,139 @@ mod lifecycle_tests {
11351160 safe_cleanup_test_db ( & path) ;
11361161 }
11371162
1163+ #[ test]
1164+ fn del_compaction_removes_old_generation_and_preserves_recreated_zset ( ) {
1165+ let _lifecycle_test_guard = lifecycle_test_mutex ( )
1166+ . lock ( )
1167+ . expect ( "lifecycle compaction tests must run serially" ) ;
1168+ let _compaction_filter_test_guard = compaction_filter_test_serial_mutex ( )
1169+ . lock ( )
1170+ . expect ( "compaction filter gate tests must run serially" ) ;
1171+ let path = unique_test_db_path ( ) ;
1172+ safe_cleanup_test_db ( & path) ;
1173+
1174+ let mut storage_options = StorageOptions :: default ( ) ;
1175+ storage_options. options . set_disable_auto_compactions ( true ) ;
1176+ let ( bg_task_handler, _) = BgTaskHandler :: new ( ) ;
1177+ let mut redis = Redis :: new (
1178+ Arc :: new ( storage_options) ,
1179+ 0 ,
1180+ Arc :: new ( bg_task_handler) ,
1181+ Arc :: new ( kstd:: lock_mgr:: LockMgr :: new ( 64 ) ) ,
1182+ ) ;
1183+ redis
1184+ . open ( path. to_str ( ) . expect ( "test DB path should be valid UTF-8" ) )
1185+ . expect ( "compaction test Redis should open" ) ;
1186+ let redis = Arc :: new ( redis) ;
1187+
1188+ let key = b"del_compaction_zset" ;
1189+ let old_members = [
1190+ ScoreMember :: new ( 1.0 , b"old-member-1" . to_vec ( ) ) ,
1191+ ScoreMember :: new ( 2.0 , b"old-member-2" . to_vec ( ) ) ,
1192+ ] ;
1193+ let mut added = 0 ;
1194+ redis
1195+ . zadd ( key, & old_members, & mut added)
1196+ . expect ( "initial zset write should succeed" ) ;
1197+ assert_eq ! ( added, 2 ) ;
1198+
1199+ let db = redis. db ( ) . expect ( "Redis should own RocksDB" ) ;
1200+ db. flush ( ) . expect ( "initial data should be flushed" ) ;
1201+ let meta_key = BaseMetaKey :: new ( key) . encode ( ) . unwrap ( ) ;
1202+ let meta_cf = redis. get_cf_handle ( ColumnFamilyIndex :: MetaCF ) . unwrap ( ) ;
1203+ let original_meta = db. get_cf ( & meta_cf, & meta_key) . unwrap ( ) . unwrap ( ) ;
1204+ let original_version = ParsedBaseMetaValue :: new ( & original_meta[ ..] )
1205+ . unwrap ( )
1206+ . version ( ) ;
1207+ let data_cf = redis. get_cf_handle ( ColumnFamilyIndex :: ZsetsDataCF ) . unwrap ( ) ;
1208+ let score_cf = redis
1209+ . get_cf_handle ( ColumnFamilyIndex :: ZsetsScoreCF )
1210+ . unwrap ( ) ;
1211+ let old_data_keys: Vec < Vec < u8 > > = db
1212+ . iterator_cf ( & data_cf, IteratorMode :: Start )
1213+ . map ( |entry| entry. unwrap ( ) . 0 . to_vec ( ) )
1214+ . collect ( ) ;
1215+ let old_score_keys: Vec < Vec < u8 > > = db
1216+ . iterator_cf ( & score_cf, IteratorMode :: Start )
1217+ . map ( |entry| entry. unwrap ( ) . 0 . to_vec ( ) )
1218+ . collect ( ) ;
1219+ assert_eq ! ( old_data_keys. len( ) , old_members. len( ) ) ;
1220+ assert_eq ! ( old_score_keys. len( ) , old_members. len( ) ) ;
1221+
1222+ assert ! ( redis. del_key( key) . unwrap( ) ) ;
1223+ db. flush ( ) . expect ( "tombstone should be flushed" ) ;
1224+ let tombstone = db. get_cf ( & meta_cf, & meta_key) . unwrap ( ) . unwrap ( ) ;
1225+ let parsed_tombstone = ParsedBaseMetaValue :: new ( & tombstone[ ..] ) . unwrap ( ) ;
1226+ assert_eq ! ( parsed_tombstone. count( ) , 0 ) ;
1227+ assert_eq ! ( parsed_tombstone. etime( ) , 0 ) ;
1228+ assert ! ( parsed_tombstone. version( ) > original_version) ;
1229+
1230+ let gate = CompactionFilterTestGate :: new ( & compaction_filter_key_prefix ( key) ) ;
1231+ let _gate_guard = install_compaction_filter_test_gate ( Arc :: clone ( & gate) ) ;
1232+ let compactor = Arc :: clone ( & redis) ;
1233+ let compaction_thread = thread:: spawn ( move || {
1234+ compactor
1235+ . compact_range ( None , None )
1236+ . expect ( "manual compaction should succeed" ) ;
1237+ } ) ;
1238+ assert ! ( gate. wait_until_entered( Duration :: from_secs( 10 ) ) ) ;
1239+ gate. release ( ) ;
1240+ compaction_thread. join ( ) . unwrap ( ) ;
1241+
1242+ let removed_keys = gate. wait_until_removed ( Duration :: from_secs ( 10 ) ) ;
1243+ assert ! ( old_data_keys. iter( ) . all( |key| removed_keys. contains( key) ) ) ;
1244+ assert ! ( old_score_keys. iter( ) . all( |key| removed_keys. contains( key) ) ) ;
1245+ let remaining_data_keys: Vec < Vec < u8 > > = db
1246+ . iterator_cf ( & data_cf, IteratorMode :: Start )
1247+ . map ( |entry| entry. unwrap ( ) . 0 . to_vec ( ) )
1248+ . collect ( ) ;
1249+ let remaining_score_keys: Vec < Vec < u8 > > = db
1250+ . iterator_cf ( & score_cf, IteratorMode :: Start )
1251+ . map ( |entry| entry. unwrap ( ) . 0 . to_vec ( ) )
1252+ . collect ( ) ;
1253+ assert ! (
1254+ old_data_keys
1255+ . iter( )
1256+ . all( |key| !remaining_data_keys. contains( key) )
1257+ ) ;
1258+ assert ! (
1259+ old_score_keys
1260+ . iter( )
1261+ . all( |key| !remaining_score_keys. contains( key) )
1262+ ) ;
1263+
1264+ let new_members = [ ScoreMember :: new ( 3.5 , b"new-member" . to_vec ( ) ) ] ;
1265+ let mut recreated_added = 0 ;
1266+ redis
1267+ . zadd ( key, & new_members, & mut recreated_added)
1268+ . expect ( "recreated zset write should succeed" ) ;
1269+ assert_eq ! ( recreated_added, 1 ) ;
1270+ let mut score = None ;
1271+ redis
1272+ . zscore ( key, b"new-member" , & mut score)
1273+ . expect ( "zscore should read recreated member" ) ;
1274+ assert_eq ! ( score, Some ( b"3.5" . to_vec( ) ) ) ;
1275+ let mut range = Vec :: new ( ) ;
1276+ redis
1277+ . zrange ( key, 0 , -1 , true , & mut range)
1278+ . expect ( "zrange should read recreated member" ) ;
1279+ assert_eq ! ( range, vec![ b"new-member" . to_vec( ) , b"3.5" . to_vec( ) ] ) ;
1280+
1281+ drop ( score_cf) ;
1282+ drop ( data_cf) ;
1283+ drop ( meta_cf) ;
1284+ drop ( redis) ;
1285+ safe_cleanup_test_db ( & path) ;
1286+ }
1287+
11381288 #[ test]
11391289 fn dropping_last_owner_waits_for_active_compaction_filter_before_reopen ( ) {
1290+ let _lifecycle_test_guard = lifecycle_test_mutex ( )
1291+ . lock ( )
1292+ . expect ( "lifecycle compaction tests must run serially" ) ;
1293+ let _compaction_filter_test_guard = compaction_filter_test_serial_mutex ( )
1294+ . lock ( )
1295+ . expect ( "compaction filter gate tests must run serially" ) ;
11401296 let path = unique_test_db_path ( ) ;
11411297 safe_cleanup_test_db ( & path) ;
11421298
@@ -1279,13 +1435,10 @@ mod type_check_state_tests {
12791435 }
12801436
12811437 /// Forge a raw on-disk value that matches `format_base_value::is_stale`:
1282- /// * `type_byte` at offset 0 (a valid `DataType` tag, 0..=6)
1283- /// * `count` at offset 1..9 (meta count; only consulted for
1284- /// Set/Hash/ZSet/List — kept non-zero so those types
1285- /// are not short-circuited as stale by `count == 0`)
1286- /// * `etime` in the final 8 bytes (microseconds since epoch).
1287- /// `etime == 0` means "permanent" (never stale); any
1288- /// `etime < now` is stale.
1438+ /// * `type_byte` at offset 0 (a valid `DataType` tag, 0..=6)
1439+ /// * `count` at offset 1..9 (meta count; kept non-zero for composite types)
1440+ /// * `etime` in the final 8 bytes (microseconds since epoch); zero means
1441+ /// permanent and a value less than now is stale.
12891442 fn make_value ( type_byte : u8 , count : u64 , etime : u64 , total_len : usize ) -> Vec < u8 > {
12901443 assert ! (
12911444 total_len >= 9 ,
0 commit comments