mirror of
https://github.com/ziex-dev/ziex.git
synced 2026-07-20 02:29:36 -06:00
refactor: more refactoring to zx.Cache
This commit is contained in:
parent
6923b09633
commit
05444f3583
@ -35,7 +35,7 @@ pub var host: []const u8 = "localhost:3000";
|
||||
pub var current_path: []const u8 = "/";
|
||||
|
||||
fn settingsKV() zx.kv.KVScope {
|
||||
return zx.kv.scope(settings_namespace);
|
||||
return zx.kv.scoped(settings_namespace);
|
||||
}
|
||||
|
||||
pub fn loadSettings() bool {
|
||||
|
||||
@ -192,7 +192,7 @@ function parseEdgeMeta(stderrText: string): { status: number; headers: Headers;
|
||||
* Run a WASM module for a single request using JSPI.
|
||||
*
|
||||
* Pass `kv` as a map of binding names → KV namespaces. The Zig side selects
|
||||
* a binding via `zx.kv.scope("name")`; the top-level `zx.kv.*` functions use
|
||||
* a binding via `zx.kv.scoped("name")`; the top-level `zx.kv.*` functions use
|
||||
* `"default"`.
|
||||
*
|
||||
* @example
|
||||
|
||||
@ -101,7 +101,7 @@ fn filterUsers(allocator: std.mem.Allocator, users: *std.ArrayList(User), search
|
||||
}
|
||||
|
||||
fn kv() zx.Kv {
|
||||
return zx.kv.scope(.@"examples/form");
|
||||
return zx.kv.scoped(.@"examples/form");
|
||||
}
|
||||
|
||||
fn syncFromKv(ctx: zx.PageContext, users: *std.ArrayList(User)) void {
|
||||
|
||||
@ -65,7 +65,7 @@ fn resolveOptions(init: zx.Init, config: Config) !Config {
|
||||
};
|
||||
|
||||
zx.kv = kv_fs.kv();
|
||||
zx.cache = try zx.Cache.init(init.io, cache_fs.kv(), .{
|
||||
zx.cache = try zx.Cache.init(init.io, allocator, cache_fs.kv(), .{
|
||||
.max_size = resolved.cache.max_size,
|
||||
});
|
||||
|
||||
|
||||
@ -18,6 +18,9 @@ const header_len = 9;
|
||||
|
||||
backend: Kv = Kv.failing,
|
||||
namespace: []const u8 = "default",
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
memory: ?*cachez.Cache(CacheEntry) = null,
|
||||
|
||||
pub const PutOptions = Kv.PutOptions;
|
||||
|
||||
@ -36,31 +39,12 @@ const CacheEntry = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const State = struct {
|
||||
io: std.Io,
|
||||
allocator: Allocator,
|
||||
memory: cachez.Cache(CacheEntry),
|
||||
};
|
||||
|
||||
const StoredEntry = struct {
|
||||
expires_at: ?u64,
|
||||
payload: []const u8,
|
||||
};
|
||||
|
||||
const StateMutex = struct {
|
||||
inner: std.Io.Mutex = .init,
|
||||
pub fn lock(self: *@This(), io: std.Io) void {
|
||||
self.inner.lockUncancelable(io);
|
||||
}
|
||||
pub fn unlock(self: *@This(), io: std.Io) void {
|
||||
self.inner.unlock(io);
|
||||
}
|
||||
};
|
||||
|
||||
var state: ?State = null;
|
||||
var state_mutex: StateMutex = .{};
|
||||
|
||||
pub fn init(io: std.Io, kv: Kv, config: Config) !Cache {
|
||||
pub fn init(io: std.Io, allocator: Allocator, kv: Kv, config: Config) !Cache {
|
||||
const cachez_config = cachez.Config{
|
||||
.max_size = config.max_size,
|
||||
.segment_count = config.segment_count,
|
||||
@ -68,36 +52,25 @@ pub fn init(io: std.Io, kv: Kv, config: Config) !Cache {
|
||||
.shrink_ratio = config.shrink_ratio,
|
||||
};
|
||||
|
||||
const lock_io = if (state) |current| current.io else io;
|
||||
state_mutex.lock(lock_io);
|
||||
defer state_mutex.unlock(lock_io);
|
||||
|
||||
if (state) |*current| {
|
||||
current.memory.deinit();
|
||||
state = null;
|
||||
}
|
||||
|
||||
state = .{
|
||||
.io = io,
|
||||
.allocator = zx.allocator,
|
||||
.memory = try cachez.Cache(CacheEntry).init(io, zx.allocator, cachez_config),
|
||||
};
|
||||
const memory = try allocator.create(cachez.Cache(CacheEntry));
|
||||
errdefer allocator.destroy(memory);
|
||||
memory.* = try cachez.Cache(CacheEntry).init(io, allocator, cachez_config);
|
||||
|
||||
return .{
|
||||
.backend = kv,
|
||||
.namespace = "default",
|
||||
.allocator = allocator,
|
||||
.io = io,
|
||||
.memory = memory,
|
||||
};
|
||||
}
|
||||
|
||||
/// Deinitialize the cache
|
||||
pub fn deinit(_: *Cache) void {
|
||||
const io = if (state) |current| current.io else return;
|
||||
state_mutex.lock(io);
|
||||
defer state_mutex.unlock(io);
|
||||
|
||||
if (state) |*current| {
|
||||
current.memory.deinit();
|
||||
state = null;
|
||||
pub fn deinit(self: *Cache) void {
|
||||
if (self.memory) |mem| {
|
||||
const allocator = mem.allocator;
|
||||
mem.deinit();
|
||||
allocator.destroy(mem);
|
||||
self.memory = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,14 +78,12 @@ pub fn scoped(self: Cache, comptime ns: @EnumLiteral()) Cache {
|
||||
return .{
|
||||
.backend = self.backend,
|
||||
.namespace = @tagName(ns),
|
||||
.allocator = self.allocator,
|
||||
.io = self.io,
|
||||
.memory = self.memory,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn scope(self: Cache, comptime ns: @EnumLiteral()) Cache {
|
||||
return self.scoped(ns);
|
||||
}
|
||||
|
||||
/// The persistent backend bound to this handle's namespace.
|
||||
fn boundBackend(self: Cache) Kv {
|
||||
return .{
|
||||
.vtable = self.backend.vtable,
|
||||
@ -122,30 +93,29 @@ fn boundBackend(self: Cache) Kv {
|
||||
}
|
||||
|
||||
pub fn get(self: Cache, allocator: std.mem.Allocator, key: []const u8) !?[]u8 {
|
||||
const io = if (state) |current| current.io else return null;
|
||||
state_mutex.lock(io);
|
||||
defer state_mutex.unlock(io);
|
||||
const io = self.io;
|
||||
const memory = self.memory orelse return error.CacheUnavailable;
|
||||
|
||||
const s = if (state) |*current| current else return null;
|
||||
const scoped_key = try scopedKey(s.allocator, self.namespace, key);
|
||||
defer s.allocator.free(scoped_key);
|
||||
const scoped_key = try scopedKey(allocator, self.namespace, key);
|
||||
defer allocator.free(scoped_key);
|
||||
|
||||
if (s.memory.get(scoped_key)) |entry| {
|
||||
if (memory.get(scoped_key)) |entry| {
|
||||
defer entry.release();
|
||||
return @as(?[]u8, try allocator.dupe(u8, entry.value.bytes));
|
||||
}
|
||||
|
||||
const backend = self.boundBackend();
|
||||
const encoded = (try backend.get(s.allocator, key)) orelse return null;
|
||||
defer s.allocator.free(encoded);
|
||||
const encoded = (try backend.get(allocator, key)) orelse return null;
|
||||
defer allocator.free(encoded);
|
||||
|
||||
const decoded = try decodeStoredEntry(encoded);
|
||||
if (isExpired(s.io, decoded.expires_at)) {
|
||||
if (isExpired(io, decoded.expires_at)) {
|
||||
try backend.delete(key);
|
||||
_ = memory.del(scoped_key);
|
||||
return null;
|
||||
}
|
||||
|
||||
putMemoryEntryLocked(s, scoped_key, decoded.payload, ttlFromExpiration(s.io, decoded.expires_at)) catch {};
|
||||
try putMemoryEntryLocked(self, scoped_key, decoded.payload, ttlFromExpiration(io, decoded.expires_at));
|
||||
return @as(?[]u8, try allocator.dupe(u8, decoded.payload));
|
||||
}
|
||||
|
||||
@ -166,29 +136,31 @@ pub fn as(self: Cache, allocator: std.mem.Allocator, key: []const u8, comptime T
|
||||
}
|
||||
|
||||
pub fn put(self: Cache, key: []const u8, value: []const u8, opts: PutOptions) !void {
|
||||
const io = if (state) |current| current.io else return;
|
||||
state_mutex.lock(io);
|
||||
defer state_mutex.unlock(io);
|
||||
_ = self.memory orelse return error.CacheUnavailable;
|
||||
|
||||
const s = if (state) |*current| current else return;
|
||||
const encoded = try encodeStoredEntry(s.io, s.allocator, value, opts);
|
||||
defer s.allocator.free(encoded);
|
||||
const io = self.io;
|
||||
const allocator = self.allocator;
|
||||
const encoded = try encodeStoredEntry(io, allocator, value, opts);
|
||||
defer allocator.free(encoded);
|
||||
|
||||
try self.boundBackend().put(key, encoded, .{});
|
||||
|
||||
const scoped_key = try scopedKey(s.allocator, self.namespace, key);
|
||||
defer s.allocator.free(scoped_key);
|
||||
try putMemoryEntryLocked(s, scoped_key, value, ttlFromOptions(s.io, opts));
|
||||
const scoped_key = try scopedKey(allocator, self.namespace, key);
|
||||
defer allocator.free(scoped_key);
|
||||
try putMemoryEntryLocked(self, scoped_key, value, ttlFromOptions(io, opts));
|
||||
}
|
||||
|
||||
pub fn putAs(self: Cache, key: []const u8, value: anytype, opts: PutOptions) !void {
|
||||
_ = self.memory orelse return error.CacheUnavailable;
|
||||
|
||||
const ValueType = @TypeOf(value);
|
||||
const TypedValue = struct {
|
||||
hash: u64,
|
||||
value: ValueType,
|
||||
};
|
||||
|
||||
var writer = std.Io.Writer.Allocating.init(zx.allocator);
|
||||
const allocator = self.allocator;
|
||||
var writer = std.Io.Writer.Allocating.init(allocator);
|
||||
defer writer.deinit();
|
||||
|
||||
try zx.util.zxon.serialize(TypedValue{
|
||||
@ -200,45 +172,39 @@ pub fn putAs(self: Cache, key: []const u8, value: anytype, opts: PutOptions) !vo
|
||||
}
|
||||
|
||||
pub fn delete(self: Cache, key: []const u8) !void {
|
||||
const io = if (state) |current| current.io else return;
|
||||
state_mutex.lock(io);
|
||||
defer state_mutex.unlock(io);
|
||||
const memory = self.memory orelse return error.CacheUnavailable;
|
||||
const allocator = self.allocator;
|
||||
const scoped_key = try scopedKey(allocator, self.namespace, key);
|
||||
defer allocator.free(scoped_key);
|
||||
|
||||
const s = if (state) |*current| current else return;
|
||||
const scoped_key = try scopedKey(s.allocator, self.namespace, key);
|
||||
defer s.allocator.free(scoped_key);
|
||||
|
||||
_ = s.memory.del(scoped_key);
|
||||
_ = memory.del(scoped_key);
|
||||
try self.boundBackend().delete(key);
|
||||
}
|
||||
|
||||
pub fn list(self: Cache, allocator: std.mem.Allocator, prefix: []const u8) ![][]u8 {
|
||||
const io = if (state) |current| current.io else return &[_][]u8{};
|
||||
state_mutex.lock(io);
|
||||
defer state_mutex.unlock(io);
|
||||
|
||||
const s = if (state) |*current| current else return &[_][]u8{};
|
||||
const io = self.io;
|
||||
const memory = self.memory orelse return error.CacheUnavailable;
|
||||
const backend = self.boundBackend();
|
||||
const keys = try backend.list(s.allocator, prefix);
|
||||
const keys = try backend.list(allocator, prefix);
|
||||
defer {
|
||||
for (keys) |key| s.allocator.free(key);
|
||||
s.allocator.free(keys);
|
||||
for (keys) |key| allocator.free(key);
|
||||
allocator.free(keys);
|
||||
}
|
||||
|
||||
var live_keys: std.ArrayList([]u8) = .empty;
|
||||
defer live_keys.deinit(allocator);
|
||||
|
||||
for (keys) |key| {
|
||||
const encoded = (try backend.get(s.allocator, key)) orelse continue;
|
||||
defer s.allocator.free(encoded);
|
||||
const encoded = (try backend.get(allocator, key)) orelse continue;
|
||||
defer allocator.free(encoded);
|
||||
|
||||
const decoded = try decodeStoredEntry(encoded);
|
||||
if (isExpired(s.io, decoded.expires_at)) {
|
||||
if (isExpired(io, decoded.expires_at)) {
|
||||
try backend.delete(key);
|
||||
|
||||
const scoped_key = try scopedKey(s.allocator, self.namespace, key);
|
||||
defer s.allocator.free(scoped_key);
|
||||
_ = s.memory.del(scoped_key);
|
||||
const scoped_key = try scopedKey(allocator, self.namespace, key);
|
||||
defer allocator.free(scoped_key);
|
||||
_ = memory.del(scoped_key);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -249,46 +215,40 @@ pub fn list(self: Cache, allocator: std.mem.Allocator, prefix: []const u8) ![][]
|
||||
}
|
||||
|
||||
pub fn del(self: Cache, key: []const u8) bool {
|
||||
const io = if (state) |current| current.io else return false;
|
||||
state_mutex.lock(io);
|
||||
defer state_mutex.unlock(io);
|
||||
|
||||
const s = if (state) |*current| current else return false;
|
||||
const memory = self.memory orelse return false;
|
||||
const allocator = self.allocator;
|
||||
const backend = self.boundBackend();
|
||||
const existing = backend.get(s.allocator, key) catch null;
|
||||
const existing = backend.get(allocator, key) catch null;
|
||||
const existed = if (existing) |bytes| blk: {
|
||||
s.allocator.free(bytes);
|
||||
allocator.free(bytes);
|
||||
break :blk true;
|
||||
} else false;
|
||||
|
||||
const scoped_key = scopedKey(s.allocator, self.namespace, key) catch return existed;
|
||||
defer s.allocator.free(scoped_key);
|
||||
const scoped_key = scopedKey(allocator, self.namespace, key) catch return existed;
|
||||
defer allocator.free(scoped_key);
|
||||
|
||||
const memory_deleted = s.memory.del(scoped_key);
|
||||
const memory_deleted = memory.del(scoped_key);
|
||||
backend.delete(key) catch {};
|
||||
return existed or memory_deleted;
|
||||
}
|
||||
|
||||
pub fn delPrefix(self: Cache, prefix: []const u8) !usize {
|
||||
const io = if (state) |current| current.io else return 0;
|
||||
state_mutex.lock(io);
|
||||
defer state_mutex.unlock(io);
|
||||
|
||||
const s = if (state) |*current| current else return 0;
|
||||
const memory = self.memory orelse return error.CacheUnavailable;
|
||||
const allocator = self.allocator;
|
||||
const backend = self.boundBackend();
|
||||
const keys = try backend.list(s.allocator, prefix);
|
||||
const keys = try backend.list(allocator, prefix);
|
||||
defer {
|
||||
for (keys) |key| s.allocator.free(key);
|
||||
s.allocator.free(keys);
|
||||
for (keys) |key| allocator.free(key);
|
||||
allocator.free(keys);
|
||||
}
|
||||
|
||||
var deleted: usize = 0;
|
||||
for (keys) |key| {
|
||||
try backend.delete(key);
|
||||
|
||||
const scoped_key = try scopedKey(s.allocator, self.namespace, key);
|
||||
defer s.allocator.free(scoped_key);
|
||||
_ = s.memory.del(scoped_key);
|
||||
const scoped_key = try scopedKey(allocator, self.namespace, key);
|
||||
defer allocator.free(scoped_key);
|
||||
_ = memory.del(scoped_key);
|
||||
deleted += 1;
|
||||
}
|
||||
|
||||
@ -354,12 +314,14 @@ fn isExpired(io: std.Io, expires_at: ?u64) bool {
|
||||
return expiration <= now(io);
|
||||
}
|
||||
|
||||
fn putMemoryEntryLocked(s: *State, scoped_key: []const u8, value: []const u8, ttl_seconds: ?u32) !void {
|
||||
const value_copy = try s.allocator.dupe(u8, value);
|
||||
errdefer s.allocator.free(value_copy);
|
||||
fn putMemoryEntryLocked(self: Cache, scoped_key: []const u8, value: []const u8, ttl_seconds: ?u32) !void {
|
||||
const io = self.io;
|
||||
const memory = self.memory orelse return error.CacheUnavailable;
|
||||
const value_copy = try memory.allocator.dupe(u8, value);
|
||||
errdefer memory.allocator.free(value_copy);
|
||||
|
||||
try s.memory.put(scoped_key, .{ .bytes = value_copy }, .{
|
||||
.ttl = cachezSafeTtl(s.io, ttl_seconds),
|
||||
try memory.put(scoped_key, .{ .bytes = value_copy }, .{
|
||||
.ttl = cachezSafeTtl(io, ttl_seconds),
|
||||
});
|
||||
}
|
||||
|
||||
@ -389,7 +351,9 @@ fn storedTypeHash(raw: []const u8) !u64 {
|
||||
}
|
||||
|
||||
pub const failing: Cache = .{
|
||||
.backend = Kv.failing,
|
||||
.allocator = .failing,
|
||||
.io = .failing,
|
||||
.backend = .failing,
|
||||
};
|
||||
|
||||
const cachez_failing = struct {
|
||||
|
||||
@ -94,7 +94,7 @@ pub fn list(self: Kv, allocator: std.mem.Allocator, prefix: []const u8) ![][]u8
|
||||
return self.vtable.list(self.userdata, self.namespace, allocator, prefix);
|
||||
}
|
||||
|
||||
pub fn scope(self: Kv, comptime ns: @EnumLiteral()) Kv {
|
||||
pub fn scoped(self: Kv, comptime ns: @EnumLiteral()) Kv {
|
||||
return .{ .userdata = self.userdata, .vtable = self.vtable, .namespace = @tagName(ns) };
|
||||
}
|
||||
|
||||
|
||||
@ -102,7 +102,11 @@ pub fn render(self: zx.Component, writer: *std.Io.Writer, options: RenderOptions
|
||||
|
||||
if (generated_key) |key| {
|
||||
// Try to get from cache
|
||||
if (try zx.cache.get(func.allocator, key)) |cached_html| {
|
||||
const cached = zx.cache.get(func.allocator, key) catch |err| switch (err) {
|
||||
error.CacheUnavailable => null,
|
||||
else => return err,
|
||||
};
|
||||
if (cached) |cached_html| {
|
||||
defer func.allocator.free(cached_html);
|
||||
try writer.writeAll(cached_html);
|
||||
return;
|
||||
@ -114,7 +118,10 @@ pub fn render(self: zx.Component, writer: *std.Io.Writer, options: RenderOptions
|
||||
try render(component, &buf_writer.writer, options);
|
||||
|
||||
const rendered = buf_writer.written();
|
||||
try zx.cache.put(key, rendered, .{ .expiration_ttl = caching.seconds });
|
||||
zx.cache.put(key, rendered, .{ .expiration_ttl = caching.seconds }) catch |err| switch (err) {
|
||||
error.CacheUnavailable => {},
|
||||
else => return err,
|
||||
};
|
||||
|
||||
// Write to actual output
|
||||
try writer.writeAll(rendered);
|
||||
|
||||
@ -13,6 +13,9 @@ fn scopedCache(namespace: []const u8) zx.Cache {
|
||||
return .{
|
||||
.backend = cache.backend,
|
||||
.namespace = namespace,
|
||||
.allocator = cache.allocator,
|
||||
.io = cache.io,
|
||||
.memory = cache.memory,
|
||||
};
|
||||
}
|
||||
|
||||
@ -37,7 +40,7 @@ const WorkerContext = struct {
|
||||
const worker_iterations = 100;
|
||||
|
||||
fn ensureCache() !void {
|
||||
cache = try zx.Cache.init(std.testing.io, cache_fs.kv(), .{
|
||||
cache = try zx.Cache.init(std.testing.io, allocator, cache_fs.kv(), .{
|
||||
.max_size = 4096,
|
||||
.segment_count = 8,
|
||||
});
|
||||
|
||||
@ -93,7 +93,7 @@ test "scoped namespaces are isolated" {
|
||||
var ns_buf: [64]u8 = undefined;
|
||||
const namespace = try uniqueLabel("kv-scope", &ns_buf);
|
||||
|
||||
// Runtime namespace: set the field directly (scope() takes a comptime literal).
|
||||
// Runtime namespace: set the field directly (scoped() takes a comptime literal).
|
||||
const scoped = zx.Kv{ .vtable = kv.vtable, .userdata = kv.userdata, .namespace = namespace };
|
||||
const key = "shared-key";
|
||||
|
||||
@ -113,8 +113,8 @@ test "scoped namespaces are isolated" {
|
||||
try std.testing.expectEqualStrings("default-value", default_value);
|
||||
}
|
||||
|
||||
test "scope() enum literal routes to a distinct namespace" {
|
||||
const users = kv.scope(.users);
|
||||
test "scoped() enum literal routes to a distinct namespace" {
|
||||
const users = kv.scoped(.users);
|
||||
const key = "u-1";
|
||||
|
||||
defer users.delete(key) catch {};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user