From 56f1b6cc19dfe1768184134514e263c9428fb5a1 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 27 Aug 2025 08:01:25 +0800 Subject: [PATCH 1/4] Make the CDP read buffer heap allocated & dynamic Rather than stack-allocating MAX_MESSAGE_SIZE upfront, we now allocate 32KB and grow the buffer as needed for larger messages, up to MAX_MESSAGE_SIZE. This will reduce memory usage for drivers that don't send huge payloads (like playwright does). While not implemented, this would also enable us to set the MAX_MESSAGE_SIZE at runtime (e.g. via a command line option). --- src/server.zig | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/server.zig b/src/server.zig index 617be02b..47aae295 100644 --- a/src/server.zig +++ b/src/server.zig @@ -36,7 +36,7 @@ const MAX_HTTP_REQUEST_SIZE = 4096; // max message size // +14 for max websocket payload overhead // +140 for the max control packet that might be interleaved in a message -const MAX_MESSAGE_SIZE = 512 * 1024 + 14; +const MAX_MESSAGE_SIZE = 512 * 1024 + 14 + 140; pub const Server = struct { app: *App, @@ -188,12 +188,15 @@ pub const Client = struct { // we expect the socket to come to us as nonblocking std.debug.assert(socket_flags & nonblocking == nonblocking); + var reader = try Reader(true).init(server.allocator); + errdefer reader.deinit(); + return .{ .socket = socket, .server = server, + .reader = reader, .mode = .{ .http = {} }, .socket_flags = socket_flags, - .reader = .{ .allocator = server.allocator }, .send_arena = ArenaAllocator.init(server.allocator), }; } @@ -537,14 +540,23 @@ fn Reader(comptime EXPECT_MASK: bool) type { // we add 140 to allow 1 control message (ping/pong/close) to be // fragmented into a normal message. - buf: [MAX_MESSAGE_SIZE + 140]u8 = undefined, + buf: []u8, fragments: ?Fragments = null, const Self = @This(); + fn init(allocator: Allocator) !Self { + const buf = try allocator.alloc(u8, 32 * 1024); + return .{ + .buf = buf, + .allocator = allocator, + }; + } + fn deinit(self: *Self) void { self.cleanup(); + self.allocator.free(self.buf); } fn cleanup(self: *Self) void { @@ -613,9 +625,17 @@ fn Reader(comptime EXPECT_MASK: bool) type { } } else if (message_len > MAX_MESSAGE_SIZE) { return error.TooLarge; - } - - if (buf.len < message_len) { + } else if (message_len > self.buf.len) { + const new_buf = try self.allocator.alloc(u8, message_len); + @memcpy(new_buf[0..buf.len], buf); + self.allocator.free(self.buf); + self.buf = new_buf; + self.len = buf.len; + buf = new_buf[0..buf.len]; + // we need more data + return null; + } else if (buf.len < message_len) { + // we need more data return null; } @@ -753,7 +773,7 @@ fn Reader(comptime EXPECT_MASK: bool) type { // We're here because we either don't have enough bytes of the next // message, or we know that it won't fit in our buffer as-is. - std.mem.copyForwards(u8, &self.buf, partial); + std.mem.copyForwards(u8, self.buf, partial); self.pos = 0; self.len = partial_bytes; } From 74d90f2892520ae9da4fe3d4e715f593b227e637 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 27 Aug 2025 08:25:53 +0800 Subject: [PATCH 2/4] fix tests --- src/server.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/server.zig b/src/server.zig index 47aae295..1d4b9ec5 100644 --- a/src/server.zig +++ b/src/server.zig @@ -1057,8 +1057,8 @@ test "Client: read invalid websocket message" { ); } - // length of message is 0000 0401, i.e: 1024 * 512 + 1 - try assertWebSocketError(1009, &.{ 129, 255, 0, 0, 0, 0, 0, 8, 0, 1, 'm', 'a', 's', 'k' }); + // length of message is 0000 0810, i.e: 1024 * 512 + 265 + try assertWebSocketError(1009, &.{ 129, 255, 0, 0, 0, 0, 0, 8, 1, 0, 'm', 'a', 's', 'k' }); // continuation type message must come after a normal message // even when not a fin frame @@ -1280,7 +1280,10 @@ fn createTestClient() !TestClient { try posix.setsockopt(stream.handle, posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout); return .{ .stream = stream, - .reader = .{ .allocator = testing.allocator }, + .reader = .{ + .allocator = testing.allocator, + .buf = try testing.allocator.alloc(u8, 1024 * 16), + }, }; } From efc983b009e5188e3a762c5cffd026a0544bbc6d Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 29 Aug 2025 10:33:27 +0800 Subject: [PATCH 3/4] Start with 16K buffer (down from 32K). Use array list growth algorithm --- src/server.zig | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/server.zig b/src/server.zig index 1d4b9ec5..fdcd5f95 100644 --- a/src/server.zig +++ b/src/server.zig @@ -547,7 +547,7 @@ fn Reader(comptime EXPECT_MASK: bool) type { const Self = @This(); fn init(allocator: Allocator) !Self { - const buf = try allocator.alloc(u8, 32 * 1024); + const buf = try allocator.alloc(u8, 16 * 1024); return .{ .buf = buf, .allocator = allocator, @@ -626,12 +626,9 @@ fn Reader(comptime EXPECT_MASK: bool) type { } else if (message_len > MAX_MESSAGE_SIZE) { return error.TooLarge; } else if (message_len > self.buf.len) { - const new_buf = try self.allocator.alloc(u8, message_len); - @memcpy(new_buf[0..buf.len], buf); - self.allocator.free(self.buf); - self.buf = new_buf; - self.len = buf.len; - buf = new_buf[0..buf.len]; + const len = self.buf.len; + self.buf = try growBuffer(self.allocator, self.buf, message_len); + buf = self.buf[0..len]; // we need more data return null; } else if (buf.len < message_len) { @@ -780,6 +777,23 @@ fn Reader(comptime EXPECT_MASK: bool) type { }; } +fn growBuffer(allocator: Allocator, buf: []u8, required_capacity: usize) ![]u8 { + // from std.ArrayList + var new_capacity = buf.len; + while (true) { + new_capacity +|= new_capacity / 2 + 8; + if (new_capacity >= required_capacity) break; + } + + if (allocator.resize(buf, new_capacity)) { + return buf.ptr[0..new_capacity]; + } + const new_buffer = try allocator.alloc(u8, new_capacity); + @memcpy(new_buffer[0..buf.len], buf); + allocator.free(buf); + return new_buffer; +} + const Fragments = struct { type: Message.Type, message: std.ArrayListUnmanaged(u8), From 1ebac06f4b43beb3d0a275d3c15c6687f410779a Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 29 Aug 2025 10:55:36 +0800 Subject: [PATCH 4/4] add debug line on cdp buffer growth --- src/server.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server.zig b/src/server.zig index fdcd5f95..39fd6907 100644 --- a/src/server.zig +++ b/src/server.zig @@ -785,6 +785,8 @@ fn growBuffer(allocator: Allocator, buf: []u8, required_capacity: usize) ![]u8 { if (new_capacity >= required_capacity) break; } + log.debug(.app, "CDP buffer growth", .{ .from = buf.len, .to = new_capacity }); + if (allocator.resize(buf, new_capacity)) { return buf.ptr[0..new_capacity]; }