Artifact 9d325b18ed997bcfb8f2b735be6765a1bf246cb3cb1c2fa61d122820992af5a3:
- File
internal/content/cache_test.go
— part of check-in
[b43fd24a1f]
at
2026-10-23 05:28:31
on branch feat/horizon-cones
— perf(sync): check received deltas against a cached basis
The real-repository gate found a dense clone of the Fossil repository CPU-bound past an hour, in delta.Checksum and chain expansion. checkDelta rebuilt each received delta's basis from the root of its chain with content.ExpandUnchecked, after walking the chain for availability; a file's history arrives as one chain of hundreds or thousands of deltas, so the cost was quadratic in its depth, as Repo.Verify's was before 1a5abca.
A receive pass now rebuilds the basis through an unchecked content cache (content.NewUncheckedCache): from the deepest node of the chain it holds, keeping each node it rebuilds, and it remembers every artifact a card stored and verified once the card's savepoint commits. A clone keeps one such cache across its rounds and clears it when a round rolls back. A basis the cache holds, or one dangling in the pass, needs no availability walk, and the clone's check for a basis to request uses the same knowledge. The cache holds what each row rebuilds to, unchecked as checkDelta's basis always was, so every verdict is unchanged; a verified delta's target also goes to the receive linker, which no longer expands it again.
TestCorpusClone (LIBFOSSIL_CORPUS) clones a copy of a repository from an in-process server for profiling. (user: [email protected], size: 12530) [annotate] [blame] [check-ins using] [more...]
package content import ( "bytes" "path/filepath" "sync" "testing" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/blob" "github.com/danmestas/go-libfossil/internal/delta" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/hash" _ "github.com/danmestas/go-libfossil/internal/testdriver" ) func TestCache_HitMiss(t *testing.T) { d := setupTestDB(t) data := []byte("hello cache") rid, _, err := blob.Store(d, data) if err != nil { t.Fatal(err) } c := NewCache(1 << 20) // 1MB // First call = miss got, err := c.Expand(d, rid) if err != nil { t.Fatal(err) } if !bytes.Equal(got, data) { t.Fatalf("got %q, want %q", got, data) } stats := c.Stats() if stats.Hits != 0 || stats.Misses != 1 { t.Fatalf("expected 0 hits / 1 miss, got %d / %d", stats.Hits, stats.Misses) } // Second call = hit got2, err := c.Expand(d, rid) if err != nil { t.Fatal(err) } if !bytes.Equal(got2, data) { t.Fatalf("got %q, want %q", got2, data) } stats = c.Stats() if stats.Hits != 1 || stats.Misses != 1 { t.Fatalf("expected 1 hit / 1 miss, got %d / %d", stats.Hits, stats.Misses) } } func TestCache_ReturnsCopy(t *testing.T) { d := setupTestDB(t) data := []byte("immutable data") rid, _, _ := blob.Store(d, data) c := NewCache(1 << 20) got1, _ := c.Expand(d, rid) got1[0] = 'X' // mutate the returned slice got2, _ := c.Expand(d, rid) if got2[0] == 'X' { t.Fatal("cache returned a reference instead of a copy — caller mutation corrupted cache") } } func TestCache_Eviction(t *testing.T) { d := setupTestDB(t) // Create blobs: 100 bytes each. Cache fits ~2. data1 := bytes.Repeat([]byte("A"), 100) data2 := bytes.Repeat([]byte("B"), 100) data3 := bytes.Repeat([]byte("C"), 100) rid1, _, _ := blob.Store(d, data1) rid2, _, _ := blob.Store(d, data2) rid3, _, _ := blob.Store(d, data3) c := NewCache(250) // fits ~2 entries of 100 bytes each c.Expand(d, rid1) c.Expand(d, rid2) stats := c.Stats() if stats.Entries != 2 { t.Fatalf("expected 2 entries, got %d", stats.Entries) } // Adding rid3 should evict rid1 (LRU) c.Expand(d, rid3) stats = c.Stats() if stats.Entries != 2 { t.Fatalf("expected 2 entries after eviction, got %d", stats.Entries) } if stats.Size > 250 { t.Fatalf("cache size %d exceeds max 250", stats.Size) } } func TestCache_MaxEntries(t *testing.T) { c := NewCache(1) for i := 1; i <= maxCacheEntries+1; i++ { c.store(libfossil.FslID(i), []byte{}) } if got := c.Stats().Entries; got != maxCacheEntries { t.Fatalf("expected %d entries, got %d", maxCacheEntries, got) } if _, ok := c.items[libfossil.FslID(1)]; ok { t.Fatal("oldest entry was not evicted") } if _, ok := c.items[libfossil.FslID(maxCacheEntries+1)]; !ok { t.Fatal("newest entry was evicted") } } func TestCache_Invalidate(t *testing.T) { d := setupTestDB(t) rid, _, _ := blob.Store(d, []byte("data to invalidate")) c := NewCache(1 << 20) c.Expand(d, rid) if c.Stats().Entries != 1 { t.Fatal("expected 1 entry") } c.Invalidate(rid) if c.Stats().Entries != 0 { t.Fatal("expected 0 entries after invalidate") } } func TestCache_Clear(t *testing.T) { d := setupTestDB(t) rid, _, _ := blob.Store(d, []byte("data to clear")) c := NewCache(1 << 20) c.Expand(d, rid) c.Clear() stats := c.Stats() if stats.Entries != 0 || stats.Size != 0 { t.Fatalf("expected empty cache, got entries=%d size=%d", stats.Entries, stats.Size) } } func TestCache_NilPassthrough(t *testing.T) { d := setupTestDB(t) data := []byte("passthrough data") rid, _, _ := blob.Store(d, data) var c *Cache got, err := c.Expand(d, rid) if err != nil { t.Fatal(err) } if !bytes.Equal(got, data) { t.Fatalf("got %q, want %q", got, data) } } func TestCache_ConcurrentAccess(t *testing.T) { d := setupTestDB(t) data := []byte("concurrent data") rid, _, _ := blob.Store(d, data) c := NewCache(1 << 20) var wg sync.WaitGroup for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() got, err := c.Expand(d, rid) if err != nil { t.Errorf("expand error: %v", err) return } if !bytes.Equal(got, data) { t.Errorf("got %q, want %q", got, data) } }() } wg.Wait() } func TestNewCache_PanicsOnZero(t *testing.T) { defer func() { if r := recover(); r == nil { t.Fatal("expected panic for maxBytes <= 0") } }() NewCache(0) } func TestCache_LRUOrder(t *testing.T) { d := setupTestDB(t) data := bytes.Repeat([]byte("X"), 100) rid1, _, _ := blob.Store(d, data) data2 := bytes.Repeat([]byte("Y"), 100) rid2, _, _ := blob.Store(d, data2) data3 := bytes.Repeat([]byte("Z"), 100) rid3, _, _ := blob.Store(d, data3) c := NewCache(250) // fits ~2 entries c.Expand(d, rid1) // order: [rid1] c.Expand(d, rid2) // order: [rid2, rid1] c.Expand(d, rid1) // touch rid1 → order: [rid1, rid2] c.Expand(d, rid3) // evicts rid2 (LRU), order: [rid3, rid1] // rid1 should still be cached (was touched) missesBefore := c.Stats().Misses c.Expand(d, rid1) if c.Stats().Misses != missesBefore { t.Fatal("rid1 should have been a cache hit (was recently touched)") } // rid2 should have been evicted c.Expand(d, rid2) if c.Stats().Misses != missesBefore+1 { t.Fatal("rid2 should have been a cache miss (was evicted)") } } func TestCache_BlobLargerThanMax(t *testing.T) { d := setupTestDB(t) // 500-byte blob into a 100-byte cache — blob exceeds maxSize big := bytes.Repeat([]byte("B"), 500) rid, _, _ := blob.Store(d, big) c := NewCache(100) // The data should still be returned correctly even though it can't stay cached. got, err := c.Expand(d, rid) if err != nil { t.Fatal(err) } if !bytes.Equal(got, big) { t.Fatal("data mismatch for oversized blob") } // The blob is inserted then immediately evicted (size 500 > max 100). stats := c.Stats() if stats.Entries != 0 { t.Fatalf("expected 0 entries (oversized blob evicted), got %d", stats.Entries) } } func TestCache_RememberOversizedDoesNotAllocate(t *testing.T) { c := NewCache(100) rid := libfossil.FslID(1) data := bytes.Repeat([]byte("B"), 500) allocs := testing.AllocsPerRun(100, func() { c.Remember(rid, data) }) if allocs != 0 { t.Fatalf("Remember allocated %v times for an oversized entry; want 0", allocs) } stats := c.Stats() if stats.Entries != 0 { t.Fatalf("Remember retained %d oversized entries; want 0", stats.Entries) } if stats.Size != 0 { t.Fatalf("Remember retained %d oversized bytes; want 0", stats.Size) } } func TestCache_RememberCachedDoesNotAllocate(t *testing.T) { c := NewCache(100) rid := libfossil.FslID(1) data := bytes.Repeat([]byte("B"), 50) c.Remember(rid, data) allocs := testing.AllocsPerRun(100, func() { c.Remember(rid, data) }) if allocs != 0 { t.Fatalf("Remember allocated %v times for a cached entry; want 0", allocs) } stats := c.Stats() if stats.Entries != 1 { t.Fatalf("Remember retained %d cached entries; want 1", stats.Entries) } if stats.Size != int64(len(data)) { t.Fatalf("Remember retained %d cached bytes; want %d", stats.Size, len(data)) } } func TestCache_ExpandError(t *testing.T) { d := setupTestDB(t) c := NewCache(1 << 20) // rid 99999 doesn't exist — Expand should fail _, err := c.Expand(d, 99999) if err == nil { t.Fatal("expected error for nonexistent rid") } // Cache should remain empty — error results must not be cached stats := c.Stats() if stats.Entries != 0 { t.Fatalf("expected 0 entries after failed expand, got %d", stats.Entries) } } func TestCache_DeltaChainViaCacheHit(t *testing.T) { d := setupTestDB(t) // Build a 3-deep delta chain and verify cached expansion is correct v1 := []byte("version one with enough padding to create proper deltas here!!") v2 := []byte("version TWO with enough padding to create proper deltas here!!") v3 := []byte("version THREE with enough padding to create proper deltas here!!") rid1, _, _ := blob.Store(d, v1) rid2, _, _ := blob.StoreDelta(d, v2, rid1) rid3, _, _ := blob.StoreDelta(d, v3, rid2) c := NewCache(1 << 20) // Expand the leaf of the chain (miss) got, err := c.Expand(d, rid3) if err != nil { t.Fatal(err) } if !bytes.Equal(got, v3) { t.Fatalf("got %q, want %q", got, v3) } // Second expand should hit cache and return identical data got2, err := c.Expand(d, rid3) if err != nil { t.Fatal(err) } if !bytes.Equal(got2, v3) { t.Fatalf("cached result differs: got %q, want %q", got2, v3) } stats := c.Stats() if stats.Hits != 1 || stats.Misses != 1 { t.Fatalf("expected 1 hit / 1 miss, got %d / %d", stats.Hits, stats.Misses) } } func BenchmarkCache_Expand(b *testing.B) { path := filepath.Join(b.TempDir(), "bench.fossil") d, _ := db.Open(path) db.CreateRepoSchema(d) defer d.Close() base := bytes.Repeat([]byte("base content for cache benchmark "), 100) rid, _, _ := blob.Store(d, base) // Build a 5-deep delta chain for i := 0; i < 5; i++ { next := make([]byte, len(base)) copy(next, base) copy(next[i*50:], []byte("CHANGED!")) newRid, _, _ := blob.StoreDelta(d, next, rid) rid = newRid base = next } c := NewCache(1 << 20) // Prime the cache c.Expand(d, rid) b.ResetTimer() for i := 0; i < b.N; i++ { c.Expand(d, rid) } } func TestCacheDerived(t *testing.T) { var nilCache *Cache nilCache.SetDerived("k", 1) if _, ok := nilCache.Derived("k"); ok { t.Fatal("nil cache held a derived value") } c := NewCache(1 << 20) if _, ok := c.Derived("k"); ok { t.Fatal("empty cache held a derived value") } c.SetDerived("k", 1) c.SetDerived("k", 2) if v, ok := c.Derived("k"); !ok || v != 2 { t.Fatalf("Derived(k) = %v, %v; want 2, true", v, ok) } if got := c.Stats().DerivedHits; got != 1 { t.Fatalf("DerivedHits = %d, want 1", got) } // Past maxDerived the oldest value goes; re-setting a held key is not new. for i := 0; i < maxDerived; i++ { c.SetDerived(i, i) } if _, ok := c.Derived("k"); ok { t.Fatal("oldest derived value survived eviction") } if v, ok := c.Derived(maxDerived - 1); !ok || v != maxDerived-1 { t.Fatalf("newest derived value lost: %v, %v", v, ok) } c.Invalidate(1) if _, ok := c.Derived(maxDerived - 1); ok { t.Fatal("Invalidate kept a derived value") } c.SetDerived("k", 3) c.Clear() if _, ok := c.Derived("k"); ok { t.Fatal("Clear kept a derived value") } } // TestUncheckedCache checks the cache a received delta's basis is rebuilt // through: a chain through a row whose bytes are not its name, which a // checked cache refuses, still rebuilds, to the bytes // ExpandUnchecked gives, with every node of its chain kept; Holds sees what // is kept without counting a lookup; and neither kind of cache serves the // other's expansion. func TestUncheckedCache(t *testing.T) { d := setupTestDB(t) base := []byte("the basis row of a two-link chain, stored under another name\n") stored, err := blob.Compress(base) if err != nil { t.Fatal(err) } misnamed := hash.SHA1([]byte("the bytes this row should hold")) res, err := d.Exec("INSERT INTO blob(uuid, size, content, rcvid) VALUES(?, ?, ?, 1)", misnamed, len(base), stored) if err != nil { t.Fatal(err) } baseRid, err := res.LastInsertId() if err != nil { t.Fatal(err) } target := append(append([]byte(nil), base...), "and a line more\n"...) dl, err := delta.Create(base, target) if err != nil { t.Fatal(err) } rid, err := blob.StoreDeltaRaw(d, hash.SHA1(target), dl, libfossil.FslID(baseRid), nil) if err != nil { t.Fatal(err) } if _, err := NewCache(1<<20).Expand(d, rid); err == nil { t.Fatal("a checked cache rebuilt a chain through a misnamed row") } want, err := ExpandUnchecked(d, rid) if err != nil { t.Fatal(err) } c := NewUncheckedCache(1 << 20) got, err := c.ExpandUnchecked(d, rid) if err != nil { t.Fatal(err) } if !bytes.Equal(got, want) { t.Fatalf("cached unchecked expansion = %q, want %q", got, want) } if !c.Holds(libfossil.FslID(baseRid)) || !c.Holds(rid) { t.Fatal("the chain's nodes are not kept") } if s := c.Stats(); s.Hits != 0 || s.Misses != 1 { t.Fatalf("Holds counted lookups: %d hits, %d misses", s.Hits, s.Misses) } for name, fn := range map[string]func(){ "Expand on an unchecked cache": func() { c.Expand(d, rid) }, "ExpandUnchecked on a checked cache": func() { NewCache(1<<20).ExpandUnchecked(d, rid) }, } { func() { defer func() { if recover() == nil { t.Errorf("%s did not panic", name) } }() fn() }() } }