mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-31 09:29:42 +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) {
|
if (!proceed) {
|
||||||
switch (cached.data) {
|
switch (cached.data) {
|
||||||
.buffer => |_| {},
|
.buffer => |_| {},
|
||||||
.file => |file| file.close(),
|
.file => |f| f.file.close(),
|
||||||
}
|
}
|
||||||
req.error_callback(req.ctx, error.Abort);
|
req.error_callback(req.ctx, error.Abort);
|
||||||
return;
|
return;
|
||||||
@@ -337,18 +337,24 @@ fn serveFromCache(req: Request, cached: *const CachedResponse) !void {
|
|||||||
try req.data_callback(response, data);
|
try req.data_callback(response, data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.file => |file| {
|
.file => |f| {
|
||||||
|
const file = f.file;
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
var buf: [1024]u8 = undefined;
|
var buf: [1024]u8 = undefined;
|
||||||
var file_reader = file.reader(&buf);
|
var file_reader = file.reader(&buf);
|
||||||
|
try file_reader.seekTo(f.offset);
|
||||||
const reader = &file_reader.interface;
|
const reader = &file_reader.interface;
|
||||||
var read_buf: [1024]u8 = undefined;
|
|
||||||
|
|
||||||
while (true) {
|
var read_buf: [1024]u8 = undefined;
|
||||||
const curr = try reader.readSliceShort(&read_buf);
|
var remaining = f.len;
|
||||||
if (curr == 0) break;
|
|
||||||
try req.data_callback(response, read_buf[0..curr]);
|
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(),
|
.transfer => |t| t.getContentLength(),
|
||||||
.cached => |c| switch (c.data) {
|
.cached => |c| switch (c.data) {
|
||||||
.buffer => |buf| @intCast(buf.len),
|
.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) {
|
pub const CachedData = union(enum) {
|
||||||
buffer: []const u8,
|
buffer: []const u8,
|
||||||
|
file: struct {
|
||||||
file: std.fs.File,
|
file: std.fs.File,
|
||||||
|
offset: usize,
|
||||||
|
len: usize,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CachedResponse = struct {
|
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,
|
dir: std.fs.Dir,
|
||||||
locks: [LOCK_STRIPES]std.Thread.Mutex = .{std.Thread.Mutex{}} ** LOCK_STRIPES,
|
locks: [LOCK_STRIPES]std.Thread.Mutex = .{std.Thread.Mutex{}} ** LOCK_STRIPES,
|
||||||
|
|
||||||
const CacheMetadataFile = struct {
|
const CacheMetadataJson = struct {
|
||||||
version: usize,
|
version: usize,
|
||||||
metadata: CachedMetadata,
|
metadata: CachedMetadata,
|
||||||
};
|
};
|
||||||
@@ -44,8 +44,9 @@ fn getLockPtr(self: *FsCache, key: *const [HASHED_KEY_LEN]u8) *std.Thread.Mutex
|
|||||||
return &self.locks[lock_idx];
|
return &self.locks[lock_idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BODY_LEN_HEADER_LEN = 8;
|
||||||
const HASHED_KEY_LEN = 64;
|
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;
|
const HASHED_TMP_PATH_LEN = HASHED_PATH_LEN + 4;
|
||||||
|
|
||||||
fn hashKey(key: []const u8) [HASHED_KEY_LEN]u8 {
|
fn hashKey(key: []const u8) [HASHED_KEY_LEN]u8 {
|
||||||
@@ -56,27 +57,15 @@ fn hashKey(key: []const u8) [HASHED_KEY_LEN]u8 {
|
|||||||
return hex;
|
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;
|
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;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bodyPath(hashed_key: *const [HASHED_KEY_LEN]u8) [HASHED_PATH_LEN]u8 {
|
fn cacheTmpPath(hashed_key: *const [HASHED_KEY_LEN]u8) [HASHED_TMP_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 {
|
|
||||||
var path: [HASHED_TMP_PATH_LEN]u8 = undefined;
|
var path: [HASHED_TMP_PATH_LEN]u8 = undefined;
|
||||||
_ = std.fmt.bufPrint(&path, "{s}.meta.tmp", .{hashed_key}) catch unreachable;
|
_ = std.fmt.bufPrint(&path, "{s}.cache.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;
|
|
||||||
return path;
|
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 {
|
pub fn get(self: *FsCache, arena: std.mem.Allocator, req: CacheRequest) ?Cache.CachedResponse {
|
||||||
const hashed_key = hashKey(req.url);
|
const hashed_key = hashKey(req.url);
|
||||||
const meta_p = metaPath(&hashed_key);
|
const cache_p = cachePath(&hashed_key);
|
||||||
const body_p = bodyPath(&hashed_key);
|
|
||||||
|
|
||||||
const lock = self.getLockPtr(&hashed_key);
|
const lock = self.getLockPtr(&hashed_key);
|
||||||
lock.lock();
|
lock.lock();
|
||||||
defer lock.unlock();
|
defer lock.unlock();
|
||||||
|
|
||||||
const meta_file = self.dir.openFile(&meta_p, .{ .mode = .read_only }) catch return null;
|
const file = self.dir.openFile(&cache_p, .{ .mode = .read_only }) catch return null;
|
||||||
defer meta_file.close();
|
errdefer file.close();
|
||||||
|
|
||||||
const contents = meta_file.readToEndAlloc(arena, 1 * 1024 * 1024) catch return null;
|
var file_buf: [1024]u8 = undefined;
|
||||||
defer arena.free(contents);
|
var len_buf: [BODY_LEN_HEADER_LEN]u8 = undefined;
|
||||||
|
|
||||||
const cache_file: CacheMetadataFile = std.json.parseFromSliceLeaky(
|
var file_reader = file.reader(&file_buf);
|
||||||
CacheMetadataFile,
|
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,
|
arena,
|
||||||
contents,
|
&json_reader,
|
||||||
.{ .allocate = .alloc_always },
|
.{
|
||||||
|
.allocate = .alloc_always,
|
||||||
|
},
|
||||||
) catch {
|
) catch {
|
||||||
self.dir.deleteFile(&meta_p) catch {};
|
self.dir.deleteFile(&cache_p) catch {};
|
||||||
self.dir.deleteFile(&body_p) catch {};
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const metadata = cache_file.metadata;
|
|
||||||
|
|
||||||
if (cache_file.version != CACHE_VERSION) {
|
if (cache_file.version != CACHE_VERSION) {
|
||||||
self.dir.deleteFile(&meta_p) catch {};
|
self.dir.deleteFile(&cache_p) catch {};
|
||||||
self.dir.deleteFile(&body_p) catch {};
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const metadata = cache_file.metadata;
|
||||||
|
|
||||||
const now = req.timestamp;
|
const now = req.timestamp;
|
||||||
const age = (now - metadata.stored_at) + @as(i64, @intCast(metadata.age_at_store));
|
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) {
|
if (age < 0 or @as(u64, @intCast(age)) >= metadata.cache_control.max_age) {
|
||||||
self.dir.deleteFile(&meta_p) catch {};
|
self.dir.deleteFile(&cache_p) catch {};
|
||||||
self.dir.deleteFile(&body_p) catch {};
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const body_file = self.dir.openFile(
|
|
||||||
&body_p,
|
|
||||||
.{ .mode = .read_only },
|
|
||||||
) catch return null;
|
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.metadata = metadata,
|
.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 {
|
pub fn put(self: *FsCache, meta: CachedMetadata, body: []const u8) !void {
|
||||||
const hashed_key = hashKey(meta.url);
|
const hashed_key = hashKey(meta.url);
|
||||||
const meta_p = metaPath(&hashed_key);
|
const cache_p = cachePath(&hashed_key);
|
||||||
const meta_tmp_p = metaTmpPath(&hashed_key);
|
const cache_tmp_p = cacheTmpPath(&hashed_key);
|
||||||
const body_p = bodyPath(&hashed_key);
|
|
||||||
const body_tmp_p = bodyTmpPath(&hashed_key);
|
|
||||||
var writer_buf: [512]u8 = undefined;
|
|
||||||
|
|
||||||
const lock = self.getLockPtr(&hashed_key);
|
const lock = self.getLockPtr(&hashed_key);
|
||||||
lock.lock();
|
lock.lock();
|
||||||
defer lock.unlock();
|
defer lock.unlock();
|
||||||
|
|
||||||
{
|
const file = try self.dir.createFile(&cache_tmp_p, .{});
|
||||||
const meta_file = try self.dir.createFile(&meta_tmp_p, .{});
|
defer file.close();
|
||||||
errdefer {
|
|
||||||
meta_file.close();
|
var writer_buf: [1024]u8 = undefined;
|
||||||
self.dir.deleteFile(&meta_tmp_p) catch {};
|
|
||||||
}
|
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(
|
try std.json.Stringify.value(
|
||||||
CacheMetadataFile{ .version = CACHE_VERSION, .metadata = meta },
|
CacheMetadataJson{ .version = CACHE_VERSION, .metadata = meta },
|
||||||
.{ .whitespace = .minified },
|
.{ .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);
|
|
||||||
|
|
||||||
{
|
try file_writer_iface.flush();
|
||||||
const body_file = try self.dir.createFile(&body_tmp_p, .{});
|
try self.dir.rename(&cache_tmp_p, &cache_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
@@ -209,9 +188,9 @@ test "FsCache: basic put and get" {
|
|||||||
const path = try tmp.dir.realpathAlloc(testing.allocator, ".");
|
const path = try tmp.dir.realpathAlloc(testing.allocator, ".");
|
||||||
defer testing.allocator.free(path);
|
defer testing.allocator.free(path);
|
||||||
|
|
||||||
var fs_cache = try FsCache.init(path);
|
const fs_cache = try FsCache.init(path);
|
||||||
defer fs_cache.deinit();
|
|
||||||
var cache = Cache{ .kind = .{ .fs = fs_cache } };
|
var cache = Cache{ .kind = .{ .fs = fs_cache } };
|
||||||
|
defer cache.deinit();
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
@@ -233,15 +212,20 @@ test "FsCache: basic put and get" {
|
|||||||
const body = "hello world";
|
const body = "hello world";
|
||||||
try cache.put(meta, body);
|
try cache.put(meta, body);
|
||||||
|
|
||||||
const result = cache.get(arena.allocator(), .{ .url = "https://example.com", .timestamp = now }) orelse return error.CacheMiss;
|
const result = cache.get(
|
||||||
defer result.data.file.close();
|
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 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);
|
defer testing.allocator.free(read_buf);
|
||||||
|
|
||||||
try testing.expectEqualStrings(body, read_buf);
|
try testing.expectEqualStrings(body, read_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,9 +236,9 @@ test "FsCache: get expiration" {
|
|||||||
const path = try tmp.dir.realpathAlloc(testing.allocator, ".");
|
const path = try tmp.dir.realpathAlloc(testing.allocator, ".");
|
||||||
defer testing.allocator.free(path);
|
defer testing.allocator.free(path);
|
||||||
|
|
||||||
var fs_cache = try FsCache.init(path);
|
const fs_cache = try FsCache.init(path);
|
||||||
defer fs_cache.deinit();
|
|
||||||
var cache = Cache{ .kind = .{ .fs = fs_cache } };
|
var cache = Cache{ .kind = .{ .fs = fs_cache } };
|
||||||
|
defer cache.deinit();
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
@@ -282,7 +266,7 @@ test "FsCache: get expiration" {
|
|||||||
arena.allocator(),
|
arena.allocator(),
|
||||||
.{ .url = "https://example.com", .timestamp = now + 50 },
|
.{ .url = "https://example.com", .timestamp = now + 50 },
|
||||||
) orelse return error.CacheMiss;
|
) orelse return error.CacheMiss;
|
||||||
result.data.file.close();
|
result.data.file.file.close();
|
||||||
|
|
||||||
try testing.expectEqual(null, cache.get(
|
try testing.expectEqual(null, cache.get(
|
||||||
arena.allocator(),
|
arena.allocator(),
|
||||||
|
|||||||
Reference in New Issue
Block a user