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:
parent
fb1d7157d2
commit
86fa1c21e1
247
docs/user/shared/MemoryRingIO.dox
Normal file
247
docs/user/shared/MemoryRingIO.dox
Normal 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.
|
||||
*/
|
60
headers/private/shared/MemoryRingIO.h
Normal file
60
headers/private/shared/MemoryRingIO.h
Normal 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
|
@ -51,6 +51,7 @@ for architectureObject in [ MultiArchSubDirSetup ] {
|
||||
Keymap.cpp
|
||||
LongAndDragTrackingFilter.cpp
|
||||
md5.cpp
|
||||
MemoryRingIO.cpp
|
||||
MessageBuilder.cpp
|
||||
NaturalCompare.cpp
|
||||
PromptWindow.cpp
|
||||
|
333
src/kits/shared/MemoryRingIO.cpp
Normal file
333
src/kits/shared/MemoryRingIO.cpp
Normal 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;
|
||||
}
|
@ -15,6 +15,7 @@ UnitTestLib libsharedtest.so :
|
||||
JsonToMessageTest.cpp
|
||||
KeymapTest.cpp
|
||||
LRUCacheTest.cpp
|
||||
MemoryRingIOTest.cpp
|
||||
NaturalCompareTest.cpp
|
||||
|
||||
: be shared bnetapi [ TargetLibstdc++ ] [ TargetLibsupc++ ]
|
||||
|
223
src/tests/kits/shared/MemoryRingIOTest.cpp
Normal file
223
src/tests/kits/shared/MemoryRingIOTest.cpp
Normal 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);
|
||||
}
|
38
src/tests/kits/shared/MemoryRingIOTest.h
Normal file
38
src/tests/kits/shared/MemoryRingIOTest.h
Normal 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
|
@ -55,7 +55,7 @@ UnitTestLib libsupporttest.so
|
||||
MallocSeekTest.cpp
|
||||
MallocWriteTest.cpp
|
||||
MallocBufferLengthTest.cpp
|
||||
|
||||
|
||||
#BString
|
||||
StringTest.cpp
|
||||
StringConstructionTest.cpp
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user