2121// on the warm path -- after the first request grew the arena, the
2222// second request's upstream alloc count stays flat.
2323
24+ #include < algorithm>
2425#include < cstddef>
2526#include < cstdint>
2627#include < cstring>
@@ -64,6 +65,10 @@ LT_END_SUITE(http_request_arena_suite)
6465// (1) connection_state must own a std::pmr::monotonic_buffer_resource named
6566// arena_, and arena_.release() must rewind the bump pointer so a second
6667// allocation lands at the same address as the first.
68+ //
69+ // Note: impl_address_reuse_after_release (test 3) extends this contract
70+ // to http_request_impl construction, so the split between these two
71+ // tests is intentional rather than redundant. (test-quality-reviewer-iter1-5)
6772LT_BEGIN_AUTO_TEST (http_request_arena_suite, arena_release_resets_bump_pointer)
6873 httpserver::detail::connection_state cs;
6974
@@ -94,7 +99,13 @@ LT_BEGIN_AUTO_TEST(http_request_arena_suite, warm_path_zero_upstream_allocs)
9499 using httpserver::detail::http_request_impl;
95100 using impl_alloc_t = std::pmr::polymorphic_allocator<http_request_impl>;
96101
97- // First request: grows the arena (consumes some of the 8 KiB).
102+ // First request (cold path): grows the arena (consumes some of the 8 KiB).
103+ // The baseline is sampled AFTER the cold cycle so the warm-path assertion
104+ // below measures only warm-path allocations. If the 8 KiB buffer is too
105+ // small for the cold cycle, the cold cycle will spill to upstream but
106+ // baseline will be non-zero, and the warm-path assertion would become
107+ // vacuous. The warm_path_zero_upstream_allocs_with_containers test (7)
108+ // asserts baseline == 0 there to guard against this. (test-quality-iter1-6)
98109 {
99110 impl_alloc_t alloc (&arena);
100111 auto * p = alloc.new_object <http_request_impl>(nullptr , nullptr , alloc);
@@ -244,14 +255,9 @@ LT_BEGIN_AUTO_TEST(http_request_arena_suite, reset_arena_clears_initial_buffer)
244255 cs.reset_arena();
245256
246257 // After reset, the bytes at that location must be zero.
247- bool all_zero = true ;
248- for (std::size_t i = 0 ; i < sentinel_size; ++i) {
249- if (alloc_ptr[i] != std::byte{0 }) {
250- all_zero = false ;
251- break ;
252- }
253- }
254- LT_CHECK (all_zero);
258+ // (test-quality-reviewer-iter1-4: use std::all_of for a cleaner assertion)
259+ LT_CHECK (std::all_of(alloc_ptr, alloc_ptr + sentinel_size,
260+ [](std::byte b) { return b == std::byte{0 }; }));
255261
256262 // Verify the arena is also released (bump pointer rewound): a new
257263 // allocation of the same size must land at the same address.
@@ -268,6 +274,13 @@ LT_END_AUTO_TEST(reset_arena_clears_initial_buffer)
268274// test only exercised construction with a null connection (no container
269275// population), so it did not validate the acceptance criterion for a
270276// request that actually populates the arena-backed containers.
277+ //
278+ // code-quality-reviewer-iter1-3 / spec-alignment-checker-iter1-7:
279+ // The baseline is asserted to be zero to confirm the cold cycle itself
280+ // fit within the 8 KiB initial buffer (no spill to upstream). If the
281+ // buffer is ever too small, the cold-cycle spill would be silently
282+ // included in the baseline and the warm-path assertion would still
283+ // pass -- making the test vacuous.
271284LT_BEGIN_AUTO_TEST (http_request_arena_suite, warm_path_zero_upstream_allocs_with_containers)
272285 assert_no_upstream_resource upstream;
273286
@@ -307,13 +320,69 @@ LT_BEGIN_AUTO_TEST(http_request_arena_suite, warm_path_zero_upstream_allocs_with
307320 arena.release();
308321 const std::size_t baseline = upstream.upstream_alloc_count();
309322
323+ // Baseline must be zero: the 8 KiB initial buffer must be sufficient to
324+ // hold all cold-cycle allocations. If this fails, the buffer is too small
325+ // and the warm-path assertion below would be vacuous (the "baseline" would
326+ // absorb cold-cycle spills and the warm path would appear zero-delta even
327+ // if it also spills). (code-quality-reviewer-iter1-3)
328+ LT_CHECK_EQ (baseline, std::size_t {0 });
329+
310330 // Warm cycle: reuses the arena's initial buffer -- upstream must stay flat.
311331 one_request_cycle ();
312332 arena.release();
313333
314334 LT_CHECK_EQ (upstream.upstream_alloc_count(), baseline);
315335LT_END_AUTO_TEST (warm_path_zero_upstream_allocs_with_containers)
316336
337+ // (6) Simulate the request_completed trampoline contract: destroy the
338+ // arena-backed impl (running its destructor), then call reset_arena()
339+ // on the connection_state, and verify the arena is ready for reuse at
340+ // the same address. This tests the two-step sequence in
341+ // webserver_impl::request_completed without requiring a live MHD daemon.
342+ //
343+ // Acceptance criterion: 'MHD_RequestTerminationCode callback resets
344+ // the arena (test)'. The structural tests (1) and (3) verify the
345+ // bump-pointer rewind property; this test verifies the ordering contract
346+ // between ~http_request_impl (step 1) and reset_arena() (step 2) by
347+ // running them in the same order request_completed does.
348+ // (code-quality-reviewer-iter1-2 / test-quality-reviewer-iter1-1)
349+ LT_BEGIN_AUTO_TEST (http_request_arena_suite, request_completed_trampoline_contract)
350+ using httpserver::detail::http_request_impl;
351+ using impl_alloc_t = std::pmr::polymorphic_allocator<http_request_impl>;
352+
353+ httpserver::detail::connection_state cs;
354+
355+ // Step 1 (mirrors request_completed): allocate and construct an
356+ // http_request_impl from the connection_state's arena, populate
357+ // a couple of args to give it live PMR state, then call its
358+ // destructor via delete_object (mirrors the arena_deleter: destructor
359+ // only, no deallocation). This must not touch the arena bump pointer.
360+ impl_alloc_t alloc (&cs.arena_);
361+ auto * p = alloc.new_object<http_request_impl>(nullptr , nullptr , alloc);
362+
363+ constexpr std::size_t limit = 1024 ;
364+ p->set_arg (" user" , " alice" , limit);
365+ p->querystring = " ?user=alice" ;
366+ p->requestor_ip = " 10.0.0.1" ;
367+
368+ // Capture the address before destruction so we can verify reuse.
369+ const void * const first_addr = static_cast <void *>(p);
370+
371+ // Destroy the impl without deallocating (mirrors destroy_impl_arena).
372+ p->~http_request_impl ();
373+
374+ // Step 2 (mirrors request_completed): rewind and zero the arena.
375+ cs.reset_arena();
376+
377+ // After reset, a new allocation of the same type must land at the same
378+ // address (bump pointer rewound to the start of the initial buffer).
379+ auto * p2 = alloc.new_object<http_request_impl>(nullptr , nullptr , alloc);
380+ const void * const second_addr = static_cast <void *>(p2);
381+ alloc.delete_object(p2);
382+
383+ LT_CHECK_EQ (first_addr, second_addr);
384+ LT_END_AUTO_TEST (request_completed_trampoline_contract)
385+
317386LT_BEGIN_AUTO_TEST_ENV ()
318387 AUTORUN_TESTS ()
319388LT_END_AUTO_TEST_ENV()
0 commit comments