@@ -1463,40 +1463,56 @@ where
14631463 return None ;
14641464 }
14651465
1466- loop {
1467- let idx = self . hand ;
1468- if self . slots [ idx] . is_some ( ) {
1469- if self . referenced [ idx] {
1466+ // Not full — scan for an empty slot without disturbing ref bits.
1467+ if self . len < self . capacity ( ) {
1468+ let cap = self . capacity ( ) ;
1469+ for offset in 0 ..cap {
1470+ let idx = ( self . hand + offset) % cap;
1471+ if self . slots [ idx] . is_none ( ) {
1472+ let entry_key = key. clone ( ) ;
1473+ self . slots [ idx] = Some ( Entry {
1474+ key : entry_key,
1475+ value,
1476+ } ) ;
14701477 self . referenced [ idx] = false ;
1471- self . advance_hand ( ) ;
1472- continue ;
1478+ self . index . insert ( key, idx) ;
1479+ self . len += 1 ;
1480+ self . hand = ( idx + 1 ) % cap;
1481+ return None ;
14731482 }
1483+ }
1484+ debug_assert ! ( false , "len < capacity but no empty slot found" ) ;
1485+ }
14741486
1475- let evicted = self . slots [ idx] . take ( ) . expect ( "occupied slot missing" ) ;
1476- self . index . remove ( & evicted. key ) ;
1477-
1478- let entry_key = key. clone ( ) ;
1479- self . slots [ idx] = Some ( Entry {
1480- key : entry_key,
1481- value,
1482- } ) ;
1487+ // Full — CLOCK sweep to find and evict a victim.
1488+ // One pass clears all ref bits; the second finds an unreferenced slot.
1489+ let cap = self . capacity ( ) ;
1490+ for _ in 0 ..( 2 * cap) {
1491+ let idx = self . hand ;
1492+ if self . referenced [ idx] {
14831493 self . referenced [ idx] = false ;
1484- self . index . insert ( key, idx) ;
14851494 self . advance_hand ( ) ;
1486- return Some ( ( evicted . key , evicted . value ) ) ;
1495+ continue ;
14871496 }
14881497
1498+ let evicted = self . slots [ idx] . take ( ) . expect ( "occupied slot missing" ) ;
1499+ self . index . remove ( & evicted. key ) ;
1500+
14891501 let entry_key = key. clone ( ) ;
14901502 self . slots [ idx] = Some ( Entry {
14911503 key : entry_key,
14921504 value,
14931505 } ) ;
14941506 self . referenced [ idx] = false ;
14951507 self . index . insert ( key, idx) ;
1496- self . len += 1 ;
14971508 self . advance_hand ( ) ;
1498- return None ;
1509+ return Some ( ( evicted . key , evicted . value ) ) ;
14991510 }
1511+ debug_assert ! (
1512+ false ,
1513+ "insert sweep exceeded 2*capacity without finding victim"
1514+ ) ;
1515+ None
15001516 }
15011517
15021518 /// Peeks the next eviction candidate without modifying state.
@@ -2012,13 +2028,10 @@ mod tests {
20122028 assert ! ( ring. peek( & "b" ) . is_none( ) ) ;
20132029
20142030 let evicted = ring. insert ( "d" , 4 ) ;
2031+ assert_eq ! ( evicted, None , "ring has free slot, must not evict" ) ;
20152032 assert ! ( ring. contains( & "d" ) ) ;
20162033 assert ! ( !ring. contains( & "b" ) ) ;
2017- if evicted. is_some ( ) {
2018- assert_eq ! ( ring. len( ) , 2 ) ;
2019- } else {
2020- assert_eq ! ( ring. len( ) , 3 ) ;
2021- }
2034+ assert_eq ! ( ring. len( ) , 3 ) ;
20222035 }
20232036
20242037 #[ test]
@@ -2368,6 +2381,84 @@ mod tests {
23682381 let pairs: Vec < _ > = cache. into_inner ( ) . into_iter ( ) . collect ( ) ;
23692382 assert_eq ! ( pairs. len( ) , 2 ) ;
23702383 }
2384+
2385+ // -----------------------------------------------------------------------
2386+ // Regression: insert must not evict when empty slots exist (len < cap)
2387+ // -----------------------------------------------------------------------
2388+
2389+ #[ test]
2390+ fn insert_uses_empty_slot_after_remove_no_eviction ( ) {
2391+ let mut ring = ClockRing :: new ( 3 ) ;
2392+ ring. insert ( "a" , 1 ) ; // slot 0, hand -> 1
2393+ ring. insert ( "b" , 2 ) ; // slot 1, hand -> 2
2394+ ring. insert ( "c" , 3 ) ; // slot 2, hand -> 0
2395+
2396+ ring. remove ( & "b" ) ; // slot 1 now empty, len = 2
2397+ assert_eq ! ( ring. len( ) , 2 ) ;
2398+
2399+ // Ring has room (len=2 < cap=3). Insert must use the empty slot,
2400+ // not evict a live entry.
2401+ let evicted = ring. insert ( "d" , 4 ) ;
2402+ assert_eq ! ( evicted, None , "must not evict when ring has free slots" ) ;
2403+ assert_eq ! ( ring. len( ) , 3 ) ;
2404+ assert ! ( ring. contains( & "a" ) , "\" a\" should still be present" ) ;
2405+ assert ! ( ring. contains( & "c" ) , "\" c\" should still be present" ) ;
2406+ assert ! ( ring. contains( & "d" ) , "\" d\" should have been inserted" ) ;
2407+ ring. debug_validate_invariants ( ) ;
2408+ }
2409+
2410+ #[ test]
2411+ fn insert_uses_empty_slot_after_pop_victim_no_eviction ( ) {
2412+ let mut ring = ClockRing :: new ( 3 ) ;
2413+ ring. insert ( "a" , 1 ) ;
2414+ ring. insert ( "b" , 2 ) ;
2415+ ring. insert ( "c" , 3 ) ;
2416+
2417+ let victim = ring. pop_victim ( ) ;
2418+ assert ! ( victim. is_some( ) ) ;
2419+ assert_eq ! ( ring. len( ) , 2 ) ;
2420+
2421+ // After pop_victim freed a slot, insert must not evict again.
2422+ let evicted = ring. insert ( "d" , 4 ) ;
2423+ assert_eq ! ( evicted, None , "must not evict when ring has free slots" ) ;
2424+ assert_eq ! ( ring. len( ) , 3 ) ;
2425+ assert ! ( ring. contains( & "d" ) ) ;
2426+ ring. debug_validate_invariants ( ) ;
2427+ }
2428+
2429+ #[ test]
2430+ fn insert_no_eviction_preserves_ref_bits ( ) {
2431+ let mut ring = ClockRing :: new ( 3 ) ;
2432+ ring. insert ( "a" , 1 ) ; // slot 0, hand -> 1
2433+ ring. insert ( "b" , 2 ) ; // slot 1, hand -> 2
2434+ ring. insert ( "c" , 3 ) ; // slot 2, hand -> 0
2435+
2436+ ring. touch ( & "a" ) ; // ref[0] = true
2437+
2438+ ring. remove ( & "c" ) ; // slot 2 = None, hand stays 0, len = 2
2439+
2440+ // Non-full insert: must find the empty slot without clearing
2441+ // "a"'s reference bit.
2442+ let evicted = ring. insert ( "d" , 4 ) ;
2443+ assert_eq ! ( evicted, None , "must not evict when ring has free slots" ) ;
2444+ assert_eq ! ( ring. len( ) , 3 ) ;
2445+
2446+ // Now full. Insert triggers eviction. If "a"'s ref bit was
2447+ // preserved, "a" survives; victim is "b" or "d".
2448+ let evicted = ring. insert ( "e" , 5 ) ;
2449+ assert ! ( evicted. is_some( ) ) ;
2450+ let ( evicted_key, _) = evicted. unwrap ( ) ;
2451+ assert ! (
2452+ evicted_key == "b" || evicted_key == "d" ,
2453+ "expected unreferenced victim, got {:?}" ,
2454+ evicted_key
2455+ ) ;
2456+ assert ! (
2457+ ring. contains( & "a" ) ,
2458+ "\" a\" should survive: ref bit preserved"
2459+ ) ;
2460+ ring. debug_validate_invariants ( ) ;
2461+ }
23712462}
23722463
23732464#[ cfg( test) ]
0 commit comments