Shared Kit: Introduce BMemoryRingIO, a thread-safe ring buffer

This commit introduces a simple thread-safe ring buffer implementation
based on top of BDataIO. The main use case for this class will be to
implement shared buffers between threads for the upcoming refactoring
of Services Kit.

Change-Id: I526bc044b28c91496ad996fabebe538e75647f2c
Reviewed-on: https://review.haiku-os.org/c/haiku/+/2966
Reviewed-by: Jacob Secunda <secundaja@gmail.com>
Reviewed-by: waddlesplash <waddlesplash@gmail.com>
Reviewed-by: Adrien Destugues <pulkomandy@pulkomandy.tk>
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
This commit is contained in:
Leorize 2020-06-29 01:53:56 -05:00 committed by waddlesplash
parent fb1d7157d2
commit 86fa1c21e1
9 changed files with 906 additions and 1 deletions

View File

@ -0,0 +1,247 @@
/*
* Copyright 2022 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Leorize, leorize+oss@disroot.org
*
* Corresponds to:
* headers/private/shared/MemoryRingIO.h hrev54369
* src/kits/shared/MemoryRingIO.cpp hrev54369
*/
/*!
\file MemoryRingIO.h
\ingroup support
\ingroup libshared
\brief Provides the BMemoryRingIO class.
*/
/*!
\class BMemoryRingIO
\ingroup support
\ingroup libshared
\brief An implementation of a ring buffer with a BDataIO interface.
\since Haiku R1
*/
/*!
\fn BMemoryRingIO::BMemoryRingIO(size_t size)
\brief Creates a new BMemoryRingIO object with the given buffer size.
Call InitCheck() to verify that the buffer has been successfully created.
\param size The initial size of the buffer.
\since Haiku R1
\sa SetSize()
*/
/*!
\fn BMemoryRingIO::~BMemoryRingIO()
\brief Free up resources held by the object.
\since Haiku R1
*/
/*!
\fn status_t BMemoryRingIO::InitCheck() const
\brief Whether the object has been initialized correctly.
\retval B_OK Everything is initialized.
\retval B_NO_INIT The internal buffer couldn't be created or the buffer
capacity is \c 0.
\since Haiku R1
*/
/*!
\fn virtual ssize_t BMemoryRingIO::Read(void* buffer, size_t size)
\brief Reads data from the object into a buffer.
If the ring buffer is empty, this method blocks until some data is made
available. \c 0 is returned if \c size is \c 0 or the buffer is empty
and was set to have write disabled.
\return The amount of bytes read.
\retval B_BAD_VALUE \c buffer is \c NULL
\since Haiku R1
\sa SetWriteDisabled()
*/
/*!
\fn virtual ssize_t BMemoryRingIO::Write(const void* buffer, size_t size)
\brief Writes data from a buffer to the object.
If the ring buffer is full, this method blocks until some space is made
available.
\return The amount of bytes written. If the ring buffer is filled or
\c size is \c 0, \c 0 will be returned.
\retval B_BAD_VALUE \c buffer is \c NULL
\retval B_READ_ONLY_DEVICE Writes to the buffer has been disabled.
\since Haiku R1
\sa SetWriteDisabled()
*/
/*!
\fn status_t BMemoryRingIO::SetSize(size_t size)
\brief Change the ring buffer capacity.
\param size The new capacity for the ring buffer. This value will be
rounded up to the nearest power of two. The new capacity must be larger
or equal to BytesAvailable(). If set to \c 0 when there are no the bytes
available to be read, the buffer will be freed.
\retval B_OK The operation was successful.
\retval B_BAD_VALUE The new capacity is smaller than BytesAvailable().
\retval B_NO_MEMORY Memory couldn't be allocated to grow/create the buffer.
The buffer remains unchanged.
\since Haiku R1
\sa BytesAvailable() for the amount of data to be read.
*/
/*!
\fn void BMemoryRingIO::Clear()
\brief Discard all data in the object.
This method will discard all data within the buffer. However it does not
free the memory held by the buffer. If this is desired, use in combination
with SetSize() with \c 0 as the new capacity.
\since Haiku R1
\sa SetSize() for freeing memory held by the buffer,
or for growing the buffer's size.
*/
/*!
\fn size_t BMemoryRingIO::BytesAvailable()
\brief Get the amount of data stored in the object.
\return The amount of bytes ready to be read from the object.
\since Haiku R1
\sa BufferSize() for the total buffer size.
\sa SpaceAvailable() for the amount of space left for writing in the object.
*/
/*!
\fn size_t BMemoryRingIO::SpaceAvailable()
\brief Get the amount of space left in the object.
\return The remaining storage capacity of the object in bytes.
\since Haiku R1
\sa BufferSize() for the total buffer size.
\sa BytesAvailable() for the amount of data ready to be read.
*/
/*!
\fn size_t BMemoryRingIO::BufferSize()
\brief Get the total capacity of the object.
\return The total capacity of the object in bytes.
\since Haiku R1
\sa SpaceAvailable() for the amount of space left for writing in the object.
\sa BytesAvailable() for the amount of data ready to be read.
*/
/*!
\fn status_t BMemoryRingIO::WaitForRead(bigtime_t timeout = B_INFINITE_TIMEOUT)
\brief Wait for data to be available for reading.
This method will block the current thread until there's data ready to be
Read() from the object or until timeout has been reached.
\param timeout The minimum amount of time to wait in microseconds. If the
value is \c B_INFINITE_TIMEOUT, this method will not return until
data can be read from the object.
\retval B_OK There's data ready to be read.
\retval B_TIMED_OUT Timeout reached but no data has been made available.
\retval B_READ_ONLY_DEVICE The buffer has write disabled.
\sa Read()
\sa SetWriteDisabled()
*/
/*!
\fn status_t BMemoryRingIO::WaitForWrite(bigtime_t timeout = B_INFINITE_TIMEOUT)
\brief Wait for space to be available for writing.
This method will block the current thread until there are storage space
available for a Write() operation or until timeout has been reached.
\param timeout The minimum amount of time to wait in microseconds. If the
value is \c B_INFINITE_TIMEOUT, this method will not return until
data can be written into the object.
\retval B_OK There's storage space to write to.
\retval B_TIMED_OUT Timeout reached but no additional storage space has
been made available.
\retval B_READ_ONLY_DEVICE The buffer has write disabled.
\sa Write()
\sa SetWriteDisabled()
*/
/*!
\fn void BMemoryRingIO::SetWriteDisabled(bool disabled)
\brief Set whether writes to the ring buffer is disabled.
This method controls whether further writes to the ring buffer is allowed.
If writing is disabled, any further writes will error with
\c B_READ_ONLY_DEVICE, and read will no longer block on an empty buffer and
instead return \c 0. In addition, WaitForRead() and WaitForWrite() will
return \c B_READ_ONLY_DEVICE.
This method is usually used to notify the writer/reader of the pipe to not
write more and/or to wait for more data.
\param disabled Whether writes should be disabled.
\sa WriteDisabled() to see whether writes to the ring buffer is currently disabled.
*/
/*!
\fn bool BMemoryRingIO::WriteDisabled()
\brief Indicates whether writes to the ring buffer is disabled.
This method indicates whether further writes to the ring buffer is allowed.
See SetWriteDisabled() for more information.
\sa SetWriteDisabled() to control whether writes to the ring buffer is disabled.
\return \c true if writes to the ring buffer is disabled, \c false if not.
*/

