xo-arena: CircularBuffer utest + bugfixes

This commit is contained in:
Roland Conybeare 2026-01-11 16:54:30 -05:00
commit 516b57ae81
6 changed files with 114 additions and 10 deletions

View file

@ -20,6 +20,7 @@ namespace xo {
/** optional name, for diagnostics **/
std::string name_;
/** hard maximum buffer size = reserved virtual memory.
* However actual max will be this value rounded up to at least page size.
* Buffer will generally map much less than this amount of memory
**/
std::size_t max_capacity_ = 0;

View file

@ -79,6 +79,7 @@ namespace xo {
const_span_type reserved_range() const noexcept { return reserved_range_; }
const_span_type mapped_range() const noexcept { return mapped_range_; }
const_span_type occupied_range() const noexcept { return occupied_range_; }
const_span_type input_range() const noexcept { return input_range_; }
/** verify DCircularBuffer invariants.
* Act on failure according to policy @p p
@ -98,6 +99,8 @@ namespace xo {
/** @defgroup mm-circularbuffer-nonconst-methods CircularBuffer non-const methods **/
///@{
span_type input_range() noexcept { return input_range_; }
/** copy memory in span @p r into buffer starting at the end of
* @ref occupied_range_. Map new physical memory as needed.
* On success returns empty suffix of @p r.
@ -139,7 +142,7 @@ namespace xo {
* Caller represents that it won't need to read this memory again
* unless overlaps with a pinned span.
**/
void consume(span_type r);
void consume(const_span_type input);
/** pin memory range @p r. circular buffer will not touch
* addresses that appear in any pinned range.

View file

@ -28,6 +28,9 @@ namespace xo {
/** typealias for span size (in units of CharT) **/
using size_type = std::uint64_t;
/** typealias for span elements **/
using value_type = CharT;
///@}
public:
@ -58,7 +61,8 @@ namespace xo {
* A null span can be concatenated with any other span
* without triggering matching-endpoint asserts.
**/
static span make_null() { return span(static_cast<CharT*>(nullptr), static_cast<CharT*>(nullptr)); }
static span make_null() { return span(static_cast<CharT*>(nullptr),
static_cast<CharT*>(nullptr)); }
/** @brief create span for C-style string @p cstr **/
static span from_cstr(const CharT * cstr) {
@ -76,6 +80,14 @@ namespace xo {
return span(lo, hi);
}
/** @brief create span from std::string @p str **/
static span from_string_view(const std::string_view & sv) {
CharT * lo = &(*sv.begin());
CharT * hi = &(*sv.end());
return span(lo, hi);
}
/** @brief concatenate two contiguous spans */
static span concat(const span & span1, const span & span2) {
if (span1.is_null())
@ -119,6 +131,11 @@ namespace xo {
return (other.lo() <= lo_) && (hi_ <= other.hi());
}
/** convert to string view **/
std::string_view to_string_view() const {
return std::string_view((const char *)lo_, (const char *)hi_);
}
///@}
/** @defgroup span-general-methods **/

View file

@ -10,6 +10,9 @@
#include <sys/mman.h>
namespace xo {
using xo::print::operator<<;
using xo::print::printspan;
namespace mm {
DCircularBuffer::DCircularBuffer(DCircularBuffer && other)
@ -152,6 +155,7 @@ namespace xo {
::memcpy(occupied_range_.hi(), src.lo(), copy_z);
this->occupied_range_ += span_type(dest.lo(), copy_z);
this->input_range_ += span_type(dest.lo(), copy_z);
return src.after_prefix(copy_z);
}
@ -198,30 +202,49 @@ namespace xo {
}
void
DCircularBuffer::consume(span_type r)
DCircularBuffer::consume(const_span_type input)
{
if (r.lo() != input_range_.lo()) {
scope log(XO_DEBUG(false), xtag("input", input.to_string_view()));
if (input.lo() != input_range_.lo()) {
assert(false);
return;
}
if (r.hi() > occupied_range_.hi()) {
if (input.hi() > occupied_range_.hi()) {
assert(false);
return;
}
if (occupied_range_.lo() < input_range_.lo()) {
log && log("pinned range prevents shrinking occupied range");
/* here: a pinned range prevents shrinking occupied_range */
this->input_range_ = input_range_.suffix_from(r.hi());
this->input_range_
= input_range_.suffix_from((span_type::value_type *)input.hi());
} else {
log && log(xtag("msg", "will shrink occupied range"),
xtag("input.lo", (void*)input.lo()),
xtag("input.hi", (void*)input.hi()),
xtag("stored.lo", (void*)input_range_.lo()),
xtag("stored.hi", (void*)input_range_.hi())
);
/* here: input; recompute occupied boundary */
this->input_range_ = input_range_.suffix_from(r.hi());
this->input_range_
= input_range_.suffix_from((span_type::value_type *)input.hi());
log && log(xtag("occupied", occupied_range_.size()),
xtag("input", input_range_.size()));
this->_shrink_occupied_to_fit();
log && log(xtag("occupied", occupied_range_.size()),
xtag("input", input_range_.size()));
}
this->_check_reset_map_start();

View file

@ -8,9 +8,8 @@ set(UTEST_SRCS
DArena.test.cpp
DArenaVector.test.cpp
DArenaHashMap.test.cpp
DCircularBuffer.test.cpp
# DArenaIterator.test.cpp
# Collector.test.cpp
# DX1CollectorIterator.test.cpp
# random_allocs.cpp
)

View file

@ -0,0 +1,61 @@
/** @file DCircularBuffer.test.cpp
*
* @author Roland Conybeare, Jan 2026
**/
#include "DCircularBuffer.hpp"
#include "print.hpp"
#include <xo/indentlog/print/tag.hpp>
#include <catch2/catch.hpp>
namespace xo {
using xo::mm::DCircularBuffer;
using xo::mm::CircularBufferConfig;
using xo::mm::span;
using std::byte;
namespace ut {
TEST_CASE("DCircularBuffer-tiny", "[arena][DCircularBuffer]")
{
// buffer works with bytes, not chars
CircularBufferConfig cfg { .name_ = "testcbuf",
.max_capacity_ = 1 };
DCircularBuffer buf = DCircularBuffer::map(cfg);
REQUIRE(buf.reserved_range().size() == getpagesize());
REQUIRE(buf.mapped_range().size() == 0);
REQUIRE(buf.occupied_range().size() == 0);
REQUIRE(buf.input_range().size() == 0);
REQUIRE(buf.verify_ok(verify_policy::log_only()));
REQUIRE(buf.get_append_span(1).size() == getpagesize());
REQUIRE(buf.mapped_range().size() == getpagesize());
REQUIRE(buf.occupied_range().size() == 0);
REQUIRE(buf.input_range().size() == 0);
std::string_view s0 = "abcdefghijk";
/* return value is unaccepted suffix of input */
REQUIRE(buf.append(DCircularBuffer::span_type((byte *)s0.begin(),
(byte *)s0.end())).empty());
REQUIRE(buf.verify_ok(verify_policy::log_only()));
REQUIRE(buf.mapped_range().size() == getpagesize());
REQUIRE(buf.occupied_range().size() == s0.size());
REQUIRE(buf.input_range().size() == s0.size());
std::string_view s1 = "lmnopq";
REQUIRE(buf.append(DCircularBuffer::span_type((byte *)s1.begin(),
(byte *)s1.end())).empty());
REQUIRE(buf.mapped_range().size() == getpagesize());
REQUIRE(buf.occupied_range().size() == s0.size() + s1.size());
REQUIRE(buf.occupied_range().to_string_view() == std::string_view("abcdefghijklmnopq"));
buf.consume(buf.occupied_range().prefix(3));
REQUIRE(buf.occupied_range().to_string_view() == std::string_view("defghijklmnopq"));
}
} /*namespace ut*/
} /*namespace xo*/
/* end DCircularBuffer.test.cpp */