Artifact 7febb2cd19a495e3180f81759c1422c7b6051721f7ccfe5eafb0e84fba81f470:
- File
internal/zitcli/cache_test.go
— part of check-in
[0cf9a57e88]
at
2026-08-17 21:14:09
on branch trunk
— zitcli: byte-bounded content cache for hash-versioned reads
zit cat costs ~147ms even for a small file, against a 3ms process floor, and every file view forked one. Under load that capped the forge at 32 req/s, degrading to 24 at twenty concurrent readers.
hash-versioned file page: 393ms -> 18ms 50 concurrent: 201 req/s, still climbing
Bounded by BYTES, not entries: values are file contents, and an entry-counted cache holding a 30MB blob is a memory leak with a polite name. Per repository, so a busy store cannot evict a quiet one's entries.
resolveVersion only expands hash PREFIXES. An earlier draft fell back to the index's branch table for names, which served tk a README from 2020: tk renamed its trunk to main, so a branch literally called trunk still exists and is six years stale, while zit resolves the word correctly. 0001 §2 makes zit the authority on names and that is not negotiable — so a symbolic version stays uncached, and the way to fix that is to make links carry the resolved hash. (user: dmestas, size: 1833) [annotate] [blame] [check-ins using]
package zitcli import ( "strings" "testing" ) // The budget is in BYTES because the values are file contents: an // entry-counted cache with a 30MB blob in it is a memory leak with a // polite name. func TestBlobCacheEvictsByBytes(t *testing.T) { c := newBlobCache(100) c.put("a", []byte(strings.Repeat("x", 60))) c.put("b", []byte(strings.Repeat("y", 30))) if n, b := c.stats(); n != 2 || b != 90 { t.Fatalf("entries=%d bytes=%d, want 2/90", n, b) } // Admitting c must evict the least recently used, not the oldest // inserted — so touch a first. if _, ok := c.get("a"); !ok { t.Fatal("a missing") } c.put("d", []byte(strings.Repeat("z", 30))) if _, ok := c.get("b"); ok { t.Error("evicted the recently-used entry instead of the least-recently-used") } if _, ok := c.get("a"); !ok { t.Error("a was evicted despite being used most recently") } if _, b := c.stats(); b > 100 { t.Errorf("cache holds %d bytes, over its 100-byte budget", b) } } // A value bigger than the whole budget must be refused rather than // admitted by evicting everything: holding one item at the cost of all // the others is worse than a miss. func TestBlobCacheRefusesOversizedValues(t *testing.T) { c := newBlobCache(100) c.put("small", []byte("keep me")) c.put("huge", []byte(strings.Repeat("x", 500))) if _, ok := c.get("huge"); ok { t.Error("cached a value larger than the entire budget") } if _, ok := c.get("small"); !ok { t.Error("an oversized put evicted a value that fit") } } // Replacing a key must not double-count its bytes. func TestBlobCacheReplaceAccountsCorrectly(t *testing.T) { c := newBlobCache(100) c.put("k", []byte(strings.Repeat("x", 40))) c.put("k", []byte(strings.Repeat("y", 10))) if n, b := c.stats(); n != 1 || b != 10 { t.Fatalf("entries=%d bytes=%d, want 1/10", n, b) } }