@@ -5,6 +5,7 @@ use log::info;
55use std:: cmp:: Reverse ;
66use std:: collections:: BinaryHeap ;
77use std:: collections:: HashMap ;
8+ use std:: collections:: VecDeque ;
89use std:: sync:: Arc ;
910use std:: sync:: RwLock ;
1011
@@ -404,7 +405,7 @@ impl Tree {
404405 . collect ( )
405406 }
406407
407- pub fn evict_tenant_data ( & self , max_size : usize ) {
408+ pub fn evict_tenant_by_size ( & self , max_size : usize ) {
408409 // Calculate used size and collect leaves
409410 let mut stack = vec ! [ Arc :: clone( & self . root) ] ;
410411 let mut pq = BinaryHeap :: new ( ) ;
@@ -483,6 +484,46 @@ impl Tree {
483484 }
484485 }
485486
487+ pub fn remove_tenant ( & self , tenant : & str ) {
488+ // 1. Find all the leaves for the tenant
489+ let mut stack = vec ! [ Arc :: clone( & self . root) ] ;
490+ let mut queue = VecDeque :: new ( ) ;
491+
492+ while let Some ( curr) = stack. pop ( ) {
493+ for child in curr. children . iter ( ) {
494+ stack. push ( Arc :: clone ( child. value ( ) ) ) ;
495+ }
496+
497+ if Tree :: leaf_of ( & curr) . contains ( & tenant. to_string ( ) ) {
498+ queue. push_back ( Arc :: clone ( & curr) ) ;
499+ }
500+ }
501+
502+ // 2. Start from the leaves and traverse up to the root, removing the tenant from each node
503+ while let Some ( curr) = queue. pop_front ( ) {
504+ // remove tenant from node
505+ curr. tenant_last_access_time . remove ( & tenant. to_string ( ) ) ;
506+
507+ // remove empty nodes
508+ if curr. children . is_empty ( ) && curr. tenant_last_access_time . is_empty ( ) {
509+ if let Some ( parent) = curr. parent . read ( ) . unwrap ( ) . as_ref ( ) {
510+ let first_char = curr. text . read ( ) . unwrap ( ) . chars ( ) . next ( ) . unwrap ( ) ;
511+ parent. children . remove ( & first_char) ;
512+ }
513+ }
514+
515+ // add parent to queue if it becomes a leaf
516+ if let Some ( parent) = curr. parent . read ( ) . unwrap ( ) . as_ref ( ) {
517+ if Tree :: leaf_of ( parent) . contains ( & tenant. to_string ( ) ) {
518+ queue. push_back ( Arc :: clone ( & parent) ) ;
519+ }
520+ }
521+ }
522+
523+ // 3. Remove the tenant from the tenant_char_count map
524+ self . tenant_char_count . remove ( & tenant. to_string ( ) ) ;
525+ }
526+
486527 pub fn get_tenant_char_count ( & self ) -> HashMap < String , usize > {
487528 self . tenant_char_count
488529 . iter ( )
@@ -673,7 +714,7 @@ mod tests {
673714 ) ;
674715
675716 // Test eviction
676- tree. evict_tenant_data ( 3 ) ; // This should evict tenants with more than 3 chars
717+ tree. evict_tenant_by_size ( 3 ) ; // This should evict tenants with more than 3 chars
677718
678719 let post_eviction_smallest = tree. get_smallest_tenant ( ) ;
679720 println ! ( "Smallest tenant after eviction: {}" , post_eviction_smallest) ;
@@ -754,7 +795,7 @@ mod tests {
754795 ) ;
755796
756797 // Phase 4: Eviction test
757- tree. evict_tenant_data ( 10 ) ;
798+ tree. evict_tenant_by_size ( 10 ) ;
758799
759800 let computed_sizes = tree. get_used_size_per_tenant ( ) ;
760801 let maintained_counts: HashMap < String , usize > = tree
@@ -1132,7 +1173,7 @@ mod tests {
11321173 assert_eq ! ( sizes_before. get( "tenant2" ) . unwrap( ) , & 10 ) ; // "hello" + "world" = 10
11331174
11341175 // Evict - should remove "hello" from tenant2 as it's the oldest
1135- tree. evict_tenant_data ( max_size) ;
1176+ tree. evict_tenant_by_size ( max_size) ;
11361177
11371178 tree. pretty_print ( ) ;
11381179
@@ -1168,7 +1209,7 @@ mod tests {
11681209 }
11691210
11701211 // Perform eviction
1171- tree. evict_tenant_data ( max_size) ;
1212+ tree. evict_tenant_by_size ( max_size) ;
11721213
11731214 // Check sizes after eviction
11741215 let sizes_after = tree. get_used_size_per_tenant ( ) ;
@@ -1200,7 +1241,7 @@ mod tests {
12001241 let handle = thread:: spawn ( move || {
12011242 while start_time. elapsed ( ) < test_duration {
12021243 // Run eviction
1203- tree. evict_tenant_data ( max_size) ;
1244+ tree. evict_tenant_by_size ( max_size) ;
12041245
12051246 // Sleep for 5 seconds
12061247 thread:: sleep ( Duration :: from_secs ( 5 ) ) ;
@@ -1245,7 +1286,7 @@ mod tests {
12451286 }
12461287
12471288 // final eviction
1248- tree. evict_tenant_data ( max_size) ;
1289+ tree. evict_tenant_by_size ( max_size) ;
12491290
12501291 // Final size check
12511292 let final_sizes = tree. get_used_size_per_tenant ( ) ;
@@ -1352,4 +1393,91 @@ mod tests {
13521393 assert_eq ! ( tree. prefix_match_tenant( "hello" , "tenant3" ) , "" ) ; // Non-existent tenant
13531394 assert_eq ! ( tree. prefix_match_tenant( "help" , "tenant3" ) , "" ) ; // Non-existent tenant
13541395 }
1396+
1397+ #[ test]
1398+ fn test_simple_tenant_eviction ( ) {
1399+ let tree = Tree :: new ( ) ;
1400+
1401+ // Insert data for multiple tenants
1402+ tree. insert ( "hello" , "tenant1" ) ;
1403+ tree. insert ( "world" , "tenant1" ) ;
1404+ tree. insert ( "hello" , "tenant2" ) ;
1405+ tree. insert ( "help" , "tenant2" ) ;
1406+
1407+ // Verify initial state
1408+ let initial_sizes = tree. get_used_size_per_tenant ( ) ;
1409+ assert_eq ! ( initial_sizes. get( "tenant1" ) . unwrap( ) , & 10 ) ; // "hello" + "world"
1410+ assert_eq ! ( initial_sizes. get( "tenant2" ) . unwrap( ) , & 6 ) ; // "hello" + "p"
1411+
1412+ // Evict tenant1
1413+ tree. remove_tenant ( "tenant1" ) ;
1414+
1415+ // Verify after eviction
1416+ let final_sizes = tree. get_used_size_per_tenant ( ) ;
1417+ assert ! (
1418+ !final_sizes. contains_key( "tenant1" ) ,
1419+ "tenant1 should be completely removed"
1420+ ) ;
1421+ assert_eq ! (
1422+ final_sizes. get( "tenant2" ) . unwrap( ) ,
1423+ & 6 ,
1424+ "tenant2 should be unaffected"
1425+ ) ;
1426+
1427+ // Verify tenant1's data is inaccessible
1428+ assert_eq ! ( tree. prefix_match_tenant( "hello" , "tenant1" ) , "" ) ;
1429+ assert_eq ! ( tree. prefix_match_tenant( "world" , "tenant1" ) , "" ) ;
1430+
1431+ // Verify tenant2's data is still accessible
1432+ assert_eq ! ( tree. prefix_match_tenant( "hello" , "tenant2" ) , "hello" ) ;
1433+ assert_eq ! ( tree. prefix_match_tenant( "help" , "tenant2" ) , "help" ) ;
1434+ }
1435+
1436+ #[ test]
1437+ fn test_complex_tenant_eviction ( ) {
1438+ let tree = Tree :: new ( ) ;
1439+
1440+ // Create a more complex tree structure with shared prefixes
1441+ tree. insert ( "apple" , "tenant1" ) ;
1442+ tree. insert ( "application" , "tenant1" ) ;
1443+ tree. insert ( "apple" , "tenant2" ) ;
1444+ tree. insert ( "appetite" , "tenant2" ) ;
1445+ tree. insert ( "banana" , "tenant1" ) ;
1446+ tree. insert ( "banana" , "tenant2" ) ;
1447+ tree. insert ( "ball" , "tenant2" ) ;
1448+
1449+ // Verify initial state
1450+ let initial_sizes = tree. get_used_size_per_tenant ( ) ;
1451+ println ! ( "Initial sizes: {:?}" , initial_sizes) ;
1452+ tree. pretty_print ( ) ;
1453+
1454+ // Evict tenant1
1455+ tree. remove_tenant ( "tenant1" ) ;
1456+
1457+ // Verify final state
1458+ let final_sizes = tree. get_used_size_per_tenant ( ) ;
1459+ println ! ( "Final sizes: {:?}" , final_sizes) ;
1460+ tree. pretty_print ( ) ;
1461+
1462+ // Verify tenant1 is completely removed
1463+ assert ! (
1464+ !final_sizes. contains_key( "tenant1" ) ,
1465+ "tenant1 should be completely removed"
1466+ ) ;
1467+
1468+ // Verify all tenant1's data is inaccessible
1469+ assert_eq ! ( tree. prefix_match_tenant( "apple" , "tenant1" ) , "" ) ;
1470+ assert_eq ! ( tree. prefix_match_tenant( "application" , "tenant1" ) , "" ) ;
1471+ assert_eq ! ( tree. prefix_match_tenant( "banana" , "tenant1" ) , "" ) ;
1472+
1473+ // Verify tenant2's data is intact
1474+ assert_eq ! ( tree. prefix_match_tenant( "apple" , "tenant2" ) , "apple" ) ;
1475+ assert_eq ! ( tree. prefix_match_tenant( "appetite" , "tenant2" ) , "appetite" ) ;
1476+ assert_eq ! ( tree. prefix_match_tenant( "banana" , "tenant2" ) , "banana" ) ;
1477+ assert_eq ! ( tree. prefix_match_tenant( "ball" , "tenant2" ) , "ball" ) ;
1478+
1479+ // Verify the tree structure is still valid for tenant2
1480+ let tenant2_size = final_sizes. get ( "tenant2" ) . unwrap ( ) ;
1481+ assert_eq ! ( tenant2_size, & ( 5 + 5 + 6 + 2 ) ) ; // "apple" + "etite" + "banana" + "ll"
1482+ }
13551483}
0 commit comments