diff --git a/src/util/ralloc.c b/src/util/ralloc.c index 4dfc6f27b3d..c85c61e3d8a 100644 --- a/src/util/ralloc.c +++ b/src/util/ralloc.c @@ -1032,14 +1032,28 @@ linear_alloc_child(linear_ctx *ctx, unsigned size) if (unlikely(!ptr)) return NULL; - ctx->offset = 0; - ctx->size = node_size; - ctx->latest = ptr + canary_size; #ifndef NDEBUG - linear_node_canary *canary = get_node_canary(ctx->latest); + linear_node_canary *canary = (void *) ptr; canary->magic = LMAGIC_NODE; canary->offset = 0; #endif + + /* If the new buffer is going to be full, don't update `latest` + * pointer. Either the current one is also full, so doesn't + * matter, or the current one is not full, so there's still chance + * to use that space. + */ + if (unlikely(size == node_size)) { +#ifndef NDEBUG + canary->offset = size; +#endif + assert((uintptr_t)(ptr + canary_size) % SUBALLOC_ALIGNMENT == 0); + return ptr + canary_size; + } + + ctx->offset = 0; + ctx->size = node_size; + ctx->latest = ptr + canary_size; } void *ptr = (char *)ctx->latest + ctx->offset; diff --git a/src/util/tests/linear_test.cpp b/src/util/tests/linear_test.cpp index 8fd95a38c6a..2b573184515 100644 --- a/src/util/tests/linear_test.cpp +++ b/src/util/tests/linear_test.cpp @@ -53,3 +53,20 @@ TEST(LinearAlloc, RewriteTail) ralloc_free(ctx); } + +TEST(LinearAlloc, AvoidWasteAfterLargeAlloc) +{ + void *ctx = ralloc_context(NULL); + linear_ctx *lin_ctx = linear_context(ctx); + + char *first = (char *) linear_alloc_child(lin_ctx, 32); + + /* Large allocation that would force a larger buffer. */ + linear_alloc_child(lin_ctx, 1024 * 16); + + char *second = (char *) linear_alloc_child(lin_ctx, 32); + + EXPECT_EQ(second - first, 32); + + ralloc_free(ctx); +}