
In all of the examples, its recommended to call `Wait()` before starting a process/task. Since `Wait()` is a blocking call, this means it must be called from a goroutine like so: ```go statusC := make(chan uint32) go func() { status, err := task.Wait(ctx) if err != nil { // handle async err } statusC <- status }() task.Start(ctx) <-statusC ``` This means there is a race here where there is no guarentee when the goroutine is going to be scheduled, and even a bit more since this requires an RPC call to be made. In addition, this code is very messy and a common pattern for any caller using Wait+Start. Instead, this changes `Wait()` to use an async model having `Wait()` return a channel instead of the code itself. This ensures that when `Wait()` returns that the client has a handle on the event stream (already made the RPC request) before returning and reduces any sort of race to how the stream is handled by grpc since we can't guarentee that we have a goroutine running and blocked on `Recv()`. Making `Wait()` async also cleans up the code in the caller drastically: ```go statusC, err := task.Wait(ctx) if err != nil { return err } task.Start(ctx) status := <-statusC if status.Err != nil { return err } ``` No more spinning up goroutines and more natural error handling for the caller. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
54 lines
1.5 KiB
Go
54 lines
1.5 KiB
Go
package containerd
|
|
|
|
import (
|
|
"context"
|
|
"syscall"
|
|
|
|
"github.com/containerd/containerd/errdefs"
|
|
"github.com/containerd/containerd/linux/runcopts"
|
|
"github.com/containerd/containerd/mount"
|
|
)
|
|
|
|
// NewTaskOpts allows the caller to set options on a new task
|
|
type NewTaskOpts func(context.Context, *Client, *TaskInfo) error
|
|
|
|
// WithRootFS allows a task to be created without a snapshot being allocated to its container
|
|
func WithRootFS(mounts []mount.Mount) NewTaskOpts {
|
|
return func(ctx context.Context, c *Client, ti *TaskInfo) error {
|
|
ti.RootFS = mounts
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithExit causes the task to exit after a successful checkpoint
|
|
func WithExit(r *CheckpointTaskInfo) error {
|
|
r.Options = &runcopts.CheckpointOptions{
|
|
Exit: true,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ProcessDeleteOpts allows the caller to set options for the deletion of a task
|
|
type ProcessDeleteOpts func(context.Context, Process) error
|
|
|
|
// WithProcessKill will forcefully kill and delete a process
|
|
func WithProcessKill(ctx context.Context, p Process) error {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
// ignore errors to wait and kill as we are forcefully killing
|
|
// the process and don't care about the exit status
|
|
s, err := p.Wait(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := p.Kill(ctx, syscall.SIGKILL); err != nil {
|
|
if errdefs.IsFailedPrecondition(err) || errdefs.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
// wait for the process to fully stop before letting the rest of the deletion complete
|
|
<-s
|
|
return nil
|
|
}
|