From 0402b1c7dbe549a7189ba8f4ab1ffb906117bfbd Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Thu, 1 Oct 2026 15:21:30 -1000 Subject: Keep clone files and oversized responses out of the cache The dumb transport reads files that already sit on the disk, so a pack copied into a slot cost that disk twice and the request a second write of every byte. A snapshot took a slot whatever its size, so a visitor naming distinct refs and ids could fill the cache root with archives. `cache-max-slot-size`, 64 MB unless set, now serves a larger response from the lock file and drops it, along with any expired copy it would have replaced. --- source/cache.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'source/cache.c') diff --git a/source/cache.c b/source/cache.c index 091c078..8c81f5c 100644 --- a/source/cache.c +++ b/source/cache.c @@ -60,6 +60,11 @@ struct cache_slot { // page has already reached the visitor and nothing more may be served // after it, not the lock file and not the stale copy still open. int abandoned; + // The largest page a slot may keep, with zero for no bound, and whether + // the fill went past it, in which case the lock file is served and then + // dropped instead of published. + size_t max_bytes; + int oversized; // The slot as it was when it was opened, or the lock file once // fill_slot has written a page into it. struct stat st; @@ -374,6 +379,7 @@ static int fill_slot(struct cache_slot *slot) // it copies after a fill is the lock file rather than the old slot. if (fstat(slot->lock_fd, &slot->st)) return errno; + slot->oversized = slot->max_bytes && (size_t)slot->st.st_size > slot->max_bytes; return 0; } @@ -398,7 +404,14 @@ static void refresh_slot(struct cache_slot *slot) close_lock(slot); } else { close_slot(slot); - publish_slot(slot); + if (slot->oversized) { + // The expired copy goes too, or every later request + // would refill it and drop the result again. + unlink(slot->path); + unlock_slot(slot, 0); + } else { + publish_slot(slot); + } slot->cache_fd = slot->lock_fd; } } @@ -460,7 +473,10 @@ static int process_slot(struct cache_slot *slot) // concurrent writer put there for a different key, so what gets // printed is the descriptor still open on the lock file. slot->cache_fd = slot->lock_fd; - publish_slot(slot); + if (slot->oversized) + unlock_slot(slot, 0); + else + publish_slot(slot); err = serve_slot(slot); close_slot(slot); return err; @@ -500,7 +516,8 @@ unsigned long cache_hash_str(const char *str) return h; } -int cache_process(int size, const char *path, const char *key, int ttl, cache_fill_fn fn) +int cache_process(int size, const char *path, const char *key, int ttl, size_t max_bytes, + cache_fill_fn fn) { unsigned long hash; int i; @@ -539,6 +556,8 @@ int cache_process(int size, const char *path, const char *key, int ttl, cache_fi slot.ttl = ttl; slot.saved_stdout = -1; slot.abandoned = 0; + slot.max_bytes = max_bytes; + slot.oversized = 0; slot.path = slot_path.buf; slot.lock_path = lock_path.buf; slot.key = key; -- cgit v2.8.0