server: every dns listener shares one budget of 32 queries, stream listeners take 16 connections, the task pool is capped from those bounds, and work it cannot start is counted by site
This commit is contained in:
@@ -12,7 +12,9 @@ const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
const net = std.Io.net;
|
||||
|
||||
const concurrency = @import("../platform/concurrency.zig");
|
||||
const handler = @import("handler.zig");
|
||||
const query_budget = @import("query_budget.zig");
|
||||
const udp_server = @import("udp_server.zig");
|
||||
const model = @import("../config/model.zig");
|
||||
const response = @import("../filter/response.zig");
|
||||
@@ -116,7 +118,9 @@ test "a udp query is answered on the loopback" {
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try udp_server.UdpServer.bind(gpa, io, listen_address, &h, .{ .max_in_flight = 4 });
|
||||
var permits: query_budget.QueryBudget = try .init(gpa, 4);
|
||||
defer permits.deinit();
|
||||
var server = try udp_server.UdpServer.bind(io, listen_address, &h, &permits);
|
||||
const server_address = server.boundAddress();
|
||||
|
||||
var group: std.Io.Group = .init;
|
||||
@@ -124,7 +128,7 @@ test "a udp query is answered on the loopback" {
|
||||
// Deferred so a failed check still stops the server: a `serve` left in
|
||||
// accept makes `threaded.deinit` wait for it forever.
|
||||
defer {
|
||||
server.deinit(gpa, io);
|
||||
server.deinit(io);
|
||||
group.await(io) catch |err| switch (err) {
|
||||
error.Canceled => unreachable,
|
||||
};
|
||||
@@ -158,13 +162,15 @@ test "a runt datagram is dropped and no reply is sent" {
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try udp_server.UdpServer.bind(gpa, io, listen_address, &h, .{ .max_in_flight = 4 });
|
||||
var permits: query_budget.QueryBudget = try .init(gpa, 4);
|
||||
defer permits.deinit();
|
||||
var server = try udp_server.UdpServer.bind(io, listen_address, &h, &permits);
|
||||
const server_address = server.boundAddress();
|
||||
|
||||
var group: std.Io.Group = .init;
|
||||
try group.concurrent(io, udp_server.UdpServer.serve, .{ &server, io });
|
||||
defer {
|
||||
server.deinit(gpa, io);
|
||||
server.deinit(io);
|
||||
group.await(io) catch |err| switch (err) {
|
||||
error.Canceled => unreachable,
|
||||
};
|
||||
@@ -195,13 +201,15 @@ test "an oversize datagram arrives truncated and is dropped" {
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try udp_server.UdpServer.bind(gpa, io, listen_address, &h, .{ .max_in_flight = 4 });
|
||||
var permits: query_budget.QueryBudget = try .init(gpa, 4);
|
||||
defer permits.deinit();
|
||||
var server = try udp_server.UdpServer.bind(io, listen_address, &h, &permits);
|
||||
const server_address = server.boundAddress();
|
||||
|
||||
var group: std.Io.Group = .init;
|
||||
try group.concurrent(io, udp_server.UdpServer.serve, .{ &server, io });
|
||||
defer {
|
||||
server.deinit(gpa, io);
|
||||
server.deinit(io);
|
||||
group.await(io) catch |err| switch (err) {
|
||||
error.Canceled => unreachable,
|
||||
};
|
||||
@@ -238,14 +246,101 @@ test "deinit ends a serve loop that is blocked on receive" {
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try udp_server.UdpServer.bind(gpa, io, listen_address, &h, .{ .max_in_flight = 4 });
|
||||
var permits: query_budget.QueryBudget = try .init(gpa, 4);
|
||||
defer permits.deinit();
|
||||
var server = try udp_server.UdpServer.bind(io, listen_address, &h, &permits);
|
||||
|
||||
var group: std.Io.Group = .init;
|
||||
try group.concurrent(io, udp_server.UdpServer.serve, .{ &server, io });
|
||||
|
||||
// No datagram ever arrives, so `serve` is inside a receive when this runs.
|
||||
server.deinit(gpa, io);
|
||||
server.deinit(io);
|
||||
group.await(io) catch |err| switch (err) {
|
||||
error.Canceled => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
test "with every permit taken a udp query is dropped and 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 udp_server.UdpServer.bind(io, listen_address, &h, &permits);
|
||||
const server_address = server.boundAddress();
|
||||
|
||||
var group: std.Io.Group = .init;
|
||||
try group.concurrent(io, udp_server.UdpServer.serve, .{ &server, io });
|
||||
defer {
|
||||
server.deinit(io);
|
||||
group.await(io) catch |err| switch (err) {
|
||||
error.Canceled => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
// Another listener's query, as far as this one can tell.
|
||||
const held = permits.tryAcquire(io).?;
|
||||
defer permits.release(io, held);
|
||||
|
||||
const client_address: net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var client = try client_address.bind(io, .{ .mode = .dgram });
|
||||
defer client.close(io);
|
||||
|
||||
try client.send(io, &server_address, query_bytes);
|
||||
|
||||
var buf: [udp_server.max_datagram]u8 = undefined;
|
||||
try testing.expectError(error.Timeout, client.receiveTimeout(io, &buf, silence));
|
||||
try testing.expectEqual(@as(u64, 1), server.stats.dropped_no_slot.load(.monotonic));
|
||||
try testing.expectEqual(@as(u64, 0), h.stats.queries.load(.monotonic));
|
||||
}
|
||||
|
||||
test "a udp query with a permit but no task is dropped, counted, and returns its permit" {
|
||||
if (!build_options.integration) return error.SkipZigTest;
|
||||
|
||||
const gpa = testing.allocator;
|
||||
// The serve task is the one task this pool allows.
|
||||
var threaded: std.Io.Threaded = .init(gpa, .{ .concurrent_limit = .limited(1) });
|
||||
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, 4);
|
||||
defer permits.deinit();
|
||||
var server = try udp_server.UdpServer.bind(io, listen_address, &h, &permits);
|
||||
const server_address = server.boundAddress();
|
||||
|
||||
var group: std.Io.Group = .init;
|
||||
try group.concurrent(io, udp_server.UdpServer.serve, .{ &server, io });
|
||||
defer {
|
||||
server.deinit(io);
|
||||
group.await(io) catch |err| switch (err) {
|
||||
error.Canceled => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
const client_address: net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var client = try client_address.bind(io, .{ .mode = .dgram });
|
||||
defer client.close(io);
|
||||
|
||||
const before = concurrency.snapshot().get(.udp_query);
|
||||
try client.send(io, &server_address, query_bytes);
|
||||
|
||||
var buf: [udp_server.max_datagram]u8 = undefined;
|
||||
try testing.expectError(error.Timeout, client.receiveTimeout(io, &buf, silence));
|
||||
try testing.expectEqual(@as(u64, 1), concurrency.snapshot().get(.udp_query) - before);
|
||||
try testing.expectEqual(@as(usize, 0), permits.taken(io));
|
||||
try testing.expectEqual(@as(u64, 0), server.stats.dropped_no_slot.load(.monotonic));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user