Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module(
name = "cpp_toolbelt",
version = "2.1.5",
version = "2.1.6",
)

bazel_dep(name = "platforms", version = "1.0.0")
Expand Down
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -833,12 +833,14 @@ struct PayloadBuffer {

// Vector operations
template <typename T>
static void VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v,
static bool VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v,
bool enable_small_block = true);
template <typename T>
static void VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n,
static bool VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n,
bool enable_small_block = true);
template <typename T>
static bool VectorResize(PayloadBuffer **self, VectorHeader *hdr, size_t n);
template <typename T>
T VectorGet(const VectorHeader *hdr, size_t index) const;

// Address conversion
Expand All @@ -852,9 +854,26 @@ struct PayloadBuffer {
void Dump(std::ostream &os);
bool IsValidMagic() const;
bool IsMoveable() const;
bool AllocationFailed() const;
};
```

#### Fixed-size buffers that run out of space

When a fixed-size buffer has no room for an allocation, the allocating
functions report it and leave the buffer's existing contents unchanged:
`Allocate`, `Realloc`, `SetString`, `AllocateMainMessage` and `NewMessage`
return `nullptr`, `AllocateString` returns an empty span, and
`AllocateMetadata`, `VectorPush`, `VectorReserve` and `VectorResize` return
`false`. The buffer also records the failure in its magic word
(`kAllocationFailedFlag`), and `AllocationFailed()` reports it. The writer can
keep making smaller allocations, while a reader that checks the magic using
`kBitMapMask` treats the incomplete buffer as invalid.

With bitmaps enabled, a small allocation that has no room for a new bitmap run
comes from the free list instead, so a small fixed-size buffer can use all of
its space. `VectorResize` sets the elements it adds to zero.

#### Example

```cpp
Expand Down
53 changes: 45 additions & 8 deletions toolbelt/payload_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,22 @@ inline int BitmapRunIndexFromEncodedSize(uint32_t n) {

void *PayloadBuffer::AllocateMainMessage(PayloadBuffer **self, size_t size) {
void *msg = Allocate(self, size, 8, true);
if (msg == nullptr) {
return nullptr;
}
(*self)->message = (*self)->ToOffset(msg);
return msg;
}

void PayloadBuffer::AllocateMetadata(PayloadBuffer **self, void *md,
bool PayloadBuffer::AllocateMetadata(PayloadBuffer **self, void *md,
size_t size) {
void *m = Allocate(self, size, 1, false);
if (m == nullptr) {
return false;
}
memcpy(m, md, size);
(*self)->metadata = (*self)->ToOffset(m);
return true;
}

char *PayloadBuffer::SetString(PayloadBuffer **self, const char *s, size_t len,
Expand All @@ -66,6 +73,9 @@ char *PayloadBuffer::SetString(PayloadBuffer **self, const char *s, size_t len,
} else {
str = Allocate(self, len + 4, 4, false);
}
if (str == nullptr) {
return nullptr;
}
uint32_t *p = reinterpret_cast<uint32_t *>(str);
p[0] = uint32_t(len);
memcpy(p + 1, s, len);
Expand Down Expand Up @@ -207,6 +217,9 @@ absl::Span<char> PayloadBuffer::AllocateString(PayloadBuffer **self, size_t len,
} else {
str = Allocate(self, len + 4, 4, clear);
}
if (str == nullptr) {
return {};
}
uint32_t *p = reinterpret_cast<uint32_t *>(str);
p[0] = uint32_t(len);

Expand All @@ -225,6 +238,9 @@ void PayloadBuffer::Dump(std::ostream &os) {
<< std::endl;
os << " bitmaps: " << ((magic & kBitMapFlag) ? "enabled" : "disabled")
<< std::endl;
if (AllocationFailed()) {
os << " allocation failed" << std::endl;
}
os << " hwm: " << hwm << " " << ToAddress(hwm) << std::endl;
os << " full_size: " << full_size << std::endl;
os << " metadata: " << metadata << " " << ToAddress(metadata) << std::endl;
Expand Down Expand Up @@ -336,6 +352,7 @@ void *PayloadBuffer::Allocate(PayloadBuffer **buffer, uint32_t n,
Resizer *resizer = (*buffer)->GetResizer();
if (resizer == nullptr) {
// Really out of memory.
(*buffer)->magic |= kAllocationFailedFlag;
return nullptr;
}
size_t old_size = (*buffer)->full_size;
Expand Down Expand Up @@ -769,7 +786,11 @@ bool PayloadBuffer::PrimeBitmapAllocator(PayloadBuffer **self, size_t size) {
}
// Re-derive hdr since AllocateBitMapRun may have triggered a buffer resize.
VectorHeader *hdr = (*self)->ToAddress<VectorHeader>((*self)->bitmaps[index]);
(*self)->VectorPush<BufferOffset>(self, hdr, (*self)->ToOffset(run), false);
if (!(*self)->VectorPush<BufferOffset>(self, hdr, (*self)->ToOffset(run),
false)) {
(*self)->Free(run);
return false;
}
return true;
}

Expand All @@ -782,9 +803,13 @@ BufferOffset PayloadBuffer::AllocateBitMapRunVector(PayloadBuffer **self) {
}
BufferOffset hdr_offset = (*self)->ToOffset(hdr);

// Preallocate space for 8 elements.
VectorReserve<BufferOffset>(self, reinterpret_cast<VectorHeader *>(hdr), 8,
false);
// Preallocate space for 8 elements. This is only an optimization so a
// failure leaves the allocation-failed flag as it was.
const uint32_t failed = (*self)->magic & kAllocationFailedFlag;
if (!VectorReserve<BufferOffset>(self, reinterpret_cast<VectorHeader *>(hdr),
8, false)) {
(*self)->magic = ((*self)->magic & ~kAllocationFailedFlag) | failed;
}
return hdr_offset;
}

Expand Down Expand Up @@ -860,7 +885,11 @@ void *BitMapRun::Allocate(PayloadBuffer **pb, int index, uint32_t, int size,
// Re-derive hdr since AllocateBitMapRun may have triggered a buffer
// resize, invalidating the previous pointer.
hdr = (*pb)->ToAddress<VectorHeader>((*pb)->bitmaps[index]);
(*pb)->VectorPush<BufferOffset>(pb, hdr, (*pb)->ToOffset(run), false);
if (!(*pb)->VectorPush<BufferOffset>(pb, hdr, (*pb)->ToOffset(run),
false)) {
(*pb)->Free(run);
return nullptr;
}
}
}

Expand All @@ -879,8 +908,16 @@ void BitMapRun::Free(PayloadBuffer *pb, int index, int bitmap_index,

void *PayloadBuffer::AllocateSmallBlock(PayloadBuffer **pb, uint32_t size,
int index, bool clear) {
return BitMapRun::Allocate(pb, index, size, bitmp_run_infos[index].size,
bitmp_run_infos[index].num, clear);
// A new run holds many blocks, so a fixed size buffer may have room for the
// block but not for the run. The block then comes from the free list.
const uint32_t failed = (*pb)->magic & kAllocationFailedFlag;
void *addr = BitMapRun::Allocate(pb, index, size, bitmp_run_infos[index].size,
bitmp_run_infos[index].num, clear);
if (addr != nullptr) {
return addr;
}
(*pb)->magic = ((*pb)->magic & ~kAllocationFailedFlag) | failed;
return Allocate(pb, size, clear, false);
}

void PayloadBuffer::FreeSmallBlock(PayloadBuffer *pb, int index,
Expand Down
Loading
Loading