View File

@ -0,0 +1,60 @@
/*
* Copyright 2022 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _MEMORY_RING_IO_H
#define _MEMORY_RING_IO_H
#include <pthread.h>
#include <DataIO.h>
#include <Locker.h>
class BMemoryRingIO : public BDataIO {
public:
BMemoryRingIO(size_t size);
virtual ~BMemoryRingIO();
status_t InitCheck() const;
virtual ssize_t Read(void* buffer, size_t size);
virtual ssize_t Write(const void* buffer, size_t size);
status_t SetSize(size_t size);
void Clear();
size_t BytesAvailable();
size_t SpaceAvailable();
size_t BufferSize();
status_t WaitForRead(
bigtime_t timeout = B_INFINITE_TIMEOUT);
status_t WaitForWrite(
bigtime_t timeout = B_INFINITE_TIMEOUT);
void SetWriteDisabled(bool disabled);
bool WriteDisabled();
private:
template<typename Condition>
status_t _WaitForCondition(bigtime_t timeout);
private:
pthread_mutex_t fLock;
pthread_cond_t fEvent;
uint8* fBuffer;
size_t fBufferSize;
size_t fWriteAtNext;
size_t fReadAtNext;
bool fBufferFull;
bool fWriteDisabled;
uint32 _reserved[4];
};
#endif // _MEMORY_RING_IO_H

View File

@ -51,6 +51,7 @@ for architectureObject in [ MultiArchSubDirSetup ] {
Keymap.cpp
LongAndDragTrackingFilter.cpp
md5.cpp
MemoryRingIO.cpp
MessageBuilder.cpp
NaturalCompare.cpp
PromptWindow.cpp

View File

@ -0,0 +1,333 @@
/*
* Copyright 2022 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Leorize, leorize+oss@disroot.org
*/
#include <MemoryRingIO.h>
#include <AutoLocker.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
class PThreadLocking {
public:
inline bool Lock(pthread_mutex_t* mutex)
{
return pthread_mutex_lock(mutex) == 0;
}
inline void Unlock(pthread_mutex_t* mutex)
{
pthread_mutex_unlock(mutex);
}
};
typedef AutoLocker<pthread_mutex_t, PThreadLocking> PThreadAutoLocker;
struct ReadCondition {
inline bool operator()(BMemoryRingIO &ring) {
return ring.BytesAvailable() != 0;
}
};
struct WriteCondition {
inline bool operator()(BMemoryRingIO &ring) {
return ring.SpaceAvailable() != 0;
}
};
#define RING_MASK(x) ((x) & (fBufferSize - 1))
static size_t
next_power_of_two(size_t value)
{
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
#if SIZE_MAX >= UINT64_MAX
value |= value >> 32;
#endif
value++;
return value;
}
BMemoryRingIO::BMemoryRingIO(size_t size)
:
fBuffer(NULL),
fBufferSize(0),
fWriteAtNext(0),
fReadAtNext(0),
fBufferFull(false),
fWriteDisabled(false)
{
// We avoid the use of pthread_mutexattr as it can possibly fail.
//
// The only Haiku-specific behavior that we depend on is that
// PTHREAD_MUTEX_DEFAULT mutexes check for double-locks.
pthread_mutex_init(&fLock, NULL);
pthread_cond_init(&fEvent, NULL);
SetSize(size);
}
BMemoryRingIO::~BMemoryRingIO()
{
SetSize(0);
pthread_mutex_destroy(&fLock);
pthread_cond_destroy(&fEvent);
}
status_t
BMemoryRingIO::InitCheck() const
{
if (fBufferSize == 0)
return B_NO_INIT;
return B_OK;
}
ssize_t
BMemoryRingIO::Read(void* _buffer, size_t size)
{
if (_buffer == NULL)
return B_BAD_VALUE;
if (size == 0)
return 0;
PThreadAutoLocker _(fLock);
if (!fWriteDisabled)
WaitForRead();
size = std::min(size, BytesAvailable());
uint8* buffer = reinterpret_cast<uint8*>(_buffer);
if (fReadAtNext + size < fBufferSize)
memcpy(buffer, fBuffer + fReadAtNext, size);
else {
const size_t upper = fBufferSize - fReadAtNext;
const size_t lower = size - upper;
memcpy(buffer, fBuffer + fReadAtNext, upper);
memcpy(buffer + upper, fBuffer, lower);
}
fReadAtNext = RING_MASK(fReadAtNext + size);
fBufferFull = false;
pthread_cond_signal(&fEvent);
return size;
}
ssize_t
BMemoryRingIO::Write(const void* _buffer, size_t size)
{
if (_buffer == NULL)
return B_BAD_VALUE;
if (size == 0)
return 0;
PThreadAutoLocker locker(fLock);
if (!fWriteDisabled)
WaitForWrite();
// We separate this check from WaitForWrite() as the boolean
// might have been toggled during our wait on the conditional.
if (fWriteDisabled)
return B_READ_ONLY_DEVICE;
const uint8* buffer = reinterpret_cast<const uint8*>(_buffer);
size = std::min(size, SpaceAvailable());
if (fWriteAtNext + size < fBufferSize)
memcpy(fBuffer + fWriteAtNext, buffer, size);
else {
const size_t upper = fBufferSize - fWriteAtNext;
const size_t lower = size - upper;
memcpy(fBuffer + fWriteAtNext, buffer, size);
memcpy(fBuffer, buffer + upper, lower);
}
fWriteAtNext = RING_MASK(fWriteAtNext + size);
fBufferFull = fReadAtNext == fWriteAtNext;
pthread_cond_signal(&fEvent);
return size;
}
status_t
BMemoryRingIO::SetSize(size_t _size)
{
PThreadAutoLocker locker(fLock);
const size_t size = next_power_of_two(_size);
const size_t availableBytes = BytesAvailable();
if (size < availableBytes)
return B_BAD_VALUE;
if (size == 0) {
free(fBuffer);
fBuffer = NULL;
fBufferSize = 0;
Clear(); // resets other internal counters
return B_OK;
}
uint8* newBuffer = reinterpret_cast<uint8*>(malloc(size));
if (newBuffer == NULL)
return B_NO_MEMORY;
Read(newBuffer, availableBytes);
free(fBuffer);
fBuffer = newBuffer;
fBufferSize = size;
fReadAtNext = 0;
fWriteAtNext = RING_MASK(availableBytes);
fBufferFull = fBufferSize == availableBytes;
pthread_cond_signal(&fEvent);
return B_OK;
}
void
BMemoryRingIO::Clear()
{
PThreadAutoLocker locker(fLock);
fReadAtNext = 0;
fWriteAtNext = 0;
fBufferFull = false;
}
size_t
BMemoryRingIO::BytesAvailable()
{
PThreadAutoLocker locker(fLock);
if (fWriteAtNext == fReadAtNext) {
if (fBufferFull)
return fBufferSize;
return 0;
}
return RING_MASK(fWriteAtNext - fReadAtNext);
}
size_t
BMemoryRingIO::SpaceAvailable()
{
PThreadAutoLocker locker(fLock);
return fBufferSize - BytesAvailable();
}
size_t
BMemoryRingIO::BufferSize()
{
PThreadAutoLocker locker(fLock);
return fBufferSize;
}
template<typename Condition>
status_t
BMemoryRingIO::_WaitForCondition(bigtime_t timeout)
{
PThreadAutoLocker autoLocker;
struct timespec absTimeout;
if (timeout == B_INFINITE_TIMEOUT) {
autoLocker.SetTo(fLock, false);
} else {
memset(&absTimeout, 0, sizeof(absTimeout));
bigtime_t target = system_time() + timeout;
absTimeout.tv_sec = target / 100000;
absTimeout.tv_nsec = (target % 100000) * 1000L;
int err = pthread_mutex_timedlock(&fLock, &absTimeout);
if (err == ETIMEDOUT)
return B_TIMED_OUT;
if (err != EDEADLK)
autoLocker.SetTo(fLock, true);
}
Condition cond;
while (!cond(*this)) {
if (fWriteDisabled)
return B_READ_ONLY_DEVICE;
int err = 0;
if (timeout == B_INFINITE_TIMEOUT)
err = pthread_cond_wait(&fEvent, &fLock);
else
err = pthread_cond_timedwait(&fEvent, &fLock, &absTimeout);
if (err != 0)
return err;
}
return B_OK;
}
status_t
BMemoryRingIO::WaitForRead(bigtime_t timeout)
{
return _WaitForCondition<ReadCondition>(timeout);
}
status_t
BMemoryRingIO::WaitForWrite(bigtime_t timeout)
{
return _WaitForCondition<WriteCondition>(timeout);
}
void
BMemoryRingIO::SetWriteDisabled(bool disabled)
{
PThreadAutoLocker autoLocker(fLock);
fWriteDisabled = disabled;
pthread_cond_broadcast(&fEvent);
}
bool
BMemoryRingIO::WriteDisabled()
{
PThreadAutoLocker autoLocker(fLock);
return fWriteDisabled;
}

