feat: std.http read buffers grow on demand toward read_buf_cap
read_buf_cap is now the per-request LIMIT, not a preallocation: slots start at 16K, double when full (one-step sizing when a Content-Length declares the body), and keep their grown capacity for slot reuse. At the limit the refusal distinguishes oversized headers (431) from an oversized body (413). Unblocks A1: distd accepts multi-hundred-MB artifact uploads — preallocating that per slot was never an option. examples/1633 adds a body past the initial capacity echoing intact.
This commit is contained in:
@@ -155,6 +155,34 @@ main :: () -> i32 {
|
||||
socket.close(c2);
|
||||
print("404 routing ok\n");
|
||||
|
||||
// ── 5b. a body past READ_BUF_INITIAL forces the buffer to grow ────
|
||||
big_n := 50000;
|
||||
payload : [*]u8 = xx context.allocator.alloc_bytes(xx big_n);
|
||||
f := 0;
|
||||
while f < big_n { payload[f] = 97 + cast(u8)(f % 26); f += 1; }
|
||||
breq := concat("POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: ", concat(int_to_string(big_n), "\r\n\r\n"));
|
||||
breq = concat(breq, string.{ ptr = payload, len = xx big_n });
|
||||
c2b := dial();
|
||||
if c2b < 0 { print("dial2b failed\n"); return 1; }
|
||||
bigbuf : [*]u8 = xx context.allocator.alloc_bytes(xx (big_n + 4096));
|
||||
socket.write(c2b, breq.ptr, xx breq.len);
|
||||
btotal : i64 = 0;
|
||||
btries := 0;
|
||||
while btries < 2000 {
|
||||
srv.tick(5) catch {};
|
||||
bn, bre := socket.read_nb(c2b, @bigbuf[btotal], xx (big_n + 4096 - btotal));
|
||||
if !bre { btotal += bn; }
|
||||
else if bre == error.Closed { break; }
|
||||
if resp_complete(bigbuf, btotal) { break; }
|
||||
btries += 1;
|
||||
}
|
||||
bresp := string.{ ptr = bigbuf, len = xx btotal };
|
||||
if !contains(bresp, "HTTP/1.1 200 OK") { print("case5b: big echo not 200\n"); return 1; }
|
||||
if !contains(bresp, concat("Content-Length: ", int_to_string(big_n))) { print("case5b: wrong echo length\n"); return 1; }
|
||||
if bigbuf[btotal - 1] != 97 + cast(u8)((big_n - 1) % 26) { print("case5b: tail byte wrong\n"); return 1; }
|
||||
socket.close(c2b);
|
||||
print("big body grows the buffer and echoes intact\n");
|
||||
|
||||
// ── 6. half a header is evicted at the request deadline, while a
|
||||
// healthy client keeps being served ──────────────────────────
|
||||
c3 := dial();
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -917,6 +917,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1409,6 +1409,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1425,6 +1425,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1401,6 +1401,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1427,6 +1427,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1403,6 +1403,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1401,6 +1401,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1401,6 +1401,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1401,6 +1401,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1401,6 +1401,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1401,6 +1401,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1401,6 +1401,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1401,6 +1401,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -1401,6 +1401,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -924,6 +924,9 @@ declare void @Server.run(ptr, ptr) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.accept_ready(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i1 @Server.grow_read_buf(ptr, ptr, i64, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Server.read_more(ptr, ptr, i64) #0
|
||||
|
||||
|
||||
@@ -3,5 +3,6 @@ keep-alive reuse ok
|
||||
request cap: close + EOF
|
||||
POST echo ok
|
||||
404 routing ok
|
||||
big body grows the buffer and echoes intact
|
||||
slow client evicted, healthy client served
|
||||
http server ok
|
||||
|
||||
@@ -33,12 +33,18 @@ Config :: struct {
|
||||
port: i64 = 8080;
|
||||
backlog: i32 = 128;
|
||||
max_conn: i64 = 256; // workers.max_conn
|
||||
read_buf_cap: i64 = 65536; // workers.large_buffer_size
|
||||
// MAXIMUM bytes one request (headers + body) may occupy. The
|
||||
// per-connection buffer starts at READ_BUF_INITIAL and grows on
|
||||
// demand toward this limit (a declared Content-Length sizes it in
|
||||
// one step); a grown buffer is retained for slot reuse.
|
||||
read_buf_cap: i64 = 1048576;
|
||||
timeout_request_ms: i64 = 5000; // deliver a full request, or evicted
|
||||
timeout_keepalive_ms: i64 = 5000;// idle between requests, or evicted
|
||||
request_count: i64 = 100; // requests per connection, then close
|
||||
}
|
||||
|
||||
READ_BUF_INITIAL :: 16384;
|
||||
|
||||
// One parsed request, viewed in place over the connection's read
|
||||
// buffer — valid for the duration of the handler call only.
|
||||
Request :: struct {
|
||||
@@ -125,7 +131,8 @@ CONN_KEEPALIVE :u8: 3; // between requests (deadline: keepalive)
|
||||
Conn :: struct {
|
||||
fd: i32 = -1;
|
||||
state: u8 = 0;
|
||||
read_buf: [*]u8 = null; // cap = config.read_buf_cap, reused across connections
|
||||
read_buf: [*]u8 = null; // grows toward config.read_buf_cap, reused across connections
|
||||
read_cap: i64 = 0;
|
||||
read_len: i64 = 0;
|
||||
out_buf: [*]u8 = null; // per-response allocation, freed when sent
|
||||
out_len: i64 = 0;
|
||||
@@ -296,7 +303,10 @@ Server :: struct {
|
||||
if !socket.set_nonblocking(fd) { socket.close(fd); return; }
|
||||
c := @self.conns[slot];
|
||||
if c.read_buf == null {
|
||||
c.read_buf = xx self.own_alloc.alloc_bytes(self.cfg.read_buf_cap);
|
||||
init_cap : i64 = READ_BUF_INITIAL;
|
||||
if init_cap > self.cfg.read_buf_cap { init_cap = self.cfg.read_buf_cap; }
|
||||
c.read_buf = xx self.own_alloc.alloc_bytes(init_cap);
|
||||
c.read_cap = init_cap;
|
||||
}
|
||||
c.fd = fd;
|
||||
c.state = CONN_READING;
|
||||
@@ -312,6 +322,22 @@ Server :: struct {
|
||||
|
||||
// ── read → parse → dispatch ──────────────────────────────────────
|
||||
|
||||
// Grow the slot's read buffer to at least `target` (0 = double),
|
||||
// never past cfg.read_buf_cap. False when already at the limit.
|
||||
grow_read_buf :: (self: *Server, slot: i64, target: i64) -> bool {
|
||||
c := @self.conns[slot];
|
||||
want := if target > 0 then target else c.read_cap * 2;
|
||||
if want <= c.read_cap { return true; }
|
||||
if want > self.cfg.read_buf_cap { want = self.cfg.read_buf_cap; }
|
||||
if want <= c.read_cap { return false; } // already at the limit
|
||||
nb : [*]u8 = xx self.own_alloc.alloc_bytes(want);
|
||||
if c.read_len > 0 { memcpy(nb, c.read_buf, xx c.read_len); }
|
||||
self.own_alloc.dealloc_bytes(xx c.read_buf, c.read_cap);
|
||||
c.read_buf = nb;
|
||||
c.read_cap = want;
|
||||
return true;
|
||||
}
|
||||
|
||||
read_more :: (self: *Server, slot: i64) {
|
||||
c := @self.conns[slot];
|
||||
if c.state == CONN_KEEPALIVE {
|
||||
@@ -319,12 +345,20 @@ Server :: struct {
|
||||
c.deadline = event.deadline_in(self.cfg.timeout_request_ms);
|
||||
}
|
||||
while true {
|
||||
cap := self.cfg.read_buf_cap - c.read_len;
|
||||
if cap <= 0 {
|
||||
self.respond_error_close(slot, 431);
|
||||
return;
|
||||
if c.read_len == c.read_cap {
|
||||
if !self.grow_read_buf(slot, 0) {
|
||||
// over the limit: oversized headers (431) or body (413)
|
||||
hdr_done := false;
|
||||
i : i64 = 0;
|
||||
while i + 3 < c.read_len {
|
||||
if c.read_buf[i] == 13 and c.read_buf[i+1] == 10 and c.read_buf[i+2] == 13 and c.read_buf[i+3] == 10 { hdr_done = true; break; }
|
||||
i += 1;
|
||||
}
|
||||
self.respond_error_close(slot, if hdr_done then 413 else 431);
|
||||
return;
|
||||
}
|
||||
}
|
||||
nq, re := socket.read_nb(c.fd, @c.read_buf[c.read_len], xx cap);
|
||||
nq, re := socket.read_nb(c.fd, @c.read_buf[c.read_len], xx (c.read_cap - c.read_len));
|
||||
if re == error.WouldBlock { break; }
|
||||
if re { // Closed or Fault
|
||||
self.conn_close(slot);
|
||||
@@ -392,7 +426,11 @@ Server :: struct {
|
||||
}
|
||||
total := xx he + 4 + clen;
|
||||
if total > self.cfg.read_buf_cap { self.respond_error_close(slot, 413); return false; }
|
||||
if c.read_len < total { return false; }
|
||||
if c.read_len < total {
|
||||
// size the buffer for the declared body in one step
|
||||
self.grow_read_buf(slot, total);
|
||||
return false;
|
||||
}
|
||||
req.body = string.{ ptr = @c.read_buf[he + 4], len = xx clen };
|
||||
|
||||
// keep-alive: 1.1 default on, 1.0 default off, header overrides
|
||||
|
||||
Reference in New Issue
Block a user