persistence.zig 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. const std = @import("std");
  2. const storage = @import("storage.zig");
  3. const MAX_PERSISTENCE_SIZE = 10_000_000 * 100;
  4. const BUFFER_SIZE = 1024 * 1024 * 8;
  5. const FLUSH_THRESHOLD = (BUFFER_SIZE * 3) / 4;
  6. var storage_file: ?std.fs.File = null;
  7. const c_allocator = std.heap.c_allocator;
  8. var mutex: std.Thread.Mutex = .{};
  9. var write_buffer: [BUFFER_SIZE]u8 = undefined;
  10. var buffer_position: usize = 0;
  11. const OPCode = enum {
  12. W,
  13. D,
  14. };
  15. pub fn init() !void {
  16. const cwd = std.fs.cwd();
  17. storage_file = cwd.openFile(".db", .{ .mode = .read_write }) catch |err| {
  18. if (err == std.fs.File.OpenError.FileNotFound) {
  19. std.debug.print("No persisted data found, starting fresh...\n", .{});
  20. storage_file = cwd.createFile(".db", .{ .read = true }) catch |ierr| {
  21. std.debug.print("Failed to create storage file: {any}\n", .{ierr});
  22. return;
  23. };
  24. std.debug.print("Created new storage file .db\n", .{});
  25. }
  26. return;
  27. };
  28. const storage_data = storage_file.?.readToEndAlloc(c_allocator, MAX_PERSISTENCE_SIZE) catch |err| {
  29. std.debug.print("Failed to read storage file: {any}\n", .{err});
  30. return;
  31. };
  32. defer c_allocator.free(storage_data);
  33. var records = std.mem.splitScalar(u8, storage_data, '\r');
  34. var record_count: usize = 0;
  35. while (records.next()) |record| {
  36. if (record.len == 0) {
  37. continue;
  38. }
  39. record_count += 1;
  40. std.debug.print("Restoring record N:{d}\r", .{record_count});
  41. var recordIterator = std.mem.splitScalar(u8, record, '|');
  42. const opcode = recordIterator.first();
  43. const key = recordIterator.next() orelse continue;
  44. const value = recordIterator.next() orelse continue;
  45. const opcodeEnum = std.meta.stringToEnum(OPCode, opcode) orelse continue;
  46. switch (opcodeEnum) {
  47. .W => _ = storage.writeVolatile(key, value),
  48. .D => _ = storage.deleteVolatile(key),
  49. }
  50. }
  51. std.debug.print("Restored {d} records from persistence", .{record_count});
  52. storage_file.?.close();
  53. storage_file = cwd.openFile(".db", .{ .mode = .write_only }) catch |err| {
  54. std.debug.print("Failed to reopen storage file in append mode: {any}\n", .{err});
  55. return;
  56. };
  57. try storage_file.?.seekFromEnd(0);
  58. return;
  59. }
  60. pub fn persist(opcode: u8, key: []const u8, value: []const u8) void {
  61. const record = std.fmt.allocPrint(c_allocator, "{c}|{s}|{s}\r", .{ opcode, key, value }) catch {
  62. std.debug.print("Failed to format record for persistence\n", .{});
  63. return;
  64. };
  65. defer c_allocator.free(record);
  66. mutex.lock();
  67. defer mutex.unlock();
  68. if (buffer_position + record.len > FLUSH_THRESHOLD) {
  69. flushBuffer() catch |err| {
  70. std.debug.print("Failed to flush buffer: {any}\n", .{err});
  71. return;
  72. };
  73. }
  74. if (record.len > BUFFER_SIZE) {
  75. _ = storage_file.?.write(record) catch |err| {
  76. std.debug.print("Failed to write large record to storage file: {any}\n", .{err});
  77. return;
  78. };
  79. return;
  80. }
  81. if (buffer_position + record.len > BUFFER_SIZE) {
  82. flushBuffer() catch |err| {
  83. std.debug.print("Failed to flush buffer: {any}\n", .{err});
  84. return;
  85. };
  86. }
  87. @memcpy(write_buffer[buffer_position .. buffer_position + record.len], record);
  88. buffer_position += record.len;
  89. }
  90. pub fn flush() !void {
  91. mutex.lock();
  92. defer mutex.unlock();
  93. try flushBuffer();
  94. }
  95. fn flushBuffer() !void {
  96. if (buffer_position == 0) {
  97. return;
  98. }
  99. _ = try storage_file.?.write(write_buffer[0..buffer_position]);
  100. buffer_position = 0;
  101. }