diff --git a/library/modules/std/http.sx b/library/modules/std/http.sx index 5846fd8e..5802669c 100644 --- a/library/modules/std/http.sx +++ b/library/modules/std/http.sx @@ -439,21 +439,29 @@ Server :: struct { if ascii_ieq(cnv, "close") { req.keep_alive = false; } if ascii_ieq(cnv, "keep-alive") { req.keep_alive = true; } - // dispatch (the field must be loaded — `self.handler(...)` would - // be parsed as a dot-call on a function named `handler`) - h := self.handler; - resp : Response = .{}; - h(@req, @resp, self.ctx); - c.served += 1; keep := req.keep_alive and c.served < self.cfg.request_count; + // Dispatch under a per-request arena: everything the handler + // (and serialization) allocates through the implicit context + // dies with the request — response bytes survive because + // serialize copies them into own_alloc inside the push scope. // Serialize while the request views are still valid (the body // may reference the read buffer), THEN drop the served bytes — // write_more's pipelining check must see only the remainder — // and only then start sending. Overlapping copy: dst < src, so // forward byte-wise is safe. - self.serialize_response(slot, @resp, keep); + // (The handler field must be loaded — `self.handler(...)` would + // be parsed as a dot-call on a function named `handler`.) + h := self.handler; + resp : Response = .{}; + req_gpa := GPA.init(); + req_arena := Arena.init(xx req_gpa, 65536); + push Context.{ allocator = xx req_arena } { + h(@req, @resp, self.ctx); + self.serialize_response(slot, @resp, keep); + } + req_arena.deinit(); rest := c.read_len - total; m : i64 = 0; while m < rest { @@ -529,10 +537,17 @@ Server :: struct { } // A terminal error response: serialize, send, close when done. + // Same arena discipline as dispatch — serialization concats must + // not accumulate in the long-lived loop context. respond_error_close :: (self: *Server, slot: i64, status: i64) { resp : Response = .{ status = status, body = reason_for(status) }; self.conns[slot].read_len = 0; - self.serialize_response(slot, @resp, false); + err_gpa := GPA.init(); + err_arena := Arena.init(xx err_gpa, 4096); + push Context.{ allocator = xx err_arena } { + self.serialize_response(slot, @resp, false); + } + err_arena.deinit(); self.write_more(slot); } }