Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions lib/store/ca_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,12 +475,14 @@ func (s *CAStore) GetCacheFileMetadata(name string, md metadata.Metadata) error
// shouldn't happen, but good to check
return fmt.Errorf("entry %s doesn't have any metainfo", entry.Name)
}
// Serialize and deserialize for consistency with disk behavior
b, err := entry.MetaInfo.Serialize()
if err != nil {
return fmt.Errorf("serialize metainfo: %s", err)
// Verify if the asked metadata is TorrentMetdata or not
tm, ok := md.(*metadata.TorrentMeta)
if !ok {
return fmt.Errorf("unvariant violation: GetCacheFileMetadata should only be called for TorrentMetadata")
Comment on lines +478 to +481
}
return md.Deserialize(b)
// hand back cached pointer
tm.MetaInfo = entry.MetaInfo
return nil
Comment on lines +483 to +485
}
}

Expand Down
124 changes: 124 additions & 0 deletions lib/store/ca_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1208,3 +1208,127 @@ func TestCAStore_ListCacheFiles(t *testing.T) {
})
}
}

func TestGetCacheFileMetadata_MemoryCache_NoCopy(t *testing.T) {
require := require.New(t)

config, cleanup := CAStoreConfigFixture()
defer cleanup()
config.MemoryCache = MemoryCacheConfig{
Enabled: true,
MaxSize: 100 * 1024 * 1024,
DrainWorkers: 1,
DrainMaxRetries: 3,
TTL: time.Hour,
TTLInterval: time.Hour,
}

mockClock := clock.NewMock()
s, err := newCAStore(config, tally.NoopScope, mockClock)
require.NoError(err)
defer s.Close()

blob := core.SizedBlobFixture(1024, 256)
name := blob.Digest.Hex()
err = s.WriteBlobToCacheWithMetaInfo(
name,
uint64(len(blob.Content)),
func(w FileReadWriter) error {
_, err := w.Write(blob.Content)
return err
},
256,
)
require.NoError(err)
require.True(s.CheckInMemCache(name))

cached := s.memCache.Get(name)
require.NotNil(cached)
require.NotNil(cached.MetaInfo)

// First read: pointer should match the cache entry's MetaInfo (no copy).
var tm1 metadata.TorrentMeta
require.NoError(s.GetCacheFileMetadata(name, &tm1))
require.Same(cached.MetaInfo, tm1.MetaInfo,
"GetCacheFileMetadata should return the cached *MetaInfo without copying")

// Second read: same again — confirms the path is deterministic, not a one-shot.
var tm2 metadata.TorrentMeta
require.NoError(s.GetCacheFileMetadata(name, &tm2))
require.Same(cached.MetaInfo, tm2.MetaInfo)

// Behavioral parity: returned MetaInfo matches the original blob's MetaInfo.
require.Equal(blob.MetaInfo.InfoHash(), tm1.MetaInfo.InfoHash())
require.Equal(blob.MetaInfo.Length(), tm1.MetaInfo.Length())
require.Equal(blob.MetaInfo.NumPieces(), tm1.MetaInfo.NumPieces())
for i := 0; i < blob.MetaInfo.NumPieces(); i++ {
require.Equal(blob.MetaInfo.GetPieceSum(i), tm1.MetaInfo.GetPieceSum(i))
}
Comment thread
thijmv marked this conversation as resolved.
}

func BenchmarkGetCacheFileMetadata_MemoryCache(b *testing.B) {
cases := []struct {
name string
blobSize uint64
pieceLength uint64
}{
// Vary blob size at fixed 256 KB pieces (realistic Docker layer scaling).
{"1MB_4pc", 1 << 20, 256 << 10},
{"16MB_64pc", 16 << 20, 256 << 10},
{"64MB_256pc", 64 << 20, 256 << 10},
// Vary piece count at fixed 16 MB blob (isolates piece-count cost).
{"16MB_4pc_4MBpc", 16 << 20, 4 << 20},
{"16MB_16pc_1MBpc", 16 << 20, 1 << 20},
{"16MB_1024pc_16KBpc", 16 << 20, 16 << 10},
}
for _, tc := range cases {
b.Run(tc.name, func(b *testing.B) {
config, cleanup := CAStoreConfigFixture()
defer cleanup()
config.MemoryCache = MemoryCacheConfig{
Enabled: true,
MaxSize: 512 << 20,
DrainWorkers: 1,
DrainMaxRetries: 3,
TTL: time.Hour,
TTLInterval: time.Hour,
}

// Mock clock keeps the drain worker's ticker from firing, so the entry
// stays in the memory cache for the duration of the benchmark.
mockClock := clock.NewMock()
s, err := newCAStore(config, tally.NoopScope, mockClock)
require.NoError(b, err)
defer func() {
b.StopTimer()
s.Close()
Comment thread
sambhav-jain-16 marked this conversation as resolved.
}()

blob := core.SizedBlobFixture(tc.blobSize, tc.pieceLength)
name := blob.Digest.Hex()
err = s.WriteBlobToCacheWithMetaInfo(
name,
uint64(len(blob.Content)),
func(w FileReadWriter) error {
_, err := w.Write(blob.Content)
return err
},
int64(tc.pieceLength),
)
require.NoError(b, err)
require.True(b, s.CheckInMemCache(name))

b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
var tm metadata.TorrentMeta
if err := s.GetCacheFileMetadata(name, &tm); err != nil {
b.Fatal(err)
}
if tm.MetaInfo == nil {
b.Fatal("nil MetaInfo")
}
}
Comment thread
sambhav-jain-16 marked this conversation as resolved.
})
}
}
Loading