Browse code

libcontainerd: create unstarted tasks

Split task creation and start into two separate method calls in the
libcontainerd API. Clients now have the opportunity to inspect the
freshly-created task and customize its runtime environment before
starting execution of the user-specified binary.

Signed-off-by: Cory Snider <csnider@mirantis.com>

Cory Snider authored on 2024/01/10 03:32:31
Showing 5 changed files
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/docker/docker/api/types/events"
12 12
 	"github.com/docker/docker/container"
13 13
 	"github.com/docker/docker/errdefs"
14
+	"github.com/docker/docker/internal/compatcontext"
14 15
 	"github.com/docker/docker/libcontainerd"
15 16
 	"github.com/pkg/errors"
16 17
 )
... ...
@@ -198,16 +199,32 @@ func (daemon *Daemon) containerStart(ctx context.Context, daemonCfg *configStore
198 198
 	if err != nil {
199 199
 		return setExitCodeFromError(container.SetExitCode, err)
200 200
 	}
201
+	defer func() {
202
+		if retErr != nil {
203
+			if err := ctr.Delete(compatcontext.WithoutCancel(ctx)); err != nil {
204
+				log.G(ctx).WithError(err).WithField("container", container.ID).
205
+					Error("failed to delete failed start container")
206
+			}
207
+		}
208
+	}()
201 209
 
202 210
 	// TODO(mlaventure): we need to specify checkpoint options here
203
-	tsk, err := ctr.Start(context.TODO(), // Passing ctx to ctr.Start caused integration tests to be stuck in the cleanup phase
211
+	tsk, err := ctr.NewTask(context.TODO(), // Passing ctx caused integration tests to be stuck in the cleanup phase
204 212
 		checkpointDir, container.StreamConfig.Stdin() != nil || container.Config.Tty,
205 213
 		container.InitializeStdio)
206 214
 	if err != nil {
207
-		if err := ctr.Delete(context.Background()); err != nil {
208
-			log.G(ctx).WithError(err).WithField("container", container.ID).
209
-				Error("failed to delete failed start container")
215
+		return setExitCodeFromError(container.SetExitCode, err)
216
+	}
217
+	defer func() {
218
+		if retErr != nil {
219
+			if err := tsk.ForceDelete(compatcontext.WithoutCancel(ctx)); err != nil {
220
+				log.G(ctx).WithError(err).WithField("container", container.ID).
221
+					Error("failed to delete task after fail start")
222
+			}
210 223
 		}
224
+	}()
225
+
226
+	if err := tsk.Start(context.TODO()); err != nil { // passing ctx caused integration tests to be stuck in the cleanup phase
211 227
 		return setExitCodeFromError(container.SetExitCode, err)
212 228
 	}
213 229
 
... ...
@@ -387,7 +387,7 @@ func (c *client) extractResourcesFromSpec(spec *specs.Spec, configuration *hcssh
387 387
 	}
388 388
 }
389 389
 
390
-func (ctr *container) Start(_ context.Context, _ string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Task, retErr error) {
390
+func (ctr *container) NewTask(_ context.Context, _ string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Task, retErr error) {
391 391
 	ctr.mu.Lock()
392 392
 	defer ctr.mu.Unlock()
393 393
 
... ...
@@ -514,6 +514,11 @@ func (ctr *container) Start(_ context.Context, _ string, withStdin bool, attachS
514 514
 	return t, nil
515 515
 }
516 516
 
517
+func (*task) Start(context.Context) error {
518
+	// No-op on Windows.
519
+	return nil
520
+}
521
+
517 522
 func (ctr *container) Task(context.Context) (libcontainerdtypes.Task, error) {
518 523
 	ctr.mu.Lock()
519 524
 	defer ctr.mu.Unlock()
... ...
@@ -145,8 +145,8 @@ func (c *client) NewContainer(ctx context.Context, id string, ociSpec *specs.Spe
145 145
 	return &created, nil
146 146
 }
147 147
 
148
-// Start create and start a task for the specified containerd id
149
-func (c *container) Start(ctx context.Context, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (libcontainerdtypes.Task, error) {
148
+// NewTask creates a task for the specified containerd id
149
+func (c *container) NewTask(ctx context.Context, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (libcontainerdtypes.Task, error) {
150 150
 	var (
151 151
 		checkpoint     *types.Descriptor
152 152
 		t              containerd.Task
... ...
@@ -236,19 +236,14 @@ func (c *container) Start(ctx context.Context, checkpointDir string, withStdin b
236 236
 	// Signal c.createIO that it can call CloseIO
237 237
 	stdinCloseSync <- t
238 238
 
239
-	if err := t.Start(ctx); err != nil {
240
-		// Only Stopped tasks can be deleted. Created tasks have to be
241
-		// killed first, to transition them to Stopped.
242
-		if _, err := t.Delete(ctx, containerd.WithProcessKill); err != nil {
243
-			c.client.logger.WithError(err).WithField("container", c.c8dCtr.ID()).
244
-				Error("failed to delete task after fail start")
245
-		}
246
-		return nil, wrapError(err)
247
-	}
248
-
249 239
 	return c.newTask(t), nil
250 240
 }
251 241
 
242
+func (t *task) Start(ctx context.Context) error {
243
+	return wrapError(t.Task.Start(ctx))
244
+
245
+}
246
+
252 247
 // Exec creates exec process.
253 248
 //
254 249
 // The containerd client calls Exec to register the exec config in the shim side.
... ...
@@ -64,7 +64,7 @@ type Client interface {
64 64
 
65 65
 // Container provides access to a containerd container.
66 66
 type Container interface {
67
-	Start(ctx context.Context, checkpointDir string, withStdin bool, attachStdio StdioCallback) (Task, error)
67
+	NewTask(ctx context.Context, checkpointDir string, withStdin bool, attachStdio StdioCallback) (Task, error)
68 68
 	Task(ctx context.Context) (Task, error)
69 69
 	// AttachTask returns the current task for the container and reattaches
70 70
 	// to the IO for the running task. If no task exists for the container
... ...
@@ -79,6 +79,8 @@ type Container interface {
79 79
 // Task provides access to a running containerd container.
80 80
 type Task interface {
81 81
 	Process
82
+	// Start begins execution of the task
83
+	Start(context.Context) error
82 84
 	// Pause suspends the execution of the task
83 85
 	Pause(context.Context) error
84 86
 	// Resume the execution of the task
... ...
@@ -81,11 +81,15 @@ func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteClo
81 81
 	}
82 82
 
83 83
 	p := c8dPlugin{log: log.G(ctx).WithField("plugin", id), ctr: ctr}
84
-	p.tsk, err = ctr.Start(ctx, "", false, attachStreamsFunc(stdout, stderr))
84
+	p.tsk, err = ctr.NewTask(ctx, "", false, attachStreamsFunc(stdout, stderr))
85 85
 	if err != nil {
86 86
 		p.deleteTaskAndContainer(ctx)
87 87
 		return err
88 88
 	}
89
+	if err := p.tsk.Start(ctx); err != nil {
90
+		p.deleteTaskAndContainer(ctx)
91
+		return err
92
+	}
89 93
 	e.mu.Lock()
90 94
 	defer e.mu.Unlock()
91 95
 	e.plugins[id] = &p