mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-31 01:28:55 +00:00
switch to single file cache
This commit is contained in:
@@ -325,7 +325,7 @@ fn serveFromCache(req: Request, cached: *const CachedResponse) !void {
|
||||
if (!proceed) {
|
||||
switch (cached.data) {
|
||||
.buffer => |_| {},
|
||||
.file => |file| file.close(),
|
||||
.file => |f| f.file.close(),
|
||||
}
|
||||
req.error_callback(req.ctx, error.Abort);
|
||||
return;
|
||||
@@ -337,18 +337,24 @@ fn serveFromCache(req: Request, cached: *const CachedResponse) !void {
|
||||
try req.data_callback(response, data);
|
||||
}
|
||||
},
|
||||
.file => |file| {
|
||||
.file => |f| {
|
||||
const file = f.file;
|
||||
defer file.close();
|
||||
|
||||
var buf: [1024]u8 = undefined;
|
||||
var file_reader = file.reader(&buf);
|
||||
|
||||
try file_reader.seekTo(f.offset);
|
||||
const reader = &file_reader.interface;
|
||||
var read_buf: [1024]u8 = undefined;
|
||||
|
||||
while (true) {
|
||||
const curr = try reader.readSliceShort(&read_buf);
|
||||
if (curr == 0) break;
|
||||
try req.data_callback(response, read_buf[0..curr]);
|
||||
var read_buf: [1024]u8 = undefined;
|
||||
var remaining = f.len;
|
||||
|
||||
while (remaining > 0) {
|
||||
const read_len = @min(read_buf.len, remaining);
|
||||
const n = try reader.readSliceShort(read_buf[0..read_len]);
|
||||
if (n == 0) break;
|
||||
remaining -= n;
|
||||
try req.data_callback(response, read_buf[0..n]);
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1133,7 +1139,7 @@ pub const Response = struct {
|
||||
.transfer => |t| t.getContentLength(),
|
||||
.cached => |c| switch (c.data) {
|
||||
.buffer => |buf| @intCast(buf.len),
|
||||
.file => |f| @intCast(f.getEndPos() catch 0),
|
||||
.file => |f| @intCast(f.len),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
4
src/network/cache/Cache.zig
vendored
4
src/network/cache/Cache.zig
vendored
@@ -137,7 +137,11 @@ pub const CacheRequest = struct {
|
||||
|
||||
pub const CachedData = union(enum) {
|
||||
buffer: []const u8,
|
||||
file: struct {
|
||||
file: std.fs.File,
|
||||
offset: usize,
|
||||
len: usize,
|
||||
},
|
||||
};
|
||||
|
||||
pub const CachedResponse = struct {
|
||||
|
||||
162
src/network/cache/FsCache.zig
vendored
162
src/network/cache/FsCache.zig
vendored
@@ -34,7 +34,7 @@ pub const FsCache = @This();
|
||||
dir: std.fs.Dir,
|
||||
locks: [LOCK_STRIPES]std.Thread.Mutex = .{std.Thread.Mutex{}} ** LOCK_STRIPES,
|
||||
|
||||
const CacheMetadataFile = struct {
|
||||
const CacheMetadataJson = struct {
|
||||
version: usize,
|
||||
metadata: CachedMetadata,
|
||||
};
|
||||
@@ -44,8 +44,9 @@ fn getLockPtr(self: *FsCache, key: *const [HASHED_KEY_LEN]u8) *std.Thread.Mutex
|
||||
return &self.locks[lock_idx];
|
||||
}
|
||||
|
||||
const BODY_LEN_HEADER_LEN = 8;
|
||||
const HASHED_KEY_LEN = 64;
|
||||
const HASHED_PATH_LEN = HASHED_KEY_LEN + 5;
|
||||
const HASHED_PATH_LEN = HASHED_KEY_LEN + 6;
|
||||
const HASHED_TMP_PATH_LEN = HASHED_PATH_LEN + 4;
|
||||
|
||||
fn hashKey(key: []const u8) [HASHED_KEY_LEN]u8 {
|
||||
@@ -56,27 +57,15 @@ fn hashKey(key: []const u8) [HASHED_KEY_LEN]u8 {
|
||||
return hex;
|
||||
}
|
||||
|
||||
fn metaPath(hashed_key: *const [HASHED_KEY_LEN]u8) [HASHED_PATH_LEN]u8 {
|
||||
fn cachePath(hashed_key: *const [HASHED_KEY_LEN]u8) [HASHED_PATH_LEN]u8 {
|
||||
var path: [HASHED_PATH_LEN]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(&path, "{s}.meta", .{hashed_key}) catch unreachable;
|
||||
_ = std.fmt.bufPrint(&path, "{s}.cache", .{hashed_key}) catch unreachable;
|
||||
return path;
|
||||
}
|
||||
|
||||
fn bodyPath(hashed_key: *const [HASHED_KEY_LEN]u8) [HASHED_PATH_LEN]u8 {
|
||||
var path: [HASHED_PATH_LEN]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(&path, "{s}.body", .{hashed_key}) catch unreachable;
|
||||
return path;
|
||||
}
|
||||
|
||||
fn metaTmpPath(hashed_key: *const [HASHED_KEY_LEN]u8) [HASHED_TMP_PATH_LEN]u8 {
|
||||
fn cacheTmpPath(hashed_key: *const [HASHED_KEY_LEN]u8) [HASHED_TMP_PATH_LEN]u8 {
|
||||
var path: [HASHED_TMP_PATH_LEN]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(&path, "{s}.meta.tmp", .{hashed_key}) catch unreachable;
|
||||
return path;
|
||||
}
|
||||
|
||||
fn bodyTmpPath(hashed_key: *const [HASHED_KEY_LEN]u8) [HASHED_TMP_PATH_LEN]u8 {
|
||||
var path: [HASHED_TMP_PATH_LEN]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(&path, "{s}.body.tmp", .{hashed_key}) catch unreachable;
|
||||
_ = std.fmt.bufPrint(&path, "{s}.cache.tmp", .{hashed_key}) catch unreachable;
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -98,106 +87,96 @@ pub fn deinit(self: *FsCache) void {
|
||||
|
||||
pub fn get(self: *FsCache, arena: std.mem.Allocator, req: CacheRequest) ?Cache.CachedResponse {
|
||||
const hashed_key = hashKey(req.url);
|
||||
const meta_p = metaPath(&hashed_key);
|
||||
const body_p = bodyPath(&hashed_key);
|
||||
const cache_p = cachePath(&hashed_key);
|
||||
|
||||
const lock = self.getLockPtr(&hashed_key);
|
||||
lock.lock();
|
||||
defer lock.unlock();
|
||||
|
||||
const meta_file = self.dir.openFile(&meta_p, .{ .mode = .read_only }) catch return null;
|
||||
defer meta_file.close();
|
||||
const file = self.dir.openFile(&cache_p, .{ .mode = .read_only }) catch return null;
|
||||
errdefer file.close();
|
||||
|
||||
const contents = meta_file.readToEndAlloc(arena, 1 * 1024 * 1024) catch return null;
|
||||
defer arena.free(contents);
|
||||
var file_buf: [1024]u8 = undefined;
|
||||
var len_buf: [BODY_LEN_HEADER_LEN]u8 = undefined;
|
||||
|
||||
const cache_file: CacheMetadataFile = std.json.parseFromSliceLeaky(
|
||||
CacheMetadataFile,
|
||||
var file_reader = file.reader(&file_buf);
|
||||
const file_reader_iface = &file_reader.interface;
|
||||
|
||||
file_reader_iface.readSliceAll(&len_buf) catch return null;
|
||||
const body_len = std.mem.readInt(u64, &len_buf, .little);
|
||||
|
||||
// Now we read metadata.
|
||||
file_reader.seekTo(body_len + BODY_LEN_HEADER_LEN) catch return null;
|
||||
|
||||
var json_reader = std.json.Reader.init(arena, file_reader_iface);
|
||||
const cache_file: CacheMetadataJson = std.json.parseFromTokenSourceLeaky(
|
||||
CacheMetadataJson,
|
||||
arena,
|
||||
contents,
|
||||
.{ .allocate = .alloc_always },
|
||||
&json_reader,
|
||||
.{
|
||||
.allocate = .alloc_always,
|
||||
},
|
||||
) catch {
|
||||
self.dir.deleteFile(&meta_p) catch {};
|
||||
self.dir.deleteFile(&body_p) catch {};
|
||||
self.dir.deleteFile(&cache_p) catch {};
|
||||
return null;
|
||||
};
|
||||
|
||||
const metadata = cache_file.metadata;
|
||||
|
||||
if (cache_file.version != CACHE_VERSION) {
|
||||
self.dir.deleteFile(&meta_p) catch {};
|
||||
self.dir.deleteFile(&body_p) catch {};
|
||||
self.dir.deleteFile(&cache_p) catch {};
|
||||
return null;
|
||||
}
|
||||
|
||||
const metadata = cache_file.metadata;
|
||||
|
||||
const now = req.timestamp;
|
||||
const age = (now - metadata.stored_at) + @as(i64, @intCast(metadata.age_at_store));
|
||||
if (age < 0 or @as(u64, @intCast(age)) >= metadata.cache_control.max_age) {
|
||||
self.dir.deleteFile(&meta_p) catch {};
|
||||
self.dir.deleteFile(&body_p) catch {};
|
||||
self.dir.deleteFile(&cache_p) catch {};
|
||||
return null;
|
||||
}
|
||||
|
||||
const body_file = self.dir.openFile(
|
||||
&body_p,
|
||||
.{ .mode = .read_only },
|
||||
) catch return null;
|
||||
|
||||
return .{
|
||||
.metadata = metadata,
|
||||
.data = .{ .file = body_file },
|
||||
.data = .{
|
||||
.file = .{
|
||||
.file = file,
|
||||
.offset = BODY_LEN_HEADER_LEN,
|
||||
.len = body_len,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn put(self: *FsCache, meta: CachedMetadata, body: []const u8) !void {
|
||||
const hashed_key = hashKey(meta.url);
|
||||
const meta_p = metaPath(&hashed_key);
|
||||
const meta_tmp_p = metaTmpPath(&hashed_key);
|
||||
const body_p = bodyPath(&hashed_key);
|
||||
const body_tmp_p = bodyTmpPath(&hashed_key);
|
||||
var writer_buf: [512]u8 = undefined;
|
||||
const cache_p = cachePath(&hashed_key);
|
||||
const cache_tmp_p = cacheTmpPath(&hashed_key);
|
||||
|
||||
const lock = self.getLockPtr(&hashed_key);
|
||||
lock.lock();
|
||||
defer lock.unlock();
|
||||
|
||||
{
|
||||
const meta_file = try self.dir.createFile(&meta_tmp_p, .{});
|
||||
errdefer {
|
||||
meta_file.close();
|
||||
self.dir.deleteFile(&meta_tmp_p) catch {};
|
||||
}
|
||||
const file = try self.dir.createFile(&cache_tmp_p, .{});
|
||||
defer file.close();
|
||||
|
||||
var writer_buf: [1024]u8 = undefined;
|
||||
|
||||
var file_writer = file.writer(&writer_buf);
|
||||
var file_writer_iface = &file_writer.interface;
|
||||
|
||||
var len_buf: [8]u8 = undefined;
|
||||
std.mem.writeInt(u64, &len_buf, body.len, .little);
|
||||
try file_writer_iface.writeAll(&len_buf);
|
||||
try file_writer_iface.writeAll(body);
|
||||
|
||||
var meta_file_writer = meta_file.writer(&writer_buf);
|
||||
const meta_file_writer_iface = &meta_file_writer.interface;
|
||||
try std.json.Stringify.value(
|
||||
CacheMetadataFile{ .version = CACHE_VERSION, .metadata = meta },
|
||||
CacheMetadataJson{ .version = CACHE_VERSION, .metadata = meta },
|
||||
.{ .whitespace = .minified },
|
||||
meta_file_writer_iface,
|
||||
file_writer_iface,
|
||||
);
|
||||
try meta_file_writer_iface.flush();
|
||||
meta_file.close();
|
||||
}
|
||||
errdefer self.dir.deleteFile(&meta_tmp_p) catch {};
|
||||
try self.dir.rename(&meta_tmp_p, &meta_p);
|
||||
|
||||
{
|
||||
const body_file = try self.dir.createFile(&body_tmp_p, .{});
|
||||
errdefer {
|
||||
body_file.close();
|
||||
self.dir.deleteFile(&body_tmp_p) catch {};
|
||||
}
|
||||
|
||||
var body_file_writer = body_file.writer(&writer_buf);
|
||||
const body_file_writer_iface = &body_file_writer.interface;
|
||||
try body_file_writer_iface.writeAll(body);
|
||||
try body_file_writer_iface.flush();
|
||||
body_file.close();
|
||||
}
|
||||
errdefer self.dir.deleteFile(&body_tmp_p) catch {};
|
||||
|
||||
errdefer self.dir.deleteFile(&meta_p) catch {};
|
||||
try self.dir.rename(&body_tmp_p, &body_p);
|
||||
try file_writer_iface.flush();
|
||||
try self.dir.rename(&cache_tmp_p, &cache_p);
|
||||
}
|
||||
|
||||
const testing = std.testing;
|
||||
@@ -209,9 +188,9 @@ test "FsCache: basic put and get" {
|
||||
const path = try tmp.dir.realpathAlloc(testing.allocator, ".");
|
||||
defer testing.allocator.free(path);
|
||||
|
||||
var fs_cache = try FsCache.init(path);
|
||||
defer fs_cache.deinit();
|
||||
const fs_cache = try FsCache.init(path);
|
||||
var cache = Cache{ .kind = .{ .fs = fs_cache } };
|
||||
defer cache.deinit();
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
@@ -233,15 +212,20 @@ test "FsCache: basic put and get" {
|
||||
const body = "hello world";
|
||||
try cache.put(meta, body);
|
||||
|
||||
const result = cache.get(arena.allocator(), .{ .url = "https://example.com", .timestamp = now }) orelse return error.CacheMiss;
|
||||
defer result.data.file.close();
|
||||
const result = cache.get(
|
||||
arena.allocator(),
|
||||
.{ .url = "https://example.com", .timestamp = now },
|
||||
) orelse return error.CacheMiss;
|
||||
const f = result.data.file;
|
||||
const file = f.file;
|
||||
defer file.close();
|
||||
|
||||
var buf: [64]u8 = undefined;
|
||||
var file_reader = result.data.file.reader(&buf);
|
||||
var file_reader = file.reader(&buf);
|
||||
try file_reader.seekTo(f.offset);
|
||||
|
||||
const read_buf = try file_reader.interface.allocRemaining(testing.allocator, .unlimited);
|
||||
const read_buf = try file_reader.interface.readAlloc(testing.allocator, f.len);
|
||||
defer testing.allocator.free(read_buf);
|
||||
|
||||
try testing.expectEqualStrings(body, read_buf);
|
||||
}
|
||||
|
||||
@@ -252,9 +236,9 @@ test "FsCache: get expiration" {
|
||||
const path = try tmp.dir.realpathAlloc(testing.allocator, ".");
|
||||
defer testing.allocator.free(path);
|
||||
|
||||
var fs_cache = try FsCache.init(path);
|
||||
defer fs_cache.deinit();
|
||||
const fs_cache = try FsCache.init(path);
|
||||
var cache = Cache{ .kind = .{ .fs = fs_cache } };
|
||||
defer cache.deinit();
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
@@ -282,7 +266,7 @@ test "FsCache: get expiration" {
|
||||
arena.allocator(),
|
||||
.{ .url = "https://example.com", .timestamp = now + 50 },
|
||||
) orelse return error.CacheMiss;
|
||||
result.data.file.close();
|
||||
result.data.file.file.close();
|
||||
|
||||
try testing.expectEqual(null, cache.get(
|
||||
arena.allocator(),
|
||||
|
||||
Reference in New Issue
Block a user