lib/machine/src/checkpoint/roots/maintenance.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const manifest = @import("manifest.zig");
2 const owner = @import("owner.zig");
3 const std = @import("std");
4 const tree = @import("tree.zig");
5 const types = @import("types.zig");
6
7 pub const CollectionCapacity = types.CollectionCapacity;
8 pub const CollectionLimits = types.CollectionLimits;
9 pub const CollectionReport = types.CollectionReport;
10 pub const CollectionSnapshot = types.CollectionSnapshot;
11 pub const ReachabilityClass = types.ReachabilityClass;
12 pub const Retention = types.Retention;
13 pub const SeedCounts = types.SeedCounts;
14 pub const compaction_capacity = types.compaction_capacity;
15 pub const compaction_limits = types.compaction_limits;
16
17 /// The call opens a manifest chain again, authenticates it, and walks the
18 /// newest page tree in full before publishing a single manifest with no parent
19 /// that carries the newest state. A parentless input returns its existing root
20 /// without publishing anything. Any publication failure aborts the compaction
21 /// transaction. A caller turns a long chain into a single record, so later
22 /// reopens walk one manifest.
23 pub fn compact(
24 storage: types.Storage,
25 expected: types.ManifestRoot,
26 ) types.Error!types.Binding {
27 const value = try owner.reopen(storage, expected);
28 try tree.verify(storage, value.pages);
29 if (value.parent == null) {
30 return .{ .root = expected, .manifest = value };
31 }
32 var compacted = value;
33 compacted.parent = null;
34 compacted.dirty_pages = @intCast(types.page_count);
35 try storage.begin(types.compaction_capacity);
36 var transaction_open = true;
37 defer if (transaction_open) storage.abort();
38 const root = try manifest.stage(storage, compacted);
39 try storage.commit(root);
40 transaction_open = false;
41 return .{ .root = root, .manifest = compacted };
42 }
43
44 /// The call traces every owner class the provider snapshotted, then commits one
45 /// atomic physical sweep. The call validates the snapshot against the supplied
46 /// capacity before marking begins, returning `RootCollectionCapacityExceeded`
47 /// when the snapshot exceeds it. A failure during tracing ends the collection
48 /// with no object removed. A caller runs collection to get space back from
49 /// captures nobody holds.
50 pub fn collect(
51 storage: types.Storage,
52 capacity: types.CollectionCapacity,
53 ) types.Error!types.CollectionReport {
54 const snapshot = try storage.beginCollection(capacity);
55 var collection_open = true;
56 defer if (collection_open) storage.abortCollection();
57 try validateSnapshot(capacity, snapshot);
58 inline for (types.reachability_classes) |class| {
59 var index: u16 = 0;
60 while (index < snapshot.seeds.count(class)) : (index += 1) {
61 const root = try storage.readSeed(class, index);
62 try retainRoot(storage, root);
63 }
64 }
65 const report = try storage.commitCollection();
66 collection_open = false;
67 validateReport(snapshot, report);
68 return report;
69 }
70
71 fn retainRoot(
72 storage: types.Storage,
73 root: types.ManifestRoot,
74 ) types.Error!void {
75 const retention = try storage.retainObject(.manifest, root.digest);
76 if (retention == .existing) return;
77 _ = try owner.openShared(storage, root);
78 var current = root;
79 var depth: usize = 0;
80 while (depth < types.chain_limit) : (depth += 1) {
81 const value = try manifest.read(storage, current);
82 try tree.retain(storage, value.pages);
83 _ = try storage.retainBlock(value.block);
84 if (value.parent) |parent| {
85 const parent_retention = try storage.retainObject(
86 .manifest,
87 parent.digest,
88 );
89 if (parent_retention == .existing) return;
90 current = parent;
91 } else {
92 return;
93 }
94 }
95 return error.DeltaChainCapacityExceeded;
96 }
97
98 fn validateSnapshot(
99 capacity: types.CollectionCapacity,
100 snapshot: types.CollectionSnapshot,
101 ) types.StorageError!void {
102 const limits = capacity.limits;
103 inline for (types.reachability_classes) |class| {
104 if (snapshot.seeds.count(class) > limits.seeds.count(class)) {
105 return error.RootCollectionCapacityExceeded;
106 }
107 }
108 if (snapshot.roots > limits.roots or
109 snapshot.objects > limits.objects or
110 snapshot.blocks > limits.blocks)
111 {
112 return error.RootCollectionCapacityExceeded;
113 }
114 }
115
116 fn validateReport(
117 snapshot: types.CollectionSnapshot,
118 report: types.CollectionReport,
119 ) void {
120 std.debug.assert(
121 @as(u32, report.retained_roots) + report.collected_roots ==
122 snapshot.roots,
123 );
124 std.debug.assert(
125 report.retained_objects + report.collected_objects == snapshot.objects,
126 );
127 std.debug.assert(
128 @as(u32, report.retained_blocks) + report.collected_blocks ==
129 snapshot.blocks,
130 );
131 }