tiny.reticulum.node.timer
Defined in node.
API (25)
Actions
Public operations.
Due.nextTable.activateTable.canScheduleAfterCancelTable.cancelTable.containsTable.countTable.deinitTable.dueTable.initTable.scheduleTable.scheduledAt: Gives the second one named timer comes due at, so a caller checks the deadline the node armed for one timer.TimerId.eql
Types and contracts
Public types and contracts.
DueSecondsTableTable.CapacityTable.ExhaustionTable.InitErrorTable.LimitsTable.StorageTimerTimerId
Values and defaults
Public values and defaults.
Source
Source: lib/reticulum/src/node/root.zig:72
zig
pub const timer = @import("timer.zig");Source: lib/reticulum/src/node/timer.zig
zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const packet = @import("../packet/root.zig");pub const Seconds = u64;pub const TimerId = union(enum) { receipt: packet.Hash, hashlist: void, announces: void, link: [16]u8, link_entries: void, pub fn eql(left: TimerId, right: TimerId) bool { return switch (left) { .receipt => |left_hash| switch (right) { .receipt => |right_hash| std.mem.eql(u8, &left_hash, &right_hash), .hashlist, .announces, .link, .link_entries => false, }, .hashlist => right == .hashlist, .announces => right == .announces, .link_entries => right == .link_entries, .link => |left_id| switch (right) { .link => |right_id| std.mem.eql(u8, &left_id, &right_id), .receipt, .hashlist, .announces, .link_entries => false, }, }; }};pub const Timer = struct { id: TimerId, at: Seconds,};const TableLimits = struct { timers_max: usize,};const TableCapacity = struct { timers_max: usize, storage_bytes: usize, pub const DeriveError = error{ InvalidLimit, CapacityOverflow }; pub fn derive(limits: TableLimits) DeriveError!TableCapacity { if (limits.timers_max == 0) return error.InvalidLimit; const timers_max = limits.timers_max; const storage_bytes = alloc_phase.capacity.mul( usize, timers_max, @sizeOf(Timer), ) catch return error.CapacityOverflow; return .{ .timers_max = timers_max, .storage_bytes = storage_bytes }; }};pub const Table = struct { phase: alloc_phase.capacity.Phase, capacity: Capacity, storage: Storage, entries: []Timer, len: usize = 0, pub const storage_alignment: usize = 8; pub const Storage = []align(storage_alignment) u8; pub const Limits: type = TableLimits; pub const Capacity: type = TableCapacity; pub const Exhaustion = error{Full}; pub const InitError = Capacity.DeriveError || error{StorageLengthMismatch}; pub const work_limits: alloc_phase.capacity.WorkLimits = .{ .transition_steps_max = 131_072, .cleanup_steps_per_call_max = 65_536, .cleanup_calls_at_capacity_max = 1, }; pub const claim: alloc_phase.capacity.Declaration = .{ .source = .{ .id = "reticulum.timers", .kind = .phase_static, .limit_source = .caller, .storage = .{ .covered = &.{.{ .id = "caller_timer_table", .lifetime = .transferred, .detail = "caller storage for bounded node timers", }}, .excluded = &.{ "event timestamps", "operating-system clocks and scheduler state", }, }, .capacity = .{ .inputs = &.{alloc_phase.capacity.bindInput( TableLimits, "timers_max", "timers_max", )}, .type_selectors = &.{alloc_phase.capacity.bindType(Timer, "timer")}, .nodes = &.{ .{ .input = 0 }, .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 0 }, } }, }, .assertions = &.{.{ .scope = .closure_total, .measure = .retained, .relation = .exact, .expression = 1, }}, }, .overload = .{ .kind = .reject_before_mutation, .detail = "full timer admission preserves scheduled timers", }, .risks = .{ .transitive = .{ .status = .excluded, .detail = "timer operations call no allocating owner", }, .foreign = .{ .status = .excluded, .detail = "the timer table reads no external clock", }, }, .work = .{ .equation = "timer operations scan at most timers_max entries" }, .obligations = &.{ .{ .key = "reticulum_timers_capacity", .role = .capacity_model }, .{ .key = "reticulum_timers_overload", .role = .overload }, .{ .key = "reticulum_timers_work", .role = .work_bound }, }, }, .bindings = .{ .owner = @This(), .seal = .{ .family = alloc_phase.capacity.selector(@This().activate), .premise = .{ .class = .checked_semantic_fact, .authority = .checker }, }, .teardown = .{ .family = alloc_phase.capacity.selector(@This().deinit), .premise = .{ .class = .checked_semantic_fact, .authority = .checker }, }, }, }; pub fn init(storage: Storage, limits: Limits) InitError!Table { const capacity = try Capacity.derive(limits); if (storage.len != capacity.storage_bytes) return error.StorageLengthMismatch; return .{ .phase = .initialization, .capacity = capacity, .storage = storage, .entries = std.mem.bytesAsSlice(Timer, storage), }; } pub fn activate(self: *Table) void { std.debug.assert(self.phase == .initialization); std.debug.assert(self.len == 0); self.phase = .steady; } pub fn schedule(self: *Table, id: TimerId, at: Seconds) Exhaustion!void { std.debug.assert(self.phase == .steady); for (self.entries[0..self.len]) |*entry| { if (!entry.id.eql(id)) continue; entry.at = at; return; } if (self.len == self.capacity.timers_max) return error.Full; self.entries[self.len] = .{ .id = id, .at = at }; self.len += 1; } pub fn cancel(self: *Table, id: TimerId) bool { std.debug.assert(self.phase == .steady); for (self.entries[0..self.len], 0..) |entry, index| { if (!entry.id.eql(id)) continue; self.removeAt(index); return true; } return false; } pub fn contains(self: *const Table, id: TimerId) bool { std.debug.assert(self.phase == .steady); for (self.entries[0..self.len]) |entry| { if (entry.id.eql(id)) return true; } return false; } /// Gives the second one named timer comes due at, so a caller checks the deadline the node /// armed for one timer. A name the table holds no timer for gives null. pub fn scheduledAt(self: *const Table, id: TimerId) ?Seconds { std.debug.assert(self.phase == .steady); for (self.entries[0..self.len]) |entry| { if (entry.id.eql(id)) return entry.at; } return null; } pub fn canScheduleAfterCancel(self: *const Table, id: TimerId, cancelled_id: ?TimerId) bool { std.debug.assert(self.phase == .steady); if (self.contains(id)) return true; if (self.len < self.capacity.timers_max) return true; const cancelled = cancelled_id orelse return false; return self.contains(cancelled); } pub fn due(self: *Table, now: Seconds) Due { std.debug.assert(self.phase == .steady); return .{ .table = self, .now = now }; } pub fn count(self: *const Table) usize { std.debug.assert(self.phase == .steady); return self.len; } pub fn deinit(self: *Table) Storage { std.debug.assert(self.phase == .steady); self.phase = .teardown; const storage = self.storage; self.* = undefined; return storage; } fn removeAt(self: *Table, index: usize) void { std.debug.assert(index < self.len); std.mem.copyForwards( Timer, self.entries[index .. self.len - 1], self.entries[index + 1 .. self.len], ); self.len -= 1; }};pub const Due = struct { table: *Table, now: Seconds, pub fn next(self: *Due) ?Timer { std.debug.assert(self.table.phase == .steady); var earliest: ?usize = null; for (0..self.table.capacity.timers_max) |index| { if (index == self.table.len) break; const candidate = self.table.entries[index]; if (candidate.at > self.now) continue; if (earliest) |selected| { if (candidate.at >= self.table.entries[selected].at) continue; } earliest = index; } const index = earliest orelse return null; const value = self.table.entries[index]; self.table.removeAt(index); return value; }};comptime { alloc_phase.capacity.requireProvisionedRejectingOwnerShape(Table);}test "timers admit maximum and reject maximum plus one" { comptime { @stardustClaim(alloc_phase.capacity.witness( Table, "reticulum_timers_capacity", ), null, null, null, null, null, null); @stardustClaim(alloc_phase.capacity.witness( Table, "reticulum_timers_overload", ), null, null, null, null, null, null); @stardustClaim(alloc_phase.capacity.witness( Table, "reticulum_timers_work", ), null, null, null, null, null, null); } const capacity = comptime TableCapacity.derive(.{ .timers_max = 3 }) catch unreachable; var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined; var table = try Table.init(&bytes, .{ .timers_max = 3 }); table.activate(); defer _ = table.deinit(); try table.schedule(.{ .receipt = @splat(1) }, 30); try table.schedule(.{ .receipt = @splat(2) }, 10); try table.schedule(.{ .receipt = @splat(3) }, 20); try std.testing.expectError(error.Full, table.schedule(.hashlist, 40)); try std.testing.expectEqual(@as(usize, 3), table.count());}test "timers iterate due entries in deadline order" { const capacity = comptime TableCapacity.derive(.{ .timers_max = 3 }) catch unreachable; var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined; var table = try Table.init(&bytes, .{ .timers_max = 3 }); table.activate(); defer _ = table.deinit(); try table.schedule(.{ .receipt = @splat(1) }, 30); try table.schedule(.{ .receipt = @splat(2) }, 10); try table.schedule(.{ .receipt = @splat(3) }, 20); var due = table.due(20); try std.testing.expect(due.next().?.id.eql(.{ .receipt = @splat(2) })); try std.testing.expect(due.next().?.id.eql(.{ .receipt = @splat(3) })); try std.testing.expect(due.next() == null); try std.testing.expect(table.cancel(.{ .receipt = @splat(1) }));}test "timer admission accounts for one cancelled receipt timer" { const capacity = comptime TableCapacity.derive(.{ .timers_max = 1 }) catch unreachable; var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined; var table = try Table.init(&bytes, .{ .timers_max = 1 }); table.activate(); defer _ = table.deinit(); const old = TimerId{ .receipt = @splat(1) }; const new = TimerId{ .receipt = @splat(2) }; try table.schedule(old, 10); try std.testing.expect(table.canScheduleAfterCancel(new, old)); try std.testing.expect(!table.canScheduleAfterCancel(new, null));}test "timers keep one deadline per link id" { const capacity = comptime TableCapacity.derive(.{ .timers_max = 2 }) catch unreachable; var bytes: [capacity.storage_bytes]u8 align(Table.storage_alignment) = undefined; var table = try Table.init(&bytes, .{ .timers_max = 2 }); table.activate(); defer _ = table.deinit(); const first = TimerId{ .link = @splat(1) }; const second = TimerId{ .link = @splat(2) }; try table.schedule(first, 10); try table.schedule(second, 20); try table.schedule(first, 30); try std.testing.expectEqual(@as(usize, 2), table.count()); try std.testing.expectEqual(@as(?Seconds, 30), table.scheduledAt(first)); try std.testing.expect(!table.contains(.{ .receipt = @splat(1) })); try std.testing.expect(table.cancel(second)); try std.testing.expect(!table.contains(second));}Audit
| Definitions | 26 |
|---|---|
| Public names | 30 |
| Members | 15 |
| Version | 26.7.0 |
| Revision | daab053ee433 |