This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

weir / src / cache.zig
5.5 kB 155 lines
1//! the appview: a derived, disposable index over the repo. 2//! 3//! the repo (signed CAR) is the only source of truth. this layer exists so 4//! reads don't walk the MST, and it can be deleted and rebuilt from the repo 5//! at any time (`hydrate` does exactly that on boot). 6//! 7//! backends are swappable behind the union: 8//! - burner: embedded burner-redis, zero-infrastructure default 9//! - redis: a real redis over TCP — the production shape (one connection 10//! guarded by a mutex; pooling is the obvious next step if it shows up 11//! in profiles) 12 13const std = @import("std"); 14const burner = @import("burner_redis"); 15const redis = @import("redis"); 16 17pub const Cache = union(enum) { 18 burner: Burner, 19 redis: Redis, 20 21 pub fn initBurner(allocator: std.mem.Allocator, io: std.Io) Cache { 22 return .{ .burner = .{ .store = burner.Store.init(allocator, io) } }; 23 } 24 25 pub fn initRedis(allocator: std.mem.Allocator, io: std.Io, host: []const u8, port: u16) !Cache { 26 return .{ .redis = .{ 27 .io = io, 28 .client = try redis.Client.connect(io, allocator, host, port), 29 } }; 30 } 31 32 pub fn deinit(self: *Cache) void { 33 switch (self.*) { 34 .burner => |*b| b.store.deinit(), 35 .redis => |*r| r.client.close(), 36 } 37 } 38 39 pub fn set(self: *Cache, key: []const u8, value: []const u8) !void { 40 switch (self.*) { 41 .burner => |*b| _ = try b.store.set(key, value, .{}), 42 .redis => |*r| { 43 r.mutex.lock(r.io) catch return error.LockFailed; 44 defer r.mutex.unlock(r.io); 45 var cmds = r.client.strings(); 46 try cmds.set(key, value); 47 }, 48 } 49 } 50 51 /// returned slice is owned by out_alloc 52 pub fn get(self: *Cache, out_alloc: std.mem.Allocator, key: []const u8) ?[]u8 { 53 switch (self.*) { 54 .burner => |*b| return b.store.get(out_alloc, key) catch null, 55 .redis => |*r| { 56 r.mutex.lock(r.io) catch return null; 57 defer r.mutex.unlock(r.io); 58 var cmds = r.client.strings(); 59 const val = cmds.get(key) catch return null; 60 const v = val orelse return null; 61 return out_alloc.dupe(u8, v) catch null; 62 }, 63 } 64 } 65 66 // set ops back the appview's secondary indexes (runs by state, by flow) 67 // — the same shape as the cloud prototype's redis run-state indices. 68 69 pub fn sadd(self: *Cache, key: []const u8, member: []const u8) !void { 70 switch (self.*) { 71 .burner => |*b| _ = try b.store.sadd(key, &.{member}), 72 .redis => |*r| { 73 r.mutex.lock(r.io) catch return error.LockFailed; 74 defer r.mutex.unlock(r.io); 75 var cmds = r.client.sets(); 76 _ = try cmds.sadd(key, &.{member}); 77 }, 78 } 79 } 80 81 pub fn srem(self: *Cache, key: []const u8, member: []const u8) !void { 82 switch (self.*) { 83 .burner => |*b| _ = b.store.srem(key, &.{member}) catch 0, 84 .redis => |*r| { 85 r.mutex.lock(r.io) catch return; 86 defer r.mutex.unlock(r.io); 87 var cmds = r.client.sets(); 88 _ = cmds.srem(key, &.{member}) catch 0; 89 }, 90 } 91 } 92 93 /// returned members owned by out_alloc 94 pub fn smembers(self: *Cache, out_alloc: std.mem.Allocator, key: []const u8) ![][]u8 { 95 switch (self.*) { 96 .burner => |*b| return b.store.smembers(out_alloc, key) catch &.{}, 97 .redis => |*r| { 98 r.mutex.lock(r.io) catch return &.{}; 99 defer r.mutex.unlock(r.io); 100 var cmds = r.client.sets(); 101 const vals = cmds.smembers(key) catch return &.{}; 102 var out = try out_alloc.alloc([]u8, vals.len); 103 var n: usize = 0; 104 for (vals) |v| { 105 switch (v) { 106 .bulk => |b| { 107 const bytes = b orelse continue; 108 out[n] = try out_alloc.dupe(u8, bytes); 109 n += 1; 110 }, 111 else => {}, 112 } 113 } 114 return out[0..n]; 115 }, 116 } 117 } 118 119 pub fn scard(self: *Cache, key: []const u8) i64 { 120 switch (self.*) { 121 .burner => |*b| return b.store.scard(key) catch 0, 122 .redis => |*r| { 123 r.mutex.lock(r.io) catch return 0; 124 defer r.mutex.unlock(r.io); 125 var cmds = r.client.sets(); 126 return cmds.scard(key) catch 0; 127 }, 128 } 129 } 130}; 131 132const Burner = struct { 133 store: burner.Store, 134}; 135 136const Redis = struct { 137 io: std.Io, 138 client: redis.Client, 139 mutex: std.Io.Mutex = .init, 140}; 141 142test "cache set/get round trip" { 143 var threaded = std.Io.Threaded.init(std.testing.allocator, .{}); 144 defer threaded.deinit(); 145 const io = threaded.io(); 146 147 var cache = Cache.initBurner(std.testing.allocator, io); 148 defer cache.deinit(); 149 150 try cache.set("flow:name:hello", "abc-123"); 151 const got = cache.get(std.testing.allocator, "flow:name:hello").?; 152 defer std.testing.allocator.free(got); 153 try std.testing.expectEqualStrings("abc-123", got); 154 try std.testing.expect(cache.get(std.testing.allocator, "missing") == null); 155}