milestone 26: upstream health answers for the selected period
This commit is contained in:
+52
-12
@@ -48,6 +48,7 @@ const faults = @import("config/faults.zig");
|
||||
const fetcher = @import("filter/fetcher.zig");
|
||||
const forward_zones = @import("local/forward_zones.zig");
|
||||
const handler = @import("server/handler.zig");
|
||||
const history_mod = @import("upstream/history.zig");
|
||||
const http_util = @import("web/http_util.zig");
|
||||
const loader = @import("config/loader.zig");
|
||||
const local_records = @import("local/records.zig");
|
||||
@@ -68,6 +69,7 @@ const shutdown = @import("server/shutdown.zig");
|
||||
const sse = @import("web/sse.zig");
|
||||
const static = @import("web/static.zig");
|
||||
const tcp_server = @import("server/tcp_server.zig");
|
||||
const upstream_history_repo = @import("storage/repositories/upstream_history_repo.zig");
|
||||
const transport = @import("upstream/transport.zig");
|
||||
const udp_server = @import("server/udp_server.zig");
|
||||
const validate = @import("config/validate.zig");
|
||||
@@ -448,6 +450,13 @@ fn serve(r: cli.Runner, args: cli.RunArgs) !u8 {
|
||||
@truncate(@as(u96, @bitCast(std.Io.Clock.real.now(io).nanoseconds))),
|
||||
);
|
||||
|
||||
// On the heap, not in this frame: the accumulator carries its pending cells
|
||||
// and the flush task's buffer inline, which is about a megabyte.
|
||||
const history = try gpa.create(history_mod.Accumulator);
|
||||
defer gpa.destroy(history);
|
||||
history.* = .init;
|
||||
pool.history = history;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// per-query state
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -524,6 +533,8 @@ fn serve(r: cli.Runner, args: cli.RunArgs) !u8 {
|
||||
defer querylog_writer_db.close();
|
||||
var querylog_retention_db = try data.reopenQuerylogDb(io);
|
||||
defer querylog_retention_db.close();
|
||||
var querylog_history_db = try data.reopenQuerylogDb(io);
|
||||
defer querylog_history_db.close();
|
||||
var tracker_db = try data.openConfigDb(io);
|
||||
defer tracker_db.close();
|
||||
|
||||
@@ -646,6 +657,7 @@ fn serve(r: cli.Runner, args: cli.RunArgs) !u8 {
|
||||
.local_tables = &tables,
|
||||
.logger = &query_logger,
|
||||
.retention = &retention,
|
||||
.history = history,
|
||||
.sessions = if (sessions) |*s| s else null,
|
||||
.limiter = if (web_limiter) |*l| l else null,
|
||||
.hub = hub,
|
||||
@@ -735,12 +747,42 @@ fn serve(r: cli.Runner, args: cli.RunArgs) !u8 {
|
||||
|
||||
shutdown.install(io);
|
||||
|
||||
// Declared after everything it borrows, so its `cancel` — which both
|
||||
// requests cancellation and joins — is the first thing that runs on the way
|
||||
// out (ruling 22). Nothing below this line may be released while a task
|
||||
// could still touch it.
|
||||
// Declared after everything it borrows, so the teardown below — whose
|
||||
// `cancel` both requests cancellation and joins — is the first thing that
|
||||
// runs on the way out (ruling 22). Nothing below this line may be released
|
||||
// while a task could still touch it.
|
||||
var group: std.Io.Group = .init;
|
||||
defer group.cancel(io);
|
||||
|
||||
// Ruling 4's shutdown order, on the one path every exit from here takes:
|
||||
// the logger sees a closed queue and drains what it holds rather than
|
||||
// losing it to cancellation (ruling 22), then every task stops, and only
|
||||
// then does the final flush run — with no recording task left that could
|
||||
// add a cell after it.
|
||||
//
|
||||
// A `defer` and not straight-line code after `shutdown.wait`, because a
|
||||
// `concurrent` spawn below can fail with the DNS listeners already
|
||||
// serving; an orderly error teardown owes the operator the same drain a
|
||||
// signal gets. The `querylog_history_db` this flush writes through is
|
||||
// declared above, so its `close` runs after it.
|
||||
defer {
|
||||
query_logger.shutdown(io);
|
||||
group.cancel(io);
|
||||
history.flushOnce(io, &querylog_history_db, upstream_history_repo.flush);
|
||||
}
|
||||
|
||||
// The gate every non-essential write consults. Reading it before the
|
||||
// monitor's own task has sampled is safe: a fresh `Monitor` publishes `.ok`
|
||||
// (disk_monitor.zig:63), so nothing is refused for want of a sample.
|
||||
const gate: ?*disk_monitor.Monitor = &monitor;
|
||||
|
||||
// The writer starts before the listeners, and that order is the deferred
|
||||
// drain's precondition: a listener that is already accepting queries
|
||||
// enqueues log entries, and `Logger.shutdown` only closes the queue —
|
||||
// someone has to be on the other end to write what it hands over. Spawned
|
||||
// after the listeners, a `concurrent` failure in between would leave those
|
||||
// entries with no consumer and `group.cancel` nothing to drain, which is
|
||||
// exactly the loss the teardown above exists to prevent.
|
||||
try group.concurrent(io, logger_mod.Logger.runWriter, .{ &query_logger, io, &querylog_writer_db, gate });
|
||||
|
||||
if (udp6) |*s| try group.concurrent(io, udp_server.UdpServer.serve, .{ s, io });
|
||||
if (udp4) |*s| try group.concurrent(io, udp_server.UdpServer.serve, .{ s, io });
|
||||
@@ -751,9 +793,10 @@ fn serve(r: cli.Runner, args: cli.RunArgs) !u8 {
|
||||
if (doh_certs) |*store| try group.concurrent(io, cert_store.CertStore.watch, .{ store, io });
|
||||
if (dot_certs) |*store| try group.concurrent(io, cert_store.CertStore.watch, .{ store, io });
|
||||
|
||||
const gate: ?*disk_monitor.Monitor = &monitor;
|
||||
try group.concurrent(io, logger_mod.Logger.runWriter, .{ &query_logger, io, &querylog_writer_db, gate });
|
||||
try group.concurrent(io, retention_mod.Retention.run, .{ &retention, io, &querylog_retention_db, gate });
|
||||
// Ungated: a flush writes at most one row per upstream per minute, the same
|
||||
// category as the query logger's own writes, which are ungated too.
|
||||
try group.concurrent(io, history_mod.Accumulator.run, .{ history, io, &querylog_history_db });
|
||||
try group.concurrent(io, disk_monitor.Monitor.run, .{ &monitor, io });
|
||||
try group.concurrent(io, manager_mod.Manager.runScheduler, .{ &manager, io });
|
||||
try group.concurrent(io, clients.Tracker.run, .{ &tracker, io, &tracker_db, gate, &client_names_resolver });
|
||||
@@ -774,14 +817,11 @@ fn serve(r: cli.Runner, args: cli.RunArgs) !u8 {
|
||||
});
|
||||
|
||||
// A canceled wait is a shutdown request too: whoever canceled this task
|
||||
// wants the process to stop, and the teardown below is how it stops.
|
||||
// wants the process to stop, and returning into the teardown deferred above
|
||||
// is how it stops.
|
||||
shutdown.wait(io) catch {};
|
||||
log.info("shutting down", .{});
|
||||
|
||||
// Before the group is canceled, so the writer sees a closed queue and
|
||||
// drains what it holds rather than losing it to cancellation (ruling 22).
|
||||
query_logger.shutdown(io);
|
||||
|
||||
return cli.exit_ok;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user