xo-alloc2: capture error info, retire exceptions in alloc path

This commit is contained in:
Roland Conybeare 2025-12-12 13:10:26 -05:00
commit 360ae4cb45
6 changed files with 103 additions and 48 deletions

View file

@ -14,6 +14,42 @@ namespace xo {
using Copaque = const void *;
using Opaque = void *;
enum class error : int32_t {
/** sentinel **/
invalid = -1,
/** not an error **/
none,
/** reserved size exhauged **/
reserve_exhausted,
/** unable to commit (i.e. mprotect failure) **/
commit_failed,
};
struct AllocatorError {
using size_type = std::size_t;
using value_type = std::byte*;
AllocatorError() = default;
explicit AllocatorError(error err) : error_{err} {}
AllocatorError(error err,
size_type req_z,
size_type com_z,
size_type rsv_z) : error_{err},
request_z_{req_z},
committed_z_{com_z},
reserved_z_{rsv_z} {}
/** error code **/
error error_ = error::none;
/** reqeust size assoc'd with errror **/
size_type request_z_ = 0;
/** committed allocator memory at time of error **/
size_type committed_z_ = 0;
/** reserved allocator memory at time of error **/
size_type reserved_z_ = 0;
};
/** @class AAllocator
* @brief Abstract facet for allocation
*
@ -41,32 +77,32 @@ namespace xo {
///@{
/** RTTI: unique id# for actual runtime data representation **/
virtual int32_t _typeseq() const = 0;
virtual int32_t _typeseq() const noexcept = 0;
/** optional name for allocator @p d
* Labeling, for diagnostics.
**/
virtual const std::string & name(Copaque d) const = 0;
virtual const std::string & name(Copaque d) const noexcept = 0;
/** reserved size in bytes for allocator @p d.
* Includes committed + uncommitted memory.
* Cannot be increased.
**/
virtual size_type reserved(Copaque d) const = 0;
virtual size_type reserved(Copaque d) const noexcept = 0;
/** Synonym for @ref committed.
* Can increase on @ref alloc
**/
virtual size_type size(Copaque d) const = 0;
virtual size_type size(Copaque d) const noexcept = 0;
/** committed size (physical addresses obtained)
* for allocator @p d.
* @ref alloc may auto-increase this
**/
virtual size_type committed(Copaque d) const = 0;
virtual size_type committed(Copaque d) const noexcept = 0;
/** unallocated (but committed) size in bytes for allocator @p d **/
virtual size_type available(Copaque d) const = 0;
virtual size_type available(Copaque d) const noexcept = 0;
/** allocated (i.e. in-use) amount in bytes for allocator @p d **/
virtual size_type allocated(Copaque d) const = 0;
virtual size_type allocated(Copaque d) const noexcept = 0;
/** true iff allocator @p d is responsible for memory at address @p p.
**/
virtual bool contains(Copaque d, const void * p) const = 0;
virtual bool contains(Copaque d, const void * p) const noexcept = 0;
/** expand committed space in arena @p d
* to size at least @p z

View file

@ -37,7 +37,7 @@ namespace xo {
* @brief represent arena allocator state
*
* Provides minimal RAII functionality around memory mapping.
* For allocation see @ref IAllocator_DArena
* For allocation implementation see @ref IAllocator_DArena
**/
struct DArena {
/*
@ -92,7 +92,7 @@ namespace xo {
ArenaConfig config_;
/** size of a VM page (obtained automatically via getpagesize()). Likely 4k **/
std::size_t page_z_ = 0;
size_type page_z_ = 0;
/** arena owns memory in range [@ref lo_, @ref hi_)
**/
@ -101,7 +101,7 @@ namespace xo {
/** prefix of this size is committed.
* Remainder mapped but uncommitted.
**/
std::size_t committed_z_ = 0;
size_type committed_z_ = 0;
/** free pointer.
* Memory in range [@ref lo_, @ref free_) current in use
@ -119,6 +119,12 @@ namespace xo {
**/
std::byte * hi_ = nullptr;
/** count runtime errors. Each error updates @ref last_error_ **/
uint32_t error_count_ = 0;
/** capture some error details if/when error **/
AllocatorError last_error_;
///@}
};

View file

@ -29,15 +29,15 @@ namespace xo {
using size_type = std::size_t;
// from AAllocator
int32_t _typeseq() const override { return s_typeseq; }
int32_t _typeseq() const noexcept override { return s_typeseq; }
[[noreturn]] const std::string & name(Copaque) const override { _fatal(); }
[[noreturn]] size_type reserved(Copaque) const override { _fatal(); }
[[noreturn]] size_type size(Copaque) const override { _fatal(); }
[[noreturn]] size_type committed(Copaque) const override { _fatal(); }
[[noreturn]] size_type available(Copaque) const override { _fatal(); }
[[noreturn]] size_type allocated(Copaque) const override { _fatal(); }
[[noreturn]] bool contains(Copaque, const void *) const override { _fatal(); }
[[noreturn]] const std::string & name(Copaque) const noexcept override { _fatal(); }
[[noreturn]] size_type reserved(Copaque) const noexcept override { _fatal(); }
[[noreturn]] size_type size(Copaque) const noexcept override { _fatal(); }
[[noreturn]] size_type committed(Copaque) const noexcept override { _fatal(); }
[[noreturn]] size_type available(Copaque) const noexcept override { _fatal(); }
[[noreturn]] size_type allocated(Copaque) const noexcept override { _fatal(); }
[[noreturn]] bool contains(Copaque, const void *) const noexcept override { _fatal(); }
[[noreturn]] bool expand(Opaque, std::size_t) const override { _fatal(); }
[[noreturn]] std::byte * alloc(Opaque, std::size_t) const override { _fatal(); }

View file

@ -24,27 +24,27 @@ namespace xo {
static DRepr & _dcast(Opaque d) { return *(DRepr *)d; }
// from AAllocator
int32_t _typeseq() const override { return s_typeseq; }
const std::string & name(Copaque d) const override {
int32_t _typeseq() const noexcept override { return s_typeseq; }
const std::string & name(Copaque d) const noexcept override {
return Impl::name(_dcast(d));
}
size_type reserved(Copaque d) const override {
size_type reserved(Copaque d) const noexcept override {
return Impl::reserved(_dcast(d));
}
size_type size(Copaque d) const override {
size_type size(Copaque d) const noexcept override {
return Impl::size(_dcast(d));
}
size_type committed(Copaque d) const override {
size_type committed(Copaque d) const noexcept override {
return Impl::committed(_dcast(d));
}
size_type available(Copaque d) const override {
size_type available(Copaque d) const noexcept override {
return I::available(_dcast(d));
}
size_type allocated(Copaque d) const override {
size_type allocated(Copaque d) const noexcept override {
return I::allocated(_dcast(d));
}
bool contains(Copaque d, const void * p) const override {
bool contains(Copaque d, const void * p) const noexcept override {
return Impl::contains(_dcast(d), p);
}

View file

@ -6,6 +6,7 @@
#include "xo/alloc2/AAllocator.hpp"
#include "xo/alloc2/DArena.hpp"
#include "xo/alloc2/padding.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <cassert>
#include <sys/mman.h> // for ::munmap()
#include <unistd.h> // for ::getpagesize()
@ -81,11 +82,8 @@ namespace xo {
// 3. assess mmap success
{
if (base == MAP_FAILED) {
assert(false);
#ifdef NOPE
throw std::runtime_error(tostr("ArenaAlloc: uncommitted allocation failed",
xtag("size", z)));
#endif
xtag("size", req_z)));
}
assert((size_t)aligned_base % hugepage_z == 0);
@ -138,11 +136,8 @@ namespace xo {
if (!lo) {
// control here implies mmap() failed silently
assert(false);
#ifdef NOPE
throw std::runtime_error(tostr("ArenaAlloc: allocation failed",
xtag("size", z)));
#endif
xtag("size", cfg.size_)));
}
size_t page_z = getpagesize();
@ -166,7 +161,9 @@ namespace xo {
committed_z_{0},
free_{lo},
limit_{lo},
hi_{hi}
hi_{hi},
error_count_{0},
last_error_{}
{
//retval.checkpoint_ = lo_;
}
@ -179,6 +176,8 @@ namespace xo {
free_ = other.free_;
limit_ = other.limit_;
hi_ = other.hi_;
error_count_ = other.error_count_;
last_error_ = other.last_error_;
other.config_ = ArenaConfig();
other.lo_ = nullptr;
@ -186,6 +185,8 @@ namespace xo {
other.free_ = nullptr;
other.limit_ = nullptr;
other.hi_ = nullptr;
other.error_count_ = 0;
other.last_error_ = AllocatorError();
}
DArena::~DArena()
@ -200,12 +201,14 @@ namespace xo {
}
// hygiene
this->lo_ = nullptr;
this->committed_z_ = 0;
lo_ = nullptr;
committed_z_ = 0;
// checkpoint_ = nullptr;
this->free_ = nullptr;
this->limit_ = nullptr;
this->hi_ = nullptr;
free_ = nullptr;
limit_ = nullptr;
hi_ = nullptr;
error_count_ = 0;
last_error_ = AllocatorError();
}
}
} /*namespace xo*/

View file

@ -66,10 +66,16 @@ namespace xo {
return true;
}
if (s.lo_ + target_z > s.hi_) {
if (s.lo_ + target_z > s.hi_) [[unlikely]] {
++(s.error_count_);
s.last_error_ = AllocatorError(error::reserve_exhausted,
target_z, s.committed_z_, reserved(s));
#ifdef OBSOLETE
throw std::runtime_error(tostr("ArenaAlloc::expand: requested size exceeds reserved size",
xtag("requested", target_z),
xtag("reserved", reserved(s))));
#endif
return false;
}
@ -107,11 +113,15 @@ namespace xo {
// xtag("add_commit_z", add_commit_z),
// xtag("commit_end", commit_start + add_commit_z));
if (::mprotect(commit_start, add_commit_z, PROT_READ | PROT_WRITE) != 0) {
assert(false);
// throw std::runtime_error(tostr("ArenaAlloc::expand: commit failure",
// xtag("committed_z", committed_z_),
// xtag("add_commit_z", add_commit_z)));
if (::mprotect(commit_start, add_commit_z, PROT_READ | PROT_WRITE) != 0) [[unlikely]] {
++(s.error_count_);
s.last_error_ = AllocatorError(error::commit_failed,
add_commit_z, s.committed_z_, reserved(s));
#ifdef OBSOLETE
throw std::runtime_error(tostr("ArenaAlloc::expand: commit failure",
xtag("committed_z", s.committed_z_),
xtag("add_commit_z", add_commit_z)));
#endif
return false;
}