Files
2025-11-30 08:35:03 +02:00

473 lines
14 KiB
C#

// Define this to use simple synchronisation rather than events.
// They are about the same in terms of speed.
#define SimpleSynch
using NUnit.Framework;
using System;
using System.Threading;
namespace Unity.SharpZipLib.Tests.TestSupport
{
/// <summary>
/// A fixed size buffer of bytes. Both reading and writing are supported.
/// Reading from an empty buffer will wait until data is written. Writing to a full buffer
/// will wait until data is read.
/// </summary>
internal class ReadWriteRingBuffer
{
#region Constructors
/// <summary>
/// Create a new RingBuffer with a specified size.
/// </summary>
/// <param name="size">The size of the ring buffer to create.</param>
public ReadWriteRingBuffer(int size, CancellationToken? token = null)
{
if (size <= 0)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
array_ = new byte[size];
lockObject_ = new object();
token_ = token;
#if SimpleSynch
waitSpan_ = TimeSpan.FromMilliseconds(1);
#else
notEmptyEvent_ = new ManualResetEvent(false);
notFullEvent_ = new ManualResetEvent(true);
#endif
}
#endregion Constructors
/// <summary>
/// Clear the buffer contents.
/// </summary>
public void Clear()
{
tail_ = 0;
head_ = 0;
count_ = 0;
Array.Clear(array_, 0, array_.Length);
#if !SimpleSynch
notFullEvent_.Set();
notEmptyEvent_.Reset();
#endif
}
/// <summary>
/// Close the buffer for writing.
/// </summary>
/// <remarks>A Read when the buffer is closed and there is no data will return -1.</remarks>
public void Close()
{
isClosed_ = true;
#if !SimpleSynch
notEmptyEvent_.Set();
#endif
}
/// <summary>
/// Write adds a byte to the head of the RingBuffer.
/// </summary>
/// <param name="value">The value to add.</param>
public void WriteByte(byte value)
{
if (isClosed_)
{
throw new ApplicationException("Buffer is closed");
}
#if SimpleSynch
while (IsFull)
{
Thread.Sleep(waitSpan_);
token_?.ThrowIfCancellationRequested();
}
#else
notFullEvent_.WaitOne();
#endif
lock (lockObject_)
{
array_[head_] = value;
head_ = (head_ + 1) % array_.Length;
#if !SimpleSynch
bool setEmpty = (count_ == 0);
#endif
count_ += 1;
#if !SimpleSynch
if (IsFull)
{
notFullEvent_.Reset();
}
if (setEmpty)
{
notEmptyEvent_.Set();
}
#endif
}
bytesWritten_++;
}
public void Write(byte[] buffer, int index, int count)
{
if (isClosed_)
{
throw new ApplicationException("Buffer is closed");
}
while (count > 0)
{
#if SimpleSynch
while (IsFull)
{
Thread.Sleep(waitSpan_);
token_?.ThrowIfCancellationRequested();
}
#else
notFullEvent_.WaitOne();
#endif
// Gauranteed to not be full at this point, however readers may sill read
// from the buffer first.
lock (lockObject_)
{
int bytesToWrite = Length - Count;
if (count < bytesToWrite)
{
bytesToWrite = count;
}
#if !SimpleSynch
bool setEmpty = (count_ == 0);
#endif
while (bytesToWrite > 0)
{
array_[head_] = buffer[index];
index++;
head_ = (head_ + 1) % array_.Length;
bytesToWrite--;
bytesWritten_++;
count--;
count_++;
}
#if !SimpleSynch
if (IsFull)
{
notFullEvent_.Reset();
}
if (setEmpty)
{
notEmptyEvent_.Set();
}
#endif
}
}
}
/// <summary>
/// Read a byte from the buffer.
/// </summary>
/// <returns></returns>
public int ReadByte()
{
int result = -1;
#if SimpleSynch
while (!isClosed_ && IsEmpty)
{
Thread.Sleep(waitSpan_);
token_?.ThrowIfCancellationRequested();
}
#else
notEmptyEvent_.WaitOne();
#endif
if (!IsEmpty)
{
lock (lockObject_)
{
result = array_[tail_];
tail_ = (tail_ + 1) % array_.Length;
#if !SimpleSynch
bool setFull = IsFull;
#endif
count_ -= 1;
#if !SimpleSynch
if (!isClosed_ && (count_ == 0))
{
notEmptyEvent_.Reset();
}
if (setFull)
{
notFullEvent_.Set();
}
#endif
}
}
bytesRead_++;
return result;
}
public int Read(byte[] buffer, int index, int count)
{
int result = 0;
while (count > 0)
{
#if SimpleSynch
while (!isClosed_ && IsEmpty)
{
Thread.Sleep(waitSpan_);
token_?.ThrowIfCancellationRequested();
}
#else
notEmptyEvent_.WaitOne();
#endif
if (IsEmpty)
{
count = 0;
}
else
{
lock (lockObject_)
{
int toRead = Count;
if (toRead > count)
{
toRead = count;
}
result += toRead;
#if !SimpleSynch
bool setFull = IsFull;
#endif
while (toRead > 0)
{
buffer[index] = array_[tail_];
index++;
tail_ = (tail_ + 1) % array_.Length;
count--;
count_--;
toRead--;
bytesRead_++;
}
#if !SimpleSynch
if (!isClosed_ && (count_ == 0))
{
notEmptyEvent_.Reset();
}
if (setFull)
{
notFullEvent_.Set();
}
#endif
}
}
}
return result;
}
#region Properties
/// <summary>
/// Gets a value indicating wether the buffer is empty or not.
/// </summary>
public bool IsEmpty
{
get { return count_ == 0; }
}
public bool IsFull
{
get
{
return (count_ == array_.Length);
}
}
public bool IsClosed
{
get { return isClosed_; }
}
/// <summary>
/// Gets the number of elements in the buffer.
/// </summary>
public int Count
{
get
{
return count_;
}
}
public int Length
{
get { return array_.Length; }
}
public long BytesWritten
{
get { return bytesWritten_; }
}
public long BytesRead
{
get { return bytesRead_; }
}
/// <summary>
/// Indexer - Get an element from the tail of the RingBuffer.
/// </summary>
public byte this[int index]
{
get
{
if ((index < 0) || (index >= array_.Length))
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return array_[(tail_ + index) % array_.Length];
}
}
#endregion Properties
#region Instance Variables
/// <summary>
/// Flag indicating the buffer is closed.
/// </summary>
private bool isClosed_;
/// <summary>
/// Index for the head of the buffer.
/// </summary>
/// <remarks>Its the index of the next byte to be <see cref="Write">written</see>.</remarks>
private int head_;
/// <summary>
/// Index for the tail of the buffer.
/// </summary>
/// <remarks>Its the index of the next byte to be <see cref="Read">written</see>.</remarks>
private int tail_;
/// <summary>
/// The total number of elements added to the buffer.
/// </summary>
private int count_;
/// <summary>
/// Storage for the ring buffer contents.
/// </summary>
private byte[] array_;
private long bytesWritten_;
private long bytesRead_;
private object lockObject_;
private CancellationToken? token_;
private TimeSpan waitSpan_;
#if !SimpleSynch
ManualResetEvent notEmptyEvent_;
ManualResetEvent notFullEvent_;
#endif
#endregion Instance Variables
}
[TestFixture]
[Explicit("Meta tests (for ringbuffer)")]
internal class ExerciseBuffer
{
[Test]
public void Basic()
{
const int Size = 64;
buffer_ = new ReadWriteRingBuffer(Size);
Assert.IsFalse(buffer_.IsFull);
Assert.IsTrue(buffer_.IsEmpty);
buffer_.WriteByte(1);
Assert.IsFalse(buffer_.IsFull);
Assert.IsFalse(buffer_.IsEmpty);
Assert.AreEqual(1, buffer_.Count);
Assert.AreEqual(1, buffer_.ReadByte());
Assert.IsFalse(buffer_.IsFull);
Assert.IsTrue(buffer_.IsEmpty);
for (int i = 0; i < buffer_.Length; ++i)
{
buffer_.WriteByte(unchecked((byte)(i & 0xff)));
}
Assert.IsTrue(buffer_.IsFull);
Assert.IsFalse(buffer_.IsEmpty);
buffer_.Close();
Assert.IsTrue(buffer_.IsClosed);
bool caught = false;
try
{
buffer_.WriteByte(1);
}
catch
{
caught = true;
}
Assert.IsTrue(caught);
int count = Size;
int expected = 0;
while (count != 0)
{
Assert.AreEqual(count, buffer_.Count);
Assert.AreEqual(expected, buffer_.ReadByte());
count--;
expected = (expected + 1) & 0xff;
}
Assert.IsTrue(buffer_.IsEmpty);
Assert.AreEqual(-1, buffer_.ReadByte());
}
[Test]
public void Buffered()
{
const int Size = 64;
buffer_ = new ReadWriteRingBuffer(Size);
byte[] writeBuffer = new byte[16];
for (int i = 0; i < 16; ++i)
{
writeBuffer[i] = (byte)i;
}
buffer_.Write(writeBuffer, 0, 3);
Assert.AreEqual(3, buffer_.Count);
byte[] readBuffer = new byte[16];
Assert.AreEqual(3, buffer_.Read(readBuffer, 0, 3));
for (int i = 0; i < 3; ++i)
{
Assert.AreEqual(i, readBuffer[i]);
}
}
[Test]
public void Threaded()
{
buffer_ = new ReadWriteRingBuffer(8);
readTarget_ = writeTarget_ = 16384;
var reader = new Thread(Reader);
reader.Start();
var writer = new Thread(Writer);
writer.Start();
writer.Join();
reader.Join();
}
private void Reader()
{
var r = new Random();
byte nextValue = 0;
while (readTarget_ > 0)
{
int thisTime = r.Next(16);
if (thisTime > readTarget_)
{
thisTime = readTarget_;
}
while (thisTime > 0)
{
int readValue = buffer_.ReadByte();
Assert.AreEqual(nextValue, readValue);
nextValue = (byte)((nextValue + 1) & 0xff);
thisTime--;
readTarget_--;
}
Thread.Sleep(r.Next(10));
}
int last = buffer_.ReadByte();
Assert.AreEqual(-1, last);
Assert.IsTrue(buffer_.IsClosed);
}
private void Writer()
{
var r = new Random();
byte nextValue = 0;
while (writeTarget_ > 0)
{
int thisTime = r.Next(16);
if (thisTime > writeTarget_)
{
thisTime = writeTarget_;
}
while (thisTime > 0)
{
buffer_.WriteByte(nextValue);
nextValue = (byte)((nextValue + 1) & 0xff);
thisTime--;
writeTarget_--;
}
Thread.Sleep(r.Next(10));
}
buffer_.Close();
}
private int readTarget_;
private int writeTarget_;
private ReadWriteRingBuffer buffer_;
}
}