package system
import (
"fmt"
"sync"
"testing"
"time"
"github.com/moby/moby/client"
"github.com/moby/moby/v2/integration/internal/image"
"github.com/moby/moby/v2/internal/testutil"
"github.com/moby/moby/v2/internal/testutil/daemon"
"github.com/moby/moby/v2/internal/testutil/specialimage"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/v3/assert"
"gotest.tools/v3/skip"
)
// TestDiskUsageConcurrentPrune tests that running DiskUsage concurrently with
// image removal does not cause an error.
// Regression test for https://github.com/moby/moby/issues/51978
func TestDiskUsageConcurrentPrune(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "cannot start multiple daemons on windows")
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
skip.If(t, !testEnv.UsingSnapshotter(), "only happens with containerd image store")
t.Parallel()
ctx := testutil.StartSpan(baseContext, t)
d := daemon.New(t)
d.Start(t, "--iptables=false", "--ip6tables=false")
defer d.Stop(t)
apiClient := d.NewClientT(t)
// Load unique images with multiple layers each.
// More snapshots = higher chance of hitting the race.
const numImages = 10
const layersPerImage = 10
imageNames := make([]string, 0, numImages)
for i := range numImages {
var layers []specialimage.SingleFileLayer
for j := range layersPerImage {
layers = append(layers, specialimage.SingleFileLayer{
Name: fmt.Sprintf("file-%d-%d", i, j),
Content: fmt.Appendf(nil, "content-%d-%d", i, j),
})
}
imageName := fmt.Sprintf("test-image-%d:latest", i)
imageNames = append(imageNames, imageName)
image.Load(ctx, t, apiClient, func(dir string) (*ocispec.Index, error) {
return specialimage.MultiLayerCustom(dir, imageName, layers)
})
}
t.Cleanup(func() {
for _, name := range imageNames {
_, _ = apiClient.ImageRemove(ctx, name, client.ImageRemoveOptions{Force: true})
}
})
const diskUsageCount = 5
var wg sync.WaitGroup
errCh := make(chan error, diskUsageCount)
removeStarted := make(chan struct{})
for range diskUsageCount {
wg.Go(func() {
<-removeStarted
time.Sleep(time.Millisecond)
_, err := apiClient.DiskUsage(ctx, client.DiskUsageOptions{
Images: true,
})
if err != nil {
errCh <- err
}
})
}
wg.Go(func() {
close(removeStarted)
for _, name := range imageNames {
_, _ = apiClient.ImageRemove(ctx, name, client.ImageRemoveOptions{Force: true})
}
})
wg.Wait()
close(errCh)
for err := range errCh {
assert.NilError(t, err, "DiskUsage should not error when images are being removed concurrently")
}
}