Skip to content

Commit b21ff05

Browse files
authored
remove coldest when full (#378)
* remove cold * cleanup * disable long running test * rem * reverse cold/warm * reverse
1 parent 5565e86 commit b21ff05

File tree

2 files changed

+46
-10
lines changed

2 files changed

+46
-10
lines changed

BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,6 @@ public class KeysInOrderTestDataGenerator : IEnumerable<object[]>
364364
new object[] { new FavorWarmPartition(128, 0.6) },
365365
new object[] { new FavorWarmPartition(256, 0.6) },
366366
new object[] { new FavorWarmPartition(1024, 0.6) },
367-
//new object[] { new FavorWarmPartition(10*1024, 0.6) },
368-
//new object[] { new FavorWarmPartition(100*1024, 0.6) },
369367
};
370368

371369
public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();
@@ -392,14 +390,10 @@ public void WhenKeysAreContinuouslyRequestedInTheOrderTheyAreAddedCountIsBounded
392390
for (int j = 0; j < i; j++)
393391
{
394392
lru.GetOrAdd(j, valueFactory.Create);
395-
}
396-
}
397-
398-
// For larger cache sizes, I have observed capacity + 5. This is linked to the number of attempts.
399-
// This is clearly a bug that needs further investigation, but considered not harmful at this point
400-
// since growth is bounded, we just allow 4 more items than we should in the absolute worst case.
401-
testOutputHelper.WriteLine($"Total: {lru.Count} Hot: {lru.HotCount} Warm: {lru.WarmCount} Cold: {lru.ColdCount}");
402-
lru.Count.Should().BeLessOrEqualTo(capacity + 1);
393+
}
394+
395+
lru.Count.Should().BeLessOrEqualTo(capacity + 1, $"Total: {lru.Count} Hot: {lru.HotCount} Warm: {lru.WarmCount} Cold: {lru.ColdCount}");
396+
}
403397
}
404398

405399
[Fact]

BitFaster.Caching/Lru/ConcurrentLruCore.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)