diff --git a/daemon/daemon.go b/daemon/daemon.go index 4d76c57988..2f6e7254a0 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -472,9 +472,12 @@ func (daemon *Daemon) restore() error { restartContainers[c] = make(chan struct{}) mapLock.Unlock() } else if c.HostConfig != nil && c.HostConfig.AutoRemove { - mapLock.Lock() - removeContainers[c.ID] = c - mapLock.Unlock() + // Remove the container if live-restore is disabled or if the container has already exited. + if !daemon.configStore.LiveRestoreEnabled || !alive { + mapLock.Lock() + removeContainers[c.ID] = c + mapLock.Unlock() + } } c.Lock() diff --git a/integration/daemon/daemon_test.go b/integration/daemon/daemon_test.go index 5ce70d8418..adbfc733c3 100644 --- a/integration/daemon/daemon_test.go +++ b/integration/daemon/daemon_test.go @@ -14,6 +14,7 @@ import ( "strings" "syscall" "testing" + "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/mount" @@ -370,6 +371,66 @@ func TestLiveRestore(t *testing.T) { skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows") t.Run("volume references", testLiveRestoreVolumeReferences) + t.Run("autoremove", testLiveRestoreAutoRemove) +} + +func testLiveRestoreAutoRemove(t *testing.T) { + skip.If(t, testEnv.IsRootless(), "restarted rootless daemon will have a new process namespace") + + t.Parallel() + ctx := context.Background() + + run := func(t *testing.T) (*daemon.Daemon, func(), string) { + d := daemon.New(t) + d.StartWithBusybox(t, "--live-restore", "--iptables=false") + t.Cleanup(func() { + d.Stop(t) + d.Cleanup(t) + }) + + tmpDir := t.TempDir() + + apiClient := d.NewClientT(t) + + cID := container.Run(ctx, t, apiClient, + container.WithBind(tmpDir, "/v"), + // Run until a 'stop' file is created. + container.WithCmd("sh", "-c", "while [ ! -f /v/stop ]; do sleep 0.1; done"), + container.WithAutoRemove) + t.Cleanup(func() { apiClient.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) }) + finishContainer := func() { + file, err := os.Create(filepath.Join(tmpDir, "stop")) + assert.NilError(t, err, "Failed to create 'stop' file") + file.Close() + } + return d, finishContainer, cID + } + + t.Run("engine restart shouldnt kill alive containers", func(t *testing.T) { + d, finishContainer, cID := run(t) + + d.Restart(t, "--live-restore", "--iptables=false") + + apiClient := d.NewClientT(t) + _, err := apiClient.ContainerInspect(ctx, cID) + assert.NilError(t, err, "Container shouldn't be removed after engine restart") + + finishContainer() + + poll.WaitOn(t, container.IsRemoved(ctx, apiClient, cID)) + }) + t.Run("engine restart should remove containers that exited", func(t *testing.T) { + d, finishContainer, cID := run(t) + + d.Stop(t) + + finishContainer() + time.Sleep(time.Millisecond * 200) + + d.Start(t, "--live-restore", "--iptables=false") + + poll.WaitOn(t, container.IsRemoved(ctx, d.NewClientT(t), cID)) + }) } func testLiveRestoreVolumeReferences(t *testing.T) {