server: a query that finds every permit taken waits for one instead of failing, the budget is 64, permit and upstream turns wait on a futex with one thread each, the thread pool has no task limit, and transparent huge pages are off for the process

This commit is contained in:
2026-10-22 20:26:25 +02:00
parent 63cc5f8dc3
commit 91ed5ae774
26 changed files with 1114 additions and 305 deletions
+137 -12
View File
@@ -527,9 +527,21 @@ fn askOn(reader: *std.Io.Reader, writer: *std.Io.Writer) ![]const u8 {
return reader.take(len);
}
fn waitForWaiters(permits: *query_budget.QueryBudget, io: std.Io, count: u64) !void {
const tick: std.Io.Clock.Duration = .{ .raw = .fromMilliseconds(1), .clock = .awake };
while (permits.snapshot().waiting != count) try tick.sleep(io);
}
fn sendQuery(writer: *std.Io.Writer) !void {
try writer.writeAll(&transport.framePrefix(@intCast(query_bytes.len)));
try writer.writeAll(query_bytes);
try writer.flush();
}
/// One answered query, then the connection idles with every permit back in the
/// budget; a second query that finds none taken ends the connection unanswered.
fn keepAliveThenStarved(io: std.Io, address: net.IpAddress, permits: *query_budget.QueryBudget) anyerror!void {
/// budget; a second query that finds every permit taken waits, and is answered
/// once one is released.
fn keepAliveThenWait(io: std.Io, address: net.IpAddress, permits: *query_budget.QueryBudget) anyerror!void {
var stream = try address.connect(io, .{ .mode = .stream });
defer stream.close(io);
@@ -546,17 +558,19 @@ fn keepAliveThenStarved(io: std.Io, address: net.IpAddress, permits: *query_budg
while (permits.taken(io) != 0) try tick.sleep(io);
const held = permits.tryAcquire(io).?;
defer permits.release(io, held);
var released = false;
defer if (!released) permits.release(io, held);
try writer.interface.writeAll(&transport.framePrefix(@intCast(query_bytes.len)));
try writer.interface.writeAll(query_bytes);
try writer.interface.flush();
try sendQuery(&writer.interface);
try waitForWaiters(permits, io, 1);
permits.release(io, held);
released = true;
var sink: [64]u8 = undefined;
if (try reader.interface.readSliceShort(&sink) != 0) return error.TestUnexpectedBytes;
const len = transport.parsePrefix((try reader.interface.takeArray(transport.prefix_len)).*);
try expectAnswersQuery(try reader.interface.take(len));
}
test "a keep-alive connection holds no permit between queries, and a query without one ends it" {
test "a keep-alive connection holds no permit between queries, and a query without one waits for it" {
if (!build_options.integration) return error.SkipZigTest;
const gpa = testing.allocator;
@@ -583,11 +597,122 @@ test "a keep-alive connection holds no permit between queries, and a query witho
};
}
try bounded(io, keepAliveThenStarved, .{ io, server_address, &permits });
try bounded(io, keepAliveThenWait, .{ io, server_address, &permits });
try testing.expectEqual(@as(u64, 1), server.core.stats.dropped_no_permit.load(.monotonic));
try testing.expectEqual(@as(u64, 1), permits.snapshot().waits);
try testing.expectEqual(@as(u64, 0), permits.snapshot().wait_timeouts);
try testing.expectEqual(@as(u64, 0), server.core.stats.connection_errors.load(.monotonic));
try testing.expectEqual(@as(u64, 1), h.stats.queries.load(.monotonic));
try testing.expectEqual(@as(u64, 2), h.stats.queries.load(.monotonic));
}
/// A query that waits out the whole idle budget for a permit: the server ends
/// the connection without an answer.
fn waitOutlastsIdle(io: std.Io, address: net.IpAddress) anyerror!void {
var stream = try address.connect(io, .{ .mode = .stream });
defer stream.close(io);
var read_buf: [1024]u8 = undefined;
var write_buf: [1024]u8 = undefined;
var reader = stream.reader(io, &read_buf);
var writer = stream.writer(io, &write_buf);
try sendQuery(&writer.interface);
var sink: [64]u8 = undefined;
if (try reader.interface.readSliceShort(&sink) != 0) return error.TestUnexpectedBytes;
}
test "a stream query whose permit wait outlasts the idle budget ends its connection, counted" {
if (!build_options.integration) return error.SkipZigTest;
const gpa = testing.allocator;
var threaded: std.Io.Threaded = .init(gpa, .{});
defer threaded.deinit();
const io = threaded.io();
var fake: FakeUpstream = .{ .reply = response_bytes };
var h_owner: upstream_owner.Borrowed = .{};
var h = bareHandler(h_owner.client(fake.client()));
const listen_address: net.IpAddress = try .parse("127.0.0.1", 0);
var permits: query_budget.QueryBudget = try .init(gpa, 1);
defer permits.deinit();
var server = try tcp_server.TcpServer.listen(gpa, io, listen_address, &h, &permits, .{
.max_connections = 2,
.idle_timeout = short_idle,
});
const server_address = server.boundAddress();
var group: std.Io.Group = .init;
try group.concurrent(io, tcp_server.TcpServer.serve, .{ &server, io });
defer {
server.deinit(io);
group.await(io) catch |err| switch (err) {
error.Canceled => unreachable,
};
}
const held = permits.tryAcquire(io).?;
defer permits.release(io, held);
try bounded(io, waitOutlastsIdle, .{ io, server_address });
try testing.expectEqual(@as(u64, 1), permits.snapshot().wait_timeouts);
try testing.expectEqual(@as(u64, 0), h.stats.queries.load(.monotonic));
}
/// Leaves a query waiting for a permit and returns once the server sees it
/// waiting.
fn queryThenWait(io: std.Io, address: net.IpAddress, permits: *query_budget.QueryBudget, stream: *net.Stream) anyerror!void {
stream.* = try address.connect(io, .{ .mode = .stream });
var write_buf: [1024]u8 = undefined;
var writer = stream.writer(io, &write_buf);
try sendQuery(&writer.interface);
try waitForWaiters(permits, io, 1);
}
test "closing a tcp listener ends a connection that waits for a permit" {
if (!build_options.integration) return error.SkipZigTest;
const gpa = testing.allocator;
var threaded: std.Io.Threaded = .init(gpa, .{});
defer threaded.deinit();
const io = threaded.io();
var fake: FakeUpstream = .{ .reply = response_bytes };
var h_owner: upstream_owner.Borrowed = .{};
var h = bareHandler(h_owner.client(fake.client()));
const listen_address: net.IpAddress = try .parse("127.0.0.1", 0);
var permits: query_budget.QueryBudget = try .init(gpa, 1);
defer permits.deinit();
// An idle budget far past `bounded`'s, so only the close can end the wait.
const long_idle: std.Io.Clock.Duration = .{ .raw = .fromSeconds(60), .clock = .awake };
var server = try tcp_server.TcpServer.listen(gpa, io, listen_address, &h, &permits, .{
.max_connections = 2,
.idle_timeout = long_idle,
});
const server_address = server.boundAddress();
var group: std.Io.Group = .init;
try group.concurrent(io, tcp_server.TcpServer.serve, .{ &server, io });
const held = permits.tryAcquire(io).?;
defer permits.release(io, held);
var stream: net.Stream = undefined;
try bounded(io, queryThenWait, .{ io, server_address, &permits, &stream });
defer stream.close(io);
try bounded(io, closeAndJoin, .{ &server, io, &group });
try testing.expectEqual(@as(u64, 0), permits.snapshot().waiting);
try testing.expectEqual(@as(u64, 0), h.stats.queries.load(.monotonic));
}
fn closeAndJoin(server: *tcp_server.TcpServer, io: std.Io, group: *std.Io.Group) anyerror!void {
server.deinit(io);
group.await(io) catch |err| switch (err) {
error.Canceled => unreachable,
};
}
/// Holds the only slot with an answered connection, then opens a second one,