feat: add bootstrap and close to version
This commit is contained in:
1 parent
d2ea602ceb
commit
38078e8106
1 file changed
+160
+160
@@ -154,3 +154,163 @@ pub const VersionSet = struct {
|
|||||||
const data = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
|
const data = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
|
||||||
defer allocator.free(data);
|
defer allocator.free(data);
|
||||||
|
|
||||||
|
var pos: usize = 0;
|
||||||
|
var goodEnd: usize = 0;
|
||||||
|
var records: usize = 0;
|
||||||
|
var counters = Counters{};
|
||||||
|
while (pos + 8 <= data.len) {
|
||||||
|
const storedCrc = encode.readU32(data, pos);
|
||||||
|
const bodyStart = pos + 4;
|
||||||
|
const bodyLen = recordBodyLen(data, bodyStart) orelse break;
|
||||||
|
if (bodyStart + bodyLen > data.len) break;
|
||||||
|
const body = data[bodyStart .. bodyStart + bodyLen];
|
||||||
|
if (encode.crc(body) != storedCrc) break;
|
||||||
|
|
||||||
|
try replayRecord(&state, &counters, body);
|
||||||
|
records += 1;
|
||||||
|
pos = bodyStart + bodyLen;
|
||||||
|
goodEnd = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goodEnd != data.len) {
|
||||||
|
try file.setEndPos(goodEnd);
|
||||||
|
try file.sync();
|
||||||
|
}
|
||||||
|
try file.seekFromEnd(0);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.set = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.dir = dir,
|
||||||
|
.file = file,
|
||||||
|
.manifestId = id,
|
||||||
|
.pendingRecords = records,
|
||||||
|
.counters = counters,
|
||||||
|
},
|
||||||
|
.state = state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bootstrap(allocator: Allocator, dir: std.fs.Dir) !VersionSet {
|
||||||
|
var nameBuf: [64]u8 = undefined;
|
||||||
|
const name = try manifestFileName(&nameBuf, 1);
|
||||||
|
const file = try dir.createFile(name, .{ .read = true, .truncate = true });
|
||||||
|
errdefer file.close();
|
||||||
|
try writeCurrent(dir, name);
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.dir = dir,
|
||||||
|
.file = file,
|
||||||
|
.manifestId = 1,
|
||||||
|
.pendingRecords = 0,
|
||||||
|
.counters = .{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(self: *VersionSet) void {
|
||||||
|
self.file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies `edit` to `state` and durably appends it to the manifest
|
||||||
|
/// log. Rotates (compacts the log to a single snapshot record) once
|
||||||
|
/// the number of pending records grows past the live file count.
|
||||||
|
pub fn logAndApply(self: *VersionSet, edit: *const VersionEdit, state: *std.AutoHashMap(u64, usize)) !void {
|
||||||
|
if (edit.isEmpty()) return;
|
||||||
|
try applyEdit(state, edit.added.items, edit.deleted.items);
|
||||||
|
if (edit.nextFileId) |v| self.counters.nextFileId = v;
|
||||||
|
if (edit.nextSeq) |v| self.counters.nextSeq = v;
|
||||||
|
try self.appendRecord(edit);
|
||||||
|
self.pendingRecords += 1;
|
||||||
|
|
||||||
|
const liveCount = state.count();
|
||||||
|
if (self.pendingRecords > @max(liveCount, 4) * 2) {
|
||||||
|
try self.rotate(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn appendRecord(self: *VersionSet, edit: *const VersionEdit) !void {
|
||||||
|
var body = std.ArrayList(u8).init(self.allocator);
|
||||||
|
defer body.deinit();
|
||||||
|
try edit.encode_(&body);
|
||||||
|
|
||||||
|
var frame = std.ArrayList(u8).init(self.allocator);
|
||||||
|
defer frame.deinit();
|
||||||
|
const frameWriter = encode.ByteWriter.init(&frame);
|
||||||
|
try frameWriter.putU32(encode.crc(body.items));
|
||||||
|
try frameWriter.slice(body.items);
|
||||||
|
|
||||||
|
try self.file.seekFromEnd(0);
|
||||||
|
try self.file.writeAll(frame.items);
|
||||||
|
try self.file.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces the whole log history with a single snapshot record that
|
||||||
|
/// reproduces the current live state, then switches CURRENT to it and
|
||||||
|
/// deletes the old log. Bounds manifest size to O(live files) instead
|
||||||
|
/// of O(edits ever applied).
|
||||||
|
fn rotate(self: *VersionSet, state: *const std.AutoHashMap(u64, usize)) !void {
|
||||||
|
var snapshot = std.ArrayList(FileEdit).init(self.allocator);
|
||||||
|
defer snapshot.deinit();
|
||||||
|
var it = state.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
try snapshot.append(.{ .fileId = entry.key_ptr.*, .level = @intCast(entry.value_ptr.*) });
|
||||||
|
}
|
||||||
|
|
||||||
|
const newId = self.manifestId + 1;
|
||||||
|
var nameBuf: [64]u8 = undefined;
|
||||||
|
const newName = try manifestFileName(&nameBuf, newId);
|
||||||
|
|
||||||
|
const newFile = try self.dir.createFile(newName, .{ .read = true, .truncate = true });
|
||||||
|
errdefer newFile.close();
|
||||||
|
|
||||||
|
{
|
||||||
|
var body = std.ArrayList(u8).init(self.allocator);
|
||||||
|
defer body.deinit();
|
||||||
|
const writer = encode.ByteWriter.init(&body);
|
||||||
|
try writer.putU32(@intCast(snapshot.items.len));
|
||||||
|
for (snapshot.items) |f| {
|
||||||
|
try writer.putU64(f.fileId);
|
||||||
|
try writer.putU32(f.level);
|
||||||
|
}
|
||||||
|
try writer.putU32(0);
|
||||||
|
// Carry the latest known counters forward into the snapshot so
|
||||||
|
// they survive rotation even though the edits that originally
|
||||||
|
// set them are being discarded along with the old log.
|
||||||
|
try writer.byte(if (self.counters.nextFileId != null) 1 else 0);
|
||||||
|
if (self.counters.nextFileId) |v| try writer.putU64(v);
|
||||||
|
try writer.byte(if (self.counters.nextSeq != null) 1 else 0);
|
||||||
|
if (self.counters.nextSeq) |v| try writer.putU64(v);
|
||||||
|
|
||||||
|
var frame = std.ArrayList(u8).init(self.allocator);
|
||||||
|
defer frame.deinit();
|
||||||
|
const frameWriter = encode.ByteWriter.init(&frame);
|
||||||
|
try frameWriter.putU32(encode.crc(body.items));
|
||||||
|
try frameWriter.slice(body.items);
|
||||||
|
try newFile.writeAll(frame.items);
|
||||||
|
try newFile.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
try writeCurrent(self.dir, newName);
|
||||||
|
|
||||||
|
var oldNameBuf: [64]u8 = undefined;
|
||||||
|
const oldName = try manifestFileName(&oldNameBuf, self.manifestId);
|
||||||
|
self.file.close();
|
||||||
|
self.dir.deleteFile(oldName) catch {};
|
||||||
|
|
||||||
|
self.file = newFile;
|
||||||
|
self.manifestId = newId;
|
||||||
|
self.pendingRecords = snapshot.items.len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn writeCurrent(dir: std.fs.Dir, manifestName: []const u8) !void {
|
||||||
|
const tmpName = "CURRENT.tmp";
|
||||||
|
{
|
||||||
|
const file = try dir.createFile(tmpName, .{ .truncate = true });
|
||||||
|
defer file.close();
|
||||||
|
try file.writeAll(manifestName);
|
||||||
|
try file.sync();
|
||||||
|
}
|
||||||
|
try dir.rename(tmpName, currentName);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user