Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Containers.Test/RingBufferTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,39 @@ public void Clear_ReleasesReferencesToClearedElements()
Assert.IsFalse(references.Any(r => r.IsAlive));
}

[TestMethod]
public void PushBack_ReleasesReferenceToEvictedElement()
{
// Length 5 is backed by a capacity of 8, so the evicted slot is not overwritten by the next push
RingBuffer<object> buffer = new(5);
WeakReference[] references = FillWithUnreferencedObjects(buffer, 1);
for (int i = 0; i < 5; i++)
{
buffer.PushBack(i);
}

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

object[] expected = [0, 1, 2, 3, 4];
Assert.AreSequenceEqual(expected, buffer);
Assert.IsFalse(references[0].IsAlive);
}

[TestMethod]
public void PushBack_FullPowerOfTwoBuffer_KeepsNewestElements()
{
// Length equals capacity, so the evicted front slot is the one the new element is written to
RingBuffer<int> buffer = new([1, 2, 3, 4], 4);
buffer.PushBack(5);
buffer.PushBack(6);

Assert.AreSequenceEqual(Enumerable.Range(3, 4), buffer);
Assert.AreEqual(3, buffer.Front());
Assert.AreEqual(6, buffer.Back());
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static WeakReference[] FillWithUnreferencedObjects(RingBuffer<object> buffer, int count)
{
Expand Down
9 changes: 8 additions & 1 deletion Containers/RingBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,13 @@ private void AllocateBuffer(int length)
/// <param name="o">The element to add.</param>
public void PushBack(T o)
{
Buffer[BackIndex] = o;
if (Count == Length)
{
// Clear the evicted slot so its element can be collected. When Length is not a power of two
// the slot lies outside the live window and would otherwise keep the element alive until it
// is next overwritten.
Buffer[FrontIndex] = default!;

// Advance front index with wraparound using bitwise AND for efficiency
FrontIndex = (FrontIndex + 1) & (Capacity - 1);
}
Expand All @@ -169,6 +173,9 @@ public void PushBack(T o)
Count++;
}

// Write after clearing, because when Length equals Capacity the evicted slot is this one
Buffer[BackIndex] = o;

// Advance back index with wraparound using bitwise AND for efficiency
BackIndex = (BackIndex + 1) & (Capacity - 1);
}
Expand Down
Loading