diff --git a/pkg/kubelet/dockershim/docker_container_test.go b/pkg/kubelet/dockershim/docker_container_test.go index 7834da3315c..e6c99c694bb 100644 --- a/pkg/kubelet/dockershim/docker_container_test.go +++ b/pkg/kubelet/dockershim/docker_container_test.go @@ -23,6 +23,7 @@ import ( "fmt" "path/filepath" "strings" + "sync" "testing" "time" @@ -51,6 +52,67 @@ func getTestCTX() context.Context { return context.Background() } +// TestConcurrentlyCreateAndDeleteContainers is a regression test for #93771, which ensures +// kubelet would not panic on concurrent writes to `dockerService.containerCleanupInfos`. +func TestConcurrentlyCreateAndDeleteContainers(t *testing.T) { + ds, _, _ := newTestDockerService() + podName, namespace := "foo", "bar" + containerName, image := "sidecar", "logger" + + const count = 20 + configs := make([]*runtimeapi.ContainerConfig, 0, count) + sConfigs := make([]*runtimeapi.PodSandboxConfig, 0, count) + for i := 0; i < count; i++ { + s := makeSandboxConfig(fmt.Sprintf("%s%d", podName, i), + fmt.Sprintf("%s%d", namespace, i), fmt.Sprintf("%d", i), 0) + labels := map[string]string{"concurrent-test": fmt.Sprintf("label%d", i)} + c := makeContainerConfig(s, fmt.Sprintf("%s%d", containerName, i), + fmt.Sprintf("%s:v%d", image, i), uint32(i), labels, nil) + sConfigs = append(sConfigs, s) + configs = append(configs, c) + } + + containerIDs := make(chan string, len(configs)) // make channel non-blocking to simulate concurrent containers creation + + var ( + creationWg sync.WaitGroup + deletionWg sync.WaitGroup + ) + + creationWg.Add(len(configs)) + + go func() { + creationWg.Wait() + close(containerIDs) + }() + for i := range configs { + go func(i int) { + defer creationWg.Done() + // We don't care about the sandbox id; pass a bogus one. + sandboxID := fmt.Sprintf("sandboxid%d", i) + req := &runtimeapi.CreateContainerRequest{PodSandboxId: sandboxID, Config: configs[i], SandboxConfig: sConfigs[i]} + createResp, err := ds.CreateContainer(getTestCTX(), req) + if err != nil { + t.Errorf("CreateContainer: %v", err) + return + } + containerIDs <- createResp.ContainerId + }(i) + } + + for containerID := range containerIDs { + deletionWg.Add(1) + go func(id string) { + defer deletionWg.Done() + _, err := ds.RemoveContainer(getTestCTX(), &runtimeapi.RemoveContainerRequest{ContainerId: id}) + if err != nil { + t.Errorf("RemoveContainer: %v", err) + } + }(containerID) + } + deletionWg.Wait() +} + // TestListContainers creates several containers and then list them to check // whether the correct metadatas, states, and labels are returned. func TestListContainers(t *testing.T) {