milestone 30: overview as a dashboard, explicit health contract, period aggregations
Gates / frontend (push) Successful in 1m32s
Gates / test (push) Successful in 1m54s
Gates / package (push) Successful in 5m28s
Gates / container (push) Successful in 14s
Gates / test-aarch64 (push) Failing after 3h10m0s
CI / gates (push) Failing after 3h11m55s
Gates / frontend (push) Successful in 1m32s
Gates / test (push) Successful in 1m54s
Gates / package (push) Successful in 5m28s
Gates / container (push) Successful in 14s
Gates / test-aarch64 (push) Failing after 3h10m0s
CI / gates (push) Failing after 3h11m55s
This commit is contained in:
+77
-5
@@ -40,7 +40,6 @@ const logger_mod = @import("../storage/logger.zig");
|
||||
const manager_mod = @import("../filter/manager.zig");
|
||||
const model = @import("../config/model.zig");
|
||||
const pause_mod = @import("../server/pause.zig");
|
||||
const history_mod = @import("../upstream/history.zig");
|
||||
const pool_mod = @import("../upstream/pool.zig");
|
||||
const query_sink = @import("../server/query_sink.zig");
|
||||
const retention_mod = @import("../storage/retention.zig");
|
||||
@@ -137,10 +136,6 @@ pub const WebState = struct {
|
||||
client_names: ?*client_names.Resolver = null,
|
||||
manager: ?*manager_mod.Manager = null,
|
||||
pool: ?*pool_mod.Pool = null,
|
||||
/// The upstream-outcome accumulator, for `metrics.collect` and the
|
||||
/// `/api/health` rollup (m26 ruling 7). The ranged endpoint reads the
|
||||
/// flushed rows through `querylog_db`, not through this.
|
||||
history: ?*history_mod.Accumulator = null,
|
||||
monitor: ?*disk_monitor.Monitor = null,
|
||||
/// The local records and forward zones the DNS path reads. The
|
||||
/// local-records and forward-zones handlers rebuild and swap them
|
||||
@@ -184,6 +179,13 @@ pub const WebState = struct {
|
||||
/// concurrent writes would misread each other's row counts.
|
||||
config_lock: std.Io.Mutex = .init,
|
||||
querylog_db: ?*db.Db = null,
|
||||
/// Serializes the web layer's work on `querylog_db`, for the same reason
|
||||
/// `config_lock` exists and one more: the read handlers wrap their several
|
||||
/// statements in a transaction, and SQLite's serialized mode protects a
|
||||
/// single call, not a transaction. Without this, two concurrent BEGINs on
|
||||
/// the shared connection would fail and a third task's reads would land
|
||||
/// inside someone else's snapshot.
|
||||
querylog_lock: std.Io.Mutex = .init,
|
||||
/// The diagnostics event store, which owns a third connection of its own
|
||||
/// and serializes every access — read and write — through its mutex. Null
|
||||
/// when `Store.init` failed, which `/api/health` reports as `unavailable`
|
||||
@@ -219,6 +221,76 @@ pub const WebState = struct {
|
||||
reload_fn: ?ReloadFn = null,
|
||||
};
|
||||
|
||||
/// One response's hold on the query log: `querylog_lock` plus one deferred read
|
||||
/// transaction, opened and closed together so no reader can hold one without
|
||||
/// the other.
|
||||
///
|
||||
/// Every web-layer read of `querylog_db` goes through this. The transaction is
|
||||
/// what makes an aggregate and the coverage watermark beside it describe one
|
||||
/// database state, and the lock is what makes the transaction meaningful on a
|
||||
/// connection several tasks share.
|
||||
///
|
||||
/// `commit` is fallible and must be called before the response is written: a
|
||||
/// connection still inside a transaction refuses the next `BEGIN`, so a handler
|
||||
/// that answered 200 over a failed commit would leave every later query-log
|
||||
/// request failing for a reason nothing on the wire ever named.
|
||||
///
|
||||
/// **The lock is always released, even when the transaction could not be
|
||||
/// ended.** `lockUncancelable` cannot be interrupted, so holding it against a
|
||||
/// connection that will not leave its transaction would park every later
|
||||
/// query-log task forever, with no status and no way out but a kill. Releasing
|
||||
/// it turns the same fault into a 500 per request: bounded, visible, and
|
||||
/// recoverable by a restart.
|
||||
/// **The lock is released exactly once, on every path.** The usage shape below
|
||||
/// runs `abort` after a failed `commit` — an `errdefer` cannot know the error
|
||||
/// came from the commit itself — so `release` is the single owner of the
|
||||
/// unlock and `held` is what makes the second call a no-op. Unlocking an
|
||||
/// already-unlocked `std.Io.Mutex` is `unreachable`, and under contention it
|
||||
/// would hand away a hold another task had just taken, so the bounded 500 this
|
||||
/// type promises would instead be a crash or a corrupted mutex.
|
||||
///
|
||||
/// ```zig
|
||||
/// var scope = try QuerylogRead.open(state, io, database);
|
||||
/// errdefer scope.abort();
|
||||
/// ... // reads only
|
||||
/// try scope.commit();
|
||||
/// ```
|
||||
pub const QuerylogRead = struct {
|
||||
state: *WebState,
|
||||
io: std.Io,
|
||||
tx: db.ReadTx,
|
||||
held: bool,
|
||||
|
||||
pub fn open(state: *WebState, io: std.Io, database: *db.Db) db.Error!QuerylogRead {
|
||||
state.querylog_lock.lockUncancelable(io);
|
||||
errdefer state.querylog_lock.unlock(io);
|
||||
return .{
|
||||
.state = state,
|
||||
.io = io,
|
||||
.tx = try db.ReadTx.begin(database),
|
||||
.held = true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn commit(self: *QuerylogRead) db.Error!void {
|
||||
defer self.release();
|
||||
return self.tx.commit();
|
||||
}
|
||||
|
||||
/// Safe in `errdefer`, and safe after `commit` however that ended: both the
|
||||
/// rollback and the release are idempotent.
|
||||
pub fn abort(self: *QuerylogRead) void {
|
||||
self.tx.rollback();
|
||||
self.release();
|
||||
}
|
||||
|
||||
fn release(self: *QuerylogRead) void {
|
||||
if (!self.held) return;
|
||||
self.held = false;
|
||||
self.state.querylog_lock.unlock(self.io);
|
||||
}
|
||||
};
|
||||
|
||||
/// Ruling 17. Authentication is enabled iff a password hash is set — the live
|
||||
/// one, so a password set through the API locks the routes without a restart.
|
||||
/// With it set but no session store wired, every session route is refused: the
|
||||
|
||||
Reference in New Issue
Block a user