View File

@ -15,6 +15,7 @@ UnitTestLib libsharedtest.so :
JsonToMessageTest.cpp
KeymapTest.cpp
LRUCacheTest.cpp
MemoryRingIOTest.cpp
NaturalCompareTest.cpp
: be shared bnetapi [ TargetLibstdc++ ] [ TargetLibsupc++ ]

View File

@ -0,0 +1,223 @@
/*
* Copyright 2022 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Leorize, leorize+oss@disroot.org
*/
#include "MemoryRingIOTest.h"
#include <stdio.h>
#include <string.h>
#include <OS.h>
#include <cppunit/Test.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
#include <TestUtils.h>
#include <ThreadedTestCaller.h>
#define BIG_PAYLOAD \
"a really long string that can fill the buffer multiple times"
#define FULL_PAYLOAD "16 characters x"
#define SMALL_PAYLOAD "shorter"
CppUnit::Test*
MemoryRingIOTest::Suite() {
CppUnit::TestSuite* suite = new CppUnit::TestSuite("MemoryRingIOTest");
BThreadedTestCaller<MemoryRingIOTest>* caller;
MemoryRingIOTest* big = new MemoryRingIOTest(sizeof(BIG_PAYLOAD));
caller = new BThreadedTestCaller<MemoryRingIOTest>(
"MemoryRingIOTest: RW threaded, big buffer", big);
caller->addThread("WR", &MemoryRingIOTest::WriteTest);
caller->addThread("RD", &MemoryRingIOTest::ReadTest);
suite->addTest(caller);
MemoryRingIOTest* full = new MemoryRingIOTest(sizeof(FULL_PAYLOAD));
caller = new BThreadedTestCaller<MemoryRingIOTest>(
"MemoryRingIOTest: RW threaded, medium buffer", full);
caller->addThread("WR", &MemoryRingIOTest::WriteTest);
caller->addThread("RD", &MemoryRingIOTest::ReadTest);
suite->addTest(caller);
MemoryRingIOTest* small = new MemoryRingIOTest(sizeof(SMALL_PAYLOAD));
caller = new BThreadedTestCaller<MemoryRingIOTest>(
"MemoryRingIOTest: RW threaded, small buffer", small);
caller->addThread("WR", &MemoryRingIOTest::WriteTest);
caller->addThread("RD", &MemoryRingIOTest::ReadTest);
suite->addTest(caller);
MemoryRingIOTest* endWrite = new MemoryRingIOTest(sizeof(FULL_PAYLOAD));
caller = new BThreadedTestCaller<MemoryRingIOTest>(
"MemoryRingIOTest: RW threaded, reader set end reached on writer wait",
endWrite);
caller->addThread("WR #1", &MemoryRingIOTest::BusyWriterTest);
caller->addThread("WR #2", &MemoryRingIOTest::BusyWriterTest);
caller->addThread("WR #3", &MemoryRingIOTest::BusyWriterTest);
caller->addThread("RD", &MemoryRingIOTest::_DisableWriteOnFullBuffer);
suite->addTest(caller);
MemoryRingIOTest* endRead = new MemoryRingIOTest(sizeof(FULL_PAYLOAD));
caller = new BThreadedTestCaller<MemoryRingIOTest>(
"MemoryRingIOTest: RW threaded, writer set end reached on reader wait",
endRead);
caller->addThread("RD #1", &MemoryRingIOTest::BusyReaderTest);
caller->addThread("RD #2", &MemoryRingIOTest::BusyReaderTest);
caller->addThread("RD #3", &MemoryRingIOTest::BusyReaderTest);
caller->addThread("WR", &MemoryRingIOTest::_DisableWriteOnEmptyBuffer);
suite->addTest(caller);
MemoryRingIOTest* single = new MemoryRingIOTest(0);
suite->addTest(new CppUnit::TestCaller<MemoryRingIOTest>(
"MemoryRingIOTest: RW single threaded with resizing",
&MemoryRingIOTest::ReadWriteSingleTest, single));
suite->addTest(new CppUnit::TestCaller<MemoryRingIOTest>(
"MemoryRingIOTest: Attempt to truncate buffer",
&MemoryRingIOTest::InvalidResizeTest, single));
suite->addTest(new CppUnit::TestCaller<MemoryRingIOTest>(
"MemoryRingIOTest: Wait timeout",
&MemoryRingIOTest::TimeoutTest, single));
return suite;
}
static void
ReadCheck(BMemoryRingIO& ring, const void* cmp, size_t size)
{
char* buffer = new char[size];
memset(buffer, 0, size);
size_t read;
CHK(ring.ReadExactly(buffer, size, &read) == B_OK);
CHK(read == size);
CHK(memcmp(buffer, cmp, size) == 0);
}
void
MemoryRingIOTest::WriteTest()
{
CHK(fRing.InitCheck() == B_OK);
CHK(fRing.WriteExactly(SMALL_PAYLOAD, sizeof(SMALL_PAYLOAD), NULL) == B_OK);
CHK(fRing.WriteExactly(FULL_PAYLOAD, sizeof(FULL_PAYLOAD), NULL) == B_OK);
CHK(fRing.WriteExactly(BIG_PAYLOAD, sizeof(BIG_PAYLOAD), NULL) == B_OK);
}
void
MemoryRingIOTest::ReadTest()
{
CHK(fRing.InitCheck() == B_OK);
ReadCheck(fRing, SMALL_PAYLOAD, sizeof(SMALL_PAYLOAD));
ReadCheck(fRing, FULL_PAYLOAD, sizeof(FULL_PAYLOAD));
ReadCheck(fRing, BIG_PAYLOAD, sizeof(BIG_PAYLOAD));
}
void
MemoryRingIOTest::BusyWriterTest()
{
CHK(fRing.InitCheck() == B_OK);
CHK(fRing.BufferSize() < sizeof(BIG_PAYLOAD));
CHK(fRing.WriteExactly(BIG_PAYLOAD, sizeof(BIG_PAYLOAD), NULL)
== B_DEVICE_FULL);
}
void
MemoryRingIOTest::BusyReaderTest()
{
CHK(fRing.InitCheck() == B_OK);
char buffer[100];
CHK(fRing.Read(buffer, sizeof(buffer)) == 0);
}
void
MemoryRingIOTest::ReadWriteSingleTest()
{
CHK(fRing.SetSize(sizeof(BIG_PAYLOAD)) == B_OK);
CHK(fRing.WriteExactly(BIG_PAYLOAD, sizeof(BIG_PAYLOAD)) == B_OK);
ReadCheck(fRing, BIG_PAYLOAD, sizeof(BIG_PAYLOAD));
CHK(fRing.SetSize(sizeof(FULL_PAYLOAD)) == B_OK);
// the size of FULL_PAYLOAD is a power of two, so our ring
// should be using the exact size.
CHK(fRing.BufferSize() == sizeof(FULL_PAYLOAD));
CHK(fRing.WriteExactly(FULL_PAYLOAD, sizeof(FULL_PAYLOAD)) == B_OK);
ReadCheck(fRing, FULL_PAYLOAD, sizeof(FULL_PAYLOAD));
CHK(fRing.SetSize(sizeof(SMALL_PAYLOAD)) == B_OK);
CHK(fRing.WriteExactly(SMALL_PAYLOAD, sizeof(SMALL_PAYLOAD)) == B_OK);
ReadCheck(fRing, SMALL_PAYLOAD, sizeof(SMALL_PAYLOAD));
}
void
MemoryRingIOTest::InvalidResizeTest()
{
CHK(fRing.SetSize(sizeof(FULL_PAYLOAD)) == B_OK);
CHK(fRing.WriteExactly(FULL_PAYLOAD, sizeof(FULL_PAYLOAD)) == B_OK);
CHK(fRing.SetSize(0) == B_BAD_VALUE);
}
void
MemoryRingIOTest::TimeoutTest()
{
fRing.Clear();
CHK(fRing.SetSize(0) == B_OK);
bigtime_t start = system_time();
const bigtime_t timeout = 100;
CHK(fRing.WaitForRead(timeout) == B_TIMED_OUT);
CHK(system_time() - start <= timeout + 10);
start = system_time();
CHK(fRing.WaitForWrite(timeout) == B_TIMED_OUT);
CHK(system_time() - start <= timeout + 10);
}
void
MemoryRingIOTest::_DisableWriteOnFullBuffer()
{
CHK(fRing.InitCheck() == B_OK);
while (fRing.SpaceAvailable() > 0)
fRing.WaitForRead();
/* snooze for sometime to ensure that the other thread entered
* WaitForWrite().
*/
snooze(1000);
/* this should unblock the other thread */
fRing.SetWriteDisabled(true);
}
void
MemoryRingIOTest::_DisableWriteOnEmptyBuffer()
{
CHK(fRing.InitCheck() == B_OK);
while (fRing.BytesAvailable() > 0)
fRing.WaitForWrite();
/* snooze for sometime to ensure that the other thread entered
* WaitForRead().
*/
snooze(1000);
/* this should unblock the other thread */
fRing.SetWriteDisabled(true);
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2022 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Leorize, leorize+oss@disroot.org
*/
#ifndef _MEMORY_RING_IO_TEST_H
#define _MEMORY_RING_IO_TEST_H
#include <ThreadedTestCase.h>
#include <MemoryRingIO.h>
class MemoryRingIOTest : public BThreadedTestCase
{
public:
MemoryRingIOTest(size_t bufferSize) : fRing(bufferSize) {};
static CppUnit::Test* Suite();
void WriteTest();
void ReadTest();
void BusyWriterTest();
void BusyReaderTest();
void ReadWriteSingleTest();
void InvalidResizeTest();
void TimeoutTest();
protected:
void _DisableWriteOnFullBuffer();
void _DisableWriteOnEmptyBuffer();
BMemoryRingIO fRing;
};
#endif // _MEMORY_RING_IO_TEST_H

View File

@ -55,7 +55,7 @@ UnitTestLib libsupporttest.so
MallocSeekTest.cpp
MallocWriteTest.cpp
MallocBufferLengthTest.cpp
#BString
StringTest.cpp
StringConstructionTest.cpp

View File

@ -7,6 +7,7 @@
#include "blocker/LockerTest.h"
#include "bmemoryio/MemoryIOTest.h"
#include "bmemoryio/MallocIOTest.h"
#include "bmemoryio/MemoryRingIOTest.h"
#include "bstring/StringTest.h"
#include "bblockcache/BlockCacheTest.h"
#include "ByteOrderTest.h"
@ -25,6 +26,7 @@ getTestSuite()
suite->addTest("BLocker", LockerTestSuite());
suite->addTest("BMemoryIO", MemoryIOTestSuite());
suite->addTest("BMallocIO", MallocIOTestSuite());
suite->addTest("BMemoryRingIO", MemoryRingIOTest::Suite());
suite->addTest("BString", StringTestSuite());
suite->addTest("BBlockCache", BlockCacheTestSuite());
suite->addTest("ByteOrder", ByteOrderTestSuite());