@@ -557,6 +557,20 @@ private void Cycle(int hotCount)
557557 break ;
558558 }
559559 }
560+
561+ // If we get here, we have cycled the queues multiple times and still have not removed an item.
562+ // This can happen if the cache is full of items that are not discardable. In this case, we simply
563+ // discard the coldest item to avoid unbounded growth.
564+ if ( dest != ItemDestination . Remove )
565+ {
566+ // if an item was last moved into warm, move the last warm item to cold to prevent enlarging warm
567+ if ( dest == ItemDestination . Warm )
568+ {
569+ LastWarmToCold ( ) ;
570+ }
571+
572+ RemoveCold ( ItemRemovedReason . Evicted ) ;
573+ }
560574 }
561575 else
562576 {
@@ -566,6 +580,20 @@ private void Cycle(int hotCount)
566580 }
567581 }
568582
583+ private void LastWarmToCold ( )
584+ {
585+ Interlocked . Decrement ( ref this . counter . warm ) ;
586+
587+ if ( this . hotQueue . TryDequeue ( out var item ) )
588+ {
589+ this . Move ( item , ItemDestination . Cold , ItemRemovedReason . Evicted ) ;
590+ }
591+ else
592+ {
593+ Interlocked . Increment ( ref this . counter . warm ) ;
594+ }
595+ }
596+
569597 private void CycleDuringWarmup ( int hotCount )
570598 {
571599 // do nothing until hot is full
@@ -698,6 +726,20 @@ private void CycleDuringWarmup(int hotCount)
698726 }
699727 }
700728
729+ private void RemoveCold ( ItemRemovedReason removedReason )
730+ {
731+ Interlocked . Decrement ( ref this . counter . cold ) ;
732+
733+ if ( this . coldQueue . TryDequeue ( out var item ) )
734+ {
735+ this . Move ( item , ItemDestination . Remove , removedReason ) ;
736+ }
737+ else
738+ {
739+ Interlocked . Increment ( ref this . counter . cold ) ;
740+ }
741+ }
742+
701743 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
702744 private int Move ( I item , ItemDestination where , ItemRemovedReason removedReason )
703745 {
0 commit comments