diff --git a/src/cache/Cache.zig b/src/cache/Cache.zig index 29fc69f..a06c6ac 100644 --- a/src/cache/Cache.zig +++ b/src/cache/Cache.zig @@ -14,18 +14,21 @@ pub const Config = struct { cache_dir: []const u8, }; -pub fn init(allocator: std.mem.Allocator, config: Config) !Cache { +pub fn init(allocator: std.mem.Allocator, config: Config) !*Cache { std.fs.makeDirAbsolute(config.cache_dir) catch |err| { if (err != error.PathAlreadyExists) return err; }; - var cache = Cache{ + const cache = try allocator.create(Cache); + errdefer allocator.destroy(cache); + + cache.* = Cache{ .allocator = allocator, .lru = try Lru.init(allocator, config.max_entries), .cache_dir = try allocator.dupe(u8, config.cache_dir), }; - cache.lru.setEvictionCallback(&cache, evictCallback); + cache.lru.setEvictionCallback(cache, evictCallback); // Clean up expired files and populate L1 cache from L2 cache.loadFromDir() catch |err| { @@ -69,6 +72,7 @@ pub fn put(self: *Cache, key: []const u8, value: []const u8, ttl_seconds: u64) ! pub fn deinit(self: *Cache) void { self.lru.deinit(); self.allocator.free(self.cache_dir); + self.allocator.destroy(self); } fn getCacheFilename(self: *Cache, key: []const u8) ![]const u8 { @@ -247,7 +251,7 @@ test "L1/L2 cache flow" { var path_buf: [std.fs.max_path_bytes]u8 = undefined; const cache_dir = try tmp_dir.dir.realpath(".", &path_buf); - var cache = try Cache.init(allocator, .{ .max_entries = 10, .cache_dir = cache_dir }); + const cache = try Cache.init(allocator, .{ .max_entries = 10, .cache_dir = cache_dir }); defer cache.deinit(); // Put item in cache @@ -271,3 +275,33 @@ test "L1/L2 cache flow" { // Now it should be in L1 try std.testing.expectEqualStrings("value1", cache.lru.get("key1").?); } + +test "expired cache entry returns null" { + const allocator = std.testing.allocator; + + var tmp_dir = std.testing.tmpDir(.{}); + defer tmp_dir.cleanup(); + + var path_buf: [std.fs.max_path_bytes]u8 = undefined; + const cache_dir = try tmp_dir.dir.realpath(".", &path_buf); + + const cache = try Cache.init(allocator, .{ .max_entries = 10, .cache_dir = cache_dir }); + defer cache.deinit(); + + // Put item with past expiration time + const now = std.time.milliTimestamp(); + const past_expires = now - 1000; + + // Manually insert expired entry into L1 + const key_copy = try allocator.dupe(u8, "key1"); + const value_copy = try allocator.dupe(u8, "value1"); + try cache.lru.map.put(key_copy, .{ + .value = value_copy, + .expires = past_expires, + .access_count = 0, + }); + + // Get should return null for expired entry + const result = cache.get("key1"); + try std.testing.expect(result == null); +} diff --git a/src/main.zig b/src/main.zig index 4d7c873..74e40cb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -49,7 +49,7 @@ pub fn main() !void { // Initialize location resolver var resolver = Resolver.init(allocator, &geoip, &geocache, &airports_db); - var cache = try Cache.init(allocator, .{ + const cache = try Cache.init(allocator, .{ .max_entries = cfg.cache_size, .cache_dir = cfg.cache_dir, }); @@ -66,7 +66,7 @@ pub fn main() !void { defer metno.deinit(); var server = try Server.init(allocator, cfg.listen_host, cfg.listen_port, .{ - .provider = metno.provider(&cache), + .provider = metno.provider(cache), .resolver = &resolver, .geoip = &geoip, }, &rate_limiter);