Files
containerd/integration
Wei Fu 4f130dbe73 cri: retry stopSandboxContainer if shim connection is closed
Background:

The TestRunPodSandboxWithShimDeleteFailure test case validates that the sandbox
can be properly cleaned up when RunPodSandbox returns an error.

This test simulates a RunPodSandbox failure by using a failpoint to inject an
error during the startup of the sandbox container. It also blocks the first
task deletion attempt within the deferred cleanup logic of RunPodSandbox.
As a result, the sandbox remains after the failed RunPodSandbox call, allowing
the test to verify the subsequent cleanup process.

Race-condition causes flaky issue:

The defer cleanup invokes `task.Delete` with `containerd.WithProcessKill` option
so that it will kill sandbox container first. Since we don't block `Kill` call,
cleanup will kill sandbox container successfully, even if `task.Delete`
fails.

```go
// Link https://github.com/containerd/containerd/blob/04fe0ff9d292d3fa29d239e6a7f312e8175415c8/internal/cri/server/podsandbox/sandbox_run.go#L248-L258

	defer func() {
		if retErr != nil && cleanupErr == nil {
			deferCtx, deferCancel := ctrdutil.DeferContext()
			defer deferCancel()
			// Cleanup the sandbox container if an error is returned.
			if _, err := task.Delete(deferCtx, WithNRISandboxDelete(id), containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) {
				log.G(ctx).WithError(err).Errorf("Failed to delete sandbox container %q", id)
				cleanupErr = err
			}
		}
	}()
```

That podSandboxEventHandler.HandleEvent receives task exit event
during defer cleanup of RunPodSandbox and then it will delete task. That
`task.Delete` call could take few second to shutdown containerd-shim. It
could bring race-condition if we invoke RemovePodSandbox.

```go
// Link https://github.com/containerd/containerd/blob/04fe0ff9d292d3fa29d239e6a7f312e8175415c8/internal/cri/server/podsandbox/events.go#L43-L61

func (p *podSandboxEventHandler) HandleEvent(any interface{}) error {
	switch e := any.(type) {
	case *eventtypes.TaskExit:
		log.L.Debugf("TaskExit event in podsandbox handler %+v", e)
		// Use ID instead of ContainerID to rule out TaskExit event for exec.
		sb := p.controller.store.Get(e.ID)
		if sb == nil || sb.Container == nil {
			return nil
		}
		ctx := ctrdutil.NamespacedContext()
		ctx, cancel := context.WithTimeout(ctx, handleEventTimeout)
		defer cancel()
		if err := handleSandboxTaskExit(ctx, sb, e); err != nil {
			return fmt.Errorf("failed to handle container TaskExit event: %w", err)
		}
		return nil
	}
	return nil
}
```

If shim exits, that RemovePodSandbox could return error like

```
... failed to kill pod sandbox container: ttrpc: closed
```

Retry and fix this transient issue:

If `a ttrpc: closed` error occurs, we should retry the operation.
There’s no need to introduce a mutex for this, as the kubelet already
has retry logic to ensure the sandbox is eventually cleaned up.

Other flaky issue:

The event handle could be able to cleanup shim asynchronously. That
could cause ListPodSandbox returns empty, which is not expected. Updated
that test case with `Kill` failpoint instead of `Delete`.

Fixes: #11107
Close: #11967

Signed-off-by: Wei Fu <fuweid89@gmail.com>
2025-10-20 21:16:00 -04:00
..
2024-06-20 23:12:31 +00:00
2023-11-01 10:37:00 -07:00
2023-11-01 15:32:43 -07:00