add concurrent churn test

Signed-off-by: Anis Khan <2815766+aniskhan001@users.noreply.github.com>
pull/32533/head
Anis Khan 4 weeks ago
parent 280cdc4f0c
commit fdb9929a93
No known key found for this signature in database
GPG Key ID: 1E7BFF978E3EEBB3

@ -17,6 +17,7 @@ package downloader
import (
"bytes"
"fmt"
"io/fs"
"os"
"path/filepath"
@ -307,6 +308,87 @@ func TestDownloadAllConcurrent(t *testing.T) {
}
}
// TestDownloadAllConcurrentVersionChurn guards the race fixed by serializing
// downloadAll per ChartPath. safeMoveDeps snapshots the destination charts/
// directory, moves in files matching its own deps (by filename, which
// embeds the version), then deletes any dest file from that snapshot that
// isn't one of those filenames - including older versions of a dependency
// it just replaced.
//
// This is racy when concurrent downloadAll calls for the same ChartPath
// resolve the same dependency to different versions between calls (e.g. a
// caller like Skaffold repeatedly rendering a chart while the upstream repo
// index is being updated). Call A moves in dep-2.0.0.tgz; call B's
// destination snapshot predates A's write and still expects dep-1.0.0.tgz,
// so once B commits, its own delete-check treats A's freshly written
// dep-2.0.0.tgz as a stranger and removes it - even though a "dep" chart is
// still one of B's deps, just resolved to a different version.
//
// Each goroutine here resolves the same dependency name to a distinct
// version, so if the race occurs, later versions written by other
// goroutines end up deleted, leaving fewer than `concurrency` chart files
// (or the wrong one) in charts/ afterward.
func TestDownloadAllConcurrentVersionChurn(t *testing.T) {
chartPath := t.TempDir()
const concurrency = 8
const depName = "churn-dep"
deps := make([]*chart.Dependency, concurrency)
for i := 0; i < concurrency; i++ {
version := fmt.Sprintf("0.1.%d", i)
src := &chart.Chart{
Metadata: &chart.Metadata{
Name: depName,
Version: version,
APIVersion: "v2",
},
}
srcParent := filepath.Join(chartPath, fmt.Sprintf("src-%d", i))
require.NoError(t, os.MkdirAll(srcParent, 0o755))
require.NoError(t, chartutil.SaveDir(src, srcParent))
deps[i] = &chart.Dependency{
Name: depName,
Repository: fmt.Sprintf("file://./src-%d/%s", i, depName),
Version: version,
}
}
errs := make([]error, concurrency)
var wg sync.WaitGroup
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
m := &Manager{
Out: new(bytes.Buffer),
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
ChartPath: chartPath,
}
errs[i] = m.downloadAll([]*chart.Dependency{deps[i]})
}(i)
}
wg.Wait()
for i, err := range errs {
assert.NoError(t, err, "concurrent downloadAll call %d failed", i)
}
// Exactly one version of depName should remain: whichever call's
// safeMoveDeps ran last should have left its own version behind, and no
// call should have raced another's write out of existence.
entries, err := os.ReadDir(filepath.Join(chartPath, "charts"))
require.NoError(t, err)
var found []string
for _, entry := range entries {
if strings.HasPrefix(entry.Name(), depName+"-") {
found = append(found, entry.Name())
}
}
assert.Len(t, found, 1, "expected exactly one version of %s in charts/, found %v", depName, found)
}
func TestUpdateBeforeBuild(t *testing.T) {
// Set up a fake repo
srv := repotest.NewTempServer(

Loading…
Cancel
Save