Make Wait() async
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>
This commit is contained in:
parent
89da512692
commit
026896ac4c
@ -208,30 +208,22 @@ func (w *worker) runContainer(ctx context.Context, id string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer task.Delete(ctx, containerd.WithProcessKill)
|
defer task.Delete(ctx, containerd.WithProcessKill)
|
||||||
var (
|
|
||||||
start sync.WaitGroup
|
statusC, err := task.Wait(ctx)
|
||||||
status = make(chan uint32, 1)
|
if err != nil {
|
||||||
)
|
return err
|
||||||
start.Add(1)
|
}
|
||||||
go func() {
|
|
||||||
start.Done()
|
|
||||||
s, err := task.Wait(w.waitContext)
|
|
||||||
if err != nil {
|
|
||||||
if err == context.DeadlineExceeded ||
|
|
||||||
err == context.Canceled {
|
|
||||||
close(status)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.failures++
|
|
||||||
logrus.WithError(err).Errorf("wait task %s", id)
|
|
||||||
}
|
|
||||||
status <- s
|
|
||||||
}()
|
|
||||||
start.Wait()
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
<-status
|
status := <-statusC
|
||||||
|
if status.Err != nil {
|
||||||
|
if status.Err == context.DeadlineExceeded || status.Err == context.Canceled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.failures++
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,12 @@ var taskAttachCommand = cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
|
statusC, err := task.Wait(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if tty {
|
if tty {
|
||||||
if err := handleConsoleResize(ctx, task, con); err != nil {
|
if err := handleConsoleResize(ctx, task, con); err != nil {
|
||||||
logrus.WithError(err).Error("console resize")
|
logrus.WithError(err).Error("console resize")
|
||||||
@ -52,12 +58,13 @@ var taskAttachCommand = cli.Command{
|
|||||||
sigc := forwardAllSignals(ctx, task)
|
sigc := forwardAllSignals(ctx, task)
|
||||||
defer stopCatch(sigc)
|
defer stopCatch(sigc)
|
||||||
}
|
}
|
||||||
status, err := task.Wait(ctx)
|
|
||||||
if err != nil {
|
ec := <-statusC
|
||||||
|
if ec.Err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status != 0 {
|
if ec.Code != 0 {
|
||||||
return cli.NewExitError("", int(status))
|
return cli.NewExitError("", int(ec.Code))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -70,14 +70,11 @@ var taskExecCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
defer process.Delete(ctx)
|
defer process.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := process.Wait(ctx)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
logrus.WithError(err).Error("wait process")
|
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
var con console.Console
|
var con console.Console
|
||||||
if tty {
|
if tty {
|
||||||
con = console.Current()
|
con = console.Current()
|
||||||
@ -98,8 +95,11 @@ var taskExecCommand = cli.Command{
|
|||||||
defer stopCatch(sigc)
|
defer stopCatch(sigc)
|
||||||
}
|
}
|
||||||
status := <-statusC
|
status := <-statusC
|
||||||
if status != 0 {
|
if status.Err != nil {
|
||||||
return cli.NewExitError("", int(status))
|
return status.Err
|
||||||
|
}
|
||||||
|
if status.Code != 0 {
|
||||||
|
return cli.NewExitError("", int(status.Code))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -129,14 +129,11 @@ var runCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
logrus.WithError(err).Error("wait process")
|
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
var con console.Console
|
var con console.Console
|
||||||
if tty {
|
if tty {
|
||||||
con = console.Current()
|
con = console.Current()
|
||||||
@ -158,11 +155,15 @@ var runCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
status := <-statusC
|
status := <-statusC
|
||||||
|
if status.Err != nil {
|
||||||
|
return status.Err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := task.Delete(ctx); err != nil {
|
if _, err := task.Delete(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status != 0 {
|
if status.Code != 0 {
|
||||||
return cli.NewExitError("", int(status))
|
return cli.NewExitError("", int(status.Code))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -47,14 +47,11 @@ var taskStartCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
logrus.WithError(err).Error("wait process")
|
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
var con console.Console
|
var con console.Console
|
||||||
if tty {
|
if tty {
|
||||||
con = console.Current()
|
con = console.Current()
|
||||||
@ -76,11 +73,14 @@ var taskStartCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
status := <-statusC
|
status := <-statusC
|
||||||
|
if status.Err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if _, err := task.Delete(ctx); err != nil {
|
if _, err := task.Delete(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status != 0 {
|
if status.Code != 0 {
|
||||||
return cli.NewExitError("", int(status))
|
return cli.NewExitError("", int(status.Code))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -47,14 +47,11 @@ func TestCheckpointRestore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -79,13 +76,11 @@ func TestCheckpointRestore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
go func() {
|
statusC, err = task.Wait(ctx)
|
||||||
status, err := task.Wait(ctx)
|
if err != nil {
|
||||||
if err != nil {
|
t.Error(err)
|
||||||
t.Error(err)
|
return
|
||||||
}
|
}
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -137,14 +132,11 @@ func TestCheckpointRestoreNewContainer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -177,13 +169,11 @@ func TestCheckpointRestoreNewContainer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
go func() {
|
statusC, err = task.Wait(ctx)
|
||||||
status, err := task.Wait(ctx)
|
if err != nil {
|
||||||
if err != nil {
|
t.Error(err)
|
||||||
t.Error(err)
|
return
|
||||||
}
|
}
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -240,14 +230,11 @@ func TestCheckpointLeaveRunning(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -57,14 +57,11 @@ func TestContainerUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
// check that the task has a limit of 32mb
|
// check that the task has a limit of 32mb
|
||||||
cgroup, err := cgroups.Load(cgroups.V1, cgroups.PidPath(int(task.Pid())))
|
cgroup, err := cgroups.Load(cgroups.V1, cgroups.PidPath(int(task.Pid())))
|
||||||
@ -157,14 +154,12 @@ func TestShimInCgroup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
// check to see if the shim is inside the cgroup
|
// check to see if the shim is inside the cgroup
|
||||||
processes, err := cg.Processes(cgroups.Devices, false)
|
processes, err := cg.Processes(cgroups.Devices, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -221,17 +216,11 @@ func TestDaemonRestart(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
synC := make(chan struct{})
|
statusC, err := task.Wait(ctx)
|
||||||
statusC := make(chan uint32, 1)
|
if err != nil {
|
||||||
go func() {
|
t.Error(err)
|
||||||
synC <- struct{}{}
|
return
|
||||||
status, err := task.Wait(ctx)
|
}
|
||||||
if err == nil {
|
|
||||||
t.Errorf(`first task.Wait() should have failed with "transport is closing"`)
|
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
<-synC
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -242,7 +231,10 @@ func TestDaemonRestart(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
<-statusC
|
status := <-statusC
|
||||||
|
if status.Err == nil {
|
||||||
|
t.Errorf(`first task.Wait() should have failed with "transport is closing"`)
|
||||||
|
}
|
||||||
|
|
||||||
waitCtx, waitCancel := context.WithTimeout(ctx, 2*time.Second)
|
waitCtx, waitCancel := context.WithTimeout(ctx, 2*time.Second)
|
||||||
serving, err := client.IsServing(waitCtx)
|
serving, err := client.IsServing(waitCtx)
|
||||||
@ -251,15 +243,11 @@ func TestDaemonRestart(t *testing.T) {
|
|||||||
t.Fatalf("containerd did not start within 2s: %v", err)
|
t.Fatalf("containerd did not start within 2s: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
statusC, err = task.Wait(ctx)
|
||||||
synC <- struct{}{}
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
<-synC
|
|
||||||
|
|
||||||
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -124,14 +124,11 @@ func TestContainerStart(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if pid := task.Pid(); pid <= 0 {
|
if pid := task.Pid(); pid <= 0 {
|
||||||
t.Errorf("invalid task pid %d", pid)
|
t.Errorf("invalid task pid %d", pid)
|
||||||
@ -142,15 +139,21 @@ func TestContainerStart(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
status := <-statusC
|
status := <-statusC
|
||||||
if status != 7 {
|
if status.Err != nil {
|
||||||
t.Errorf("expected status 7 from wait but received %d", status)
|
|
||||||
}
|
|
||||||
if status, err = task.Delete(ctx); err != nil {
|
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status != 7 {
|
if status.Code != 7 {
|
||||||
t.Errorf("expected status 7 from delete but received %d", status)
|
t.Errorf("expected status 7 from wait but received %d", status.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteStatus, err := task.Delete(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if deleteStatus != 7 {
|
||||||
|
t.Errorf("expected status 7 from delete but received %d", deleteStatus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,14 +202,11 @@ func TestContainerOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -214,7 +214,7 @@ func TestContainerOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status := <-statusC
|
status := <-statusC
|
||||||
if status != 0 {
|
if status.Code != 0 {
|
||||||
t.Errorf("expected status 0 but received %d", status)
|
t.Errorf("expected status 0 but received %d", status)
|
||||||
}
|
}
|
||||||
if _, err := task.Delete(ctx); err != nil {
|
if _, err := task.Delete(ctx); err != nil {
|
||||||
@ -273,13 +273,11 @@ func TestContainerExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
finished := make(chan struct{}, 1)
|
finishedC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
if _, err := task.Wait(ctx); err != nil {
|
t.Error(err)
|
||||||
t.Error(err)
|
return
|
||||||
}
|
}
|
||||||
close(finished)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -295,14 +293,11 @@ func TestContainerExec(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
processStatusC := make(chan uint32, 1)
|
processStatusC, err := process.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := process.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
processStatusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := process.Start(ctx); err != nil {
|
if err := process.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -311,9 +306,13 @@ func TestContainerExec(t *testing.T) {
|
|||||||
|
|
||||||
// wait for the exec to return
|
// wait for the exec to return
|
||||||
status := <-processStatusC
|
status := <-processStatusC
|
||||||
|
if status.Err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if status != 6 {
|
if status.Code != 6 {
|
||||||
t.Errorf("expected exec exit code 6 but received %d", status)
|
t.Errorf("expected exec exit code 6 but received %d", status.Code)
|
||||||
}
|
}
|
||||||
deleteStatus, err := process.Delete(ctx)
|
deleteStatus, err := process.Delete(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -326,7 +325,7 @@ func TestContainerExec(t *testing.T) {
|
|||||||
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
<-finished
|
<-finishedC
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerPids(t *testing.T) {
|
func TestContainerPids(t *testing.T) {
|
||||||
@ -372,14 +371,11 @@ func TestContainerPids(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -462,14 +458,11 @@ func TestContainerCloseIO(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -576,14 +569,10 @@ func TestContainerAttach(t *testing.T) {
|
|||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
originalIO := task.IO()
|
originalIO := task.IO()
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
}
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -620,7 +609,11 @@ func TestContainerAttach(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
<-statusC
|
status := <-statusC
|
||||||
|
if status.Err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
originalIO.Close()
|
originalIO.Close()
|
||||||
if _, err := task.Delete(ctx); err != nil {
|
if _, err := task.Delete(ctx); err != nil {
|
||||||
@ -681,14 +674,11 @@ func TestDeleteRunningContainer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -752,14 +742,11 @@ func TestContainerKill(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -878,19 +865,15 @@ func TestContainerExecNoBinaryExists(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
|
finishedC, err := task.Wait(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
finished := make(chan struct{}, 1)
|
|
||||||
go func() {
|
|
||||||
if _, err := task.Wait(ctx); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
close(finished)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// start an exec process without running the original container process
|
// start an exec process without running the original container process
|
||||||
processSpec := spec.Process
|
processSpec := spec.Process
|
||||||
processSpec.Args = []string{
|
processSpec.Args = []string{
|
||||||
@ -909,7 +892,7 @@ func TestContainerExecNoBinaryExists(t *testing.T) {
|
|||||||
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
<-finished
|
<-finishedC
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserNamespaces(t *testing.T) {
|
func TestUserNamespaces(t *testing.T) {
|
||||||
@ -962,14 +945,11 @@ func TestUserNamespaces(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if pid := task.Pid(); pid <= 0 {
|
if pid := task.Pid(); pid <= 0 {
|
||||||
t.Errorf("invalid task pid %d", pid)
|
t.Errorf("invalid task pid %d", pid)
|
||||||
@ -980,15 +960,20 @@ func TestUserNamespaces(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
status := <-statusC
|
status := <-statusC
|
||||||
if status != 7 {
|
if status.Err != nil {
|
||||||
t.Errorf("expected status 7 from wait but received %d", status)
|
|
||||||
}
|
|
||||||
if status, err = task.Delete(ctx); err != nil {
|
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status != 7 {
|
if status.Code != 7 {
|
||||||
t.Errorf("expected status 7 from delete but received %d", status)
|
t.Errorf("expected status 7 from wait but received %d", status.Code)
|
||||||
|
}
|
||||||
|
deleteStatus, err := task.Delete(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if deleteStatus != 7 {
|
||||||
|
t.Errorf("expected status 7 from delete but received %d", deleteStatus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1035,14 +1020,11 @@ func TestWaitStoppedTask(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if pid := task.Pid(); pid <= 0 {
|
if pid := task.Pid(); pid <= 0 {
|
||||||
t.Errorf("invalid task pid %d", pid)
|
t.Errorf("invalid task pid %d", pid)
|
||||||
@ -1052,15 +1034,21 @@ func TestWaitStoppedTask(t *testing.T) {
|
|||||||
task.Delete(ctx)
|
task.Delete(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for the task to stop then call wait again
|
// wait for the task to stop then call wait again
|
||||||
<-statusC
|
<-statusC
|
||||||
status, err := task.Wait(ctx)
|
statusC, err = task.Wait(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status != 7 {
|
status := <-statusC
|
||||||
t.Errorf("exit status from stopped task should be 7 but received %d", status)
|
if status.Err != nil {
|
||||||
|
t.Error(status.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if status.Code != 7 {
|
||||||
|
t.Errorf("exit status from stopped task should be 7 but received %d", status.Code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1107,13 +1095,10 @@ func TestWaitStoppedProcess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
finished := make(chan struct{}, 1)
|
finishedC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
if _, err := task.Wait(ctx); err != nil {
|
t.Error(err)
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
close(finished)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -1130,14 +1115,12 @@ func TestWaitStoppedProcess(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer process.Delete(ctx)
|
defer process.Delete(ctx)
|
||||||
processStatusC := make(chan uint32, 1)
|
|
||||||
go func() {
|
statusC, err := process.Wait(ctx)
|
||||||
status, err := process.Wait(ctx)
|
if err != nil {
|
||||||
if err != nil {
|
t.Error(err)
|
||||||
t.Error(err)
|
return
|
||||||
}
|
}
|
||||||
processStatusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := process.Start(ctx); err != nil {
|
if err := process.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -1145,20 +1128,27 @@ func TestWaitStoppedProcess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wait for the exec to return
|
// wait for the exec to return
|
||||||
<-processStatusC
|
<-statusC
|
||||||
|
|
||||||
// try to wait on the process after it has stopped
|
// try to wait on the process after it has stopped
|
||||||
status, err := process.Wait(ctx)
|
statusC, err = process.Wait(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status != 6 {
|
status := <-statusC
|
||||||
t.Errorf("exit status from stopped process should be 6 but received %d", status)
|
if status.Err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if status.Code != 6 {
|
||||||
|
t.Errorf("exit status from stopped process should be 6 but received %d", status.Code)
|
||||||
|
}
|
||||||
|
|
||||||
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
<-finished
|
<-finishedC
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaskForceDelete(t *testing.T) {
|
func TestTaskForceDelete(t *testing.T) {
|
||||||
@ -1256,14 +1246,11 @@ func TestProcessForceDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
// task must be started on windows
|
// task must be started on windows
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
@ -1344,14 +1331,11 @@ func TestContainerHostname(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
return
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -1359,7 +1343,11 @@ func TestContainerHostname(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status := <-statusC
|
status := <-statusC
|
||||||
if status != 0 {
|
if status.Err != nil {
|
||||||
|
t.Error(status.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if status.Code != 0 {
|
||||||
t.Errorf("expected status 0 but received %d", status)
|
t.Errorf("expected status 0 but received %d", status)
|
||||||
}
|
}
|
||||||
if _, err := task.Delete(ctx); err != nil {
|
if _, err := task.Delete(ctx); err != nil {
|
||||||
@ -1420,14 +1408,10 @@ func TestContainerExitedAtSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
statusC := make(chan uint32, 1)
|
statusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
t.Error(err)
|
||||||
if err != nil {
|
}
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
statusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
@ -1436,7 +1420,7 @@ func TestContainerExitedAtSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status := <-statusC
|
status := <-statusC
|
||||||
if status != 0 {
|
if status.Code != 0 {
|
||||||
t.Errorf("expected status 0 but received %d", status)
|
t.Errorf("expected status 0 but received %d", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1495,13 +1479,10 @@ func TestDeleteContainerExecCreated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
finished := make(chan struct{}, 1)
|
finished, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
if _, err := task.Wait(ctx); err != nil {
|
t.Error(err)
|
||||||
t.Error(err)
|
}
|
||||||
}
|
|
||||||
close(finished)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -163,14 +163,10 @@ You always want to make sure you `Wait` before calling `Start` on a task.
|
|||||||
This makes sure that you do not encounter any races if the task has a simple program like `/bin/true` that exits promptly after calling start.
|
This makes sure that you do not encounter any races if the task has a simple program like `/bin/true` that exits promptly after calling start.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
exitStatusC := make(chan uint32, 1)
|
exitStatusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
exitStatusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -192,7 +188,10 @@ To do this we will simply call `Kill` on the task after waiting a couple of seco
|
|||||||
}
|
}
|
||||||
|
|
||||||
status := <-exitStatusC
|
status := <-exitStatusC
|
||||||
fmt.Printf("redis-server exited with status: %d\n", status)
|
if status.Err != nil {
|
||||||
|
return status.Err
|
||||||
|
}
|
||||||
|
fmt.Printf("redis-server exited with status: %d\n", status.Code)
|
||||||
```
|
```
|
||||||
|
|
||||||
We wait on our exit status channel that we setup to ensure the task has fully exited and we get the exit status.
|
We wait on our exit status channel that we setup to ensure the task has fully exited and we get the exit status.
|
||||||
@ -271,14 +270,10 @@ func redisExample() error {
|
|||||||
defer task.Delete(ctx)
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
// make sure we wait before calling start
|
// make sure we wait before calling start
|
||||||
exitStatusC := make(chan uint32, 1)
|
exitStatusC, err := task.Wait(ctx)
|
||||||
go func() {
|
if err != nil {
|
||||||
status, err := task.Wait(ctx)
|
fmt.Println(err)
|
||||||
if err != nil {
|
}
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
exitStatusC <- status
|
|
||||||
}()
|
|
||||||
|
|
||||||
// call start on the task to execute the redis server
|
// call start on the task to execute the redis server
|
||||||
if err := task.Start(ctx); err != nil {
|
if err := task.Start(ctx); err != nil {
|
||||||
@ -296,7 +291,10 @@ func redisExample() error {
|
|||||||
// wait for the process to fully exit and print out the exit status
|
// wait for the process to fully exit and print out the exit status
|
||||||
|
|
||||||
status := <-exitStatusC
|
status := <-exitStatusC
|
||||||
fmt.Printf("redis-server exited with status: %d\n", status)
|
if status.Err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("redis-server exited with status: %d\n", status.Code)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
65
process.go
65
process.go
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
eventsapi "github.com/containerd/containerd/api/services/events/v1"
|
eventsapi "github.com/containerd/containerd/api/services/events/v1"
|
||||||
"github.com/containerd/containerd/api/services/tasks/v1"
|
"github.com/containerd/containerd/api/services/tasks/v1"
|
||||||
@ -24,8 +25,8 @@ type Process interface {
|
|||||||
Delete(context.Context, ...ProcessDeleteOpts) (uint32, error)
|
Delete(context.Context, ...ProcessDeleteOpts) (uint32, error)
|
||||||
// Kill sends the provided signal to the process
|
// Kill sends the provided signal to the process
|
||||||
Kill(context.Context, syscall.Signal) error
|
Kill(context.Context, syscall.Signal) error
|
||||||
// Wait blocks until the process has exited returning the exit status
|
// Wait asynchronously waits for the process to exit, and sends the exit code to the returned channel
|
||||||
Wait(context.Context) (uint32, error)
|
Wait(context.Context) (<-chan ExitStatus, error)
|
||||||
// CloseIO allows various pipes to be closed on the process
|
// CloseIO allows various pipes to be closed on the process
|
||||||
CloseIO(context.Context, ...IOCloserOpts) error
|
CloseIO(context.Context, ...IOCloserOpts) error
|
||||||
// Resize changes the width and heigh of the process's terminal
|
// Resize changes the width and heigh of the process's terminal
|
||||||
@ -36,6 +37,16 @@ type Process interface {
|
|||||||
Status(context.Context) (Status, error)
|
Status(context.Context) (Status, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExitStatus encapsulates a process' exit code.
|
||||||
|
// It is used by `Wait()` to return either a process exit code or an error
|
||||||
|
// The `Err` field is provided to return an error that may occur while waiting
|
||||||
|
// `Err` is not used to convey an error with the process itself.
|
||||||
|
type ExitStatus struct {
|
||||||
|
Code uint32
|
||||||
|
ExitedAt time.Time
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
type process struct {
|
type process struct {
|
||||||
id string
|
id string
|
||||||
task *task
|
task *task
|
||||||
@ -79,39 +90,55 @@ func (p *process) Kill(ctx context.Context, s syscall.Signal) error {
|
|||||||
return errdefs.FromGRPC(err)
|
return errdefs.FromGRPC(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *process) Wait(ctx context.Context) (uint32, error) {
|
func (p *process) Wait(ctx context.Context) (<-chan ExitStatus, error) {
|
||||||
cancellable, cancel := context.WithCancel(ctx)
|
cancellable, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
|
||||||
eventstream, err := p.task.client.EventService().Subscribe(cancellable, &eventsapi.SubscribeRequest{
|
eventstream, err := p.task.client.EventService().Subscribe(cancellable, &eventsapi.SubscribeRequest{
|
||||||
Filters: []string{"topic==" + runtime.TaskExitEventTopic},
|
Filters: []string{"topic==" + runtime.TaskExitEventTopic},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownExitStatus, err
|
cancel()
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
// first check if the task has exited
|
// first check if the task has exited
|
||||||
status, err := p.Status(ctx)
|
status, err := p.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownExitStatus, errdefs.FromGRPC(err)
|
cancel()
|
||||||
|
return nil, errdefs.FromGRPC(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chStatus := make(chan ExitStatus, 1)
|
||||||
if status.Status == Stopped {
|
if status.Status == Stopped {
|
||||||
return status.ExitStatus, nil
|
cancel()
|
||||||
|
chStatus <- ExitStatus{Code: status.ExitStatus, ExitedAt: status.ExitTime}
|
||||||
|
return chStatus, nil
|
||||||
}
|
}
|
||||||
for {
|
|
||||||
evt, err := eventstream.Recv()
|
go func() {
|
||||||
if err != nil {
|
defer cancel()
|
||||||
return UnknownExitStatus, err
|
chStatus <- ExitStatus{} // signal that the goroutine is running
|
||||||
}
|
for {
|
||||||
if typeurl.Is(evt.Event, &eventsapi.TaskExit{}) {
|
evt, err := eventstream.Recv()
|
||||||
v, err := typeurl.UnmarshalAny(evt.Event)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownExitStatus, err
|
chStatus <- ExitStatus{Code: UnknownExitStatus, Err: err}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
e := v.(*eventsapi.TaskExit)
|
if typeurl.Is(evt.Event, &eventsapi.TaskExit{}) {
|
||||||
if e.ID == p.id && e.ContainerID == p.task.id {
|
v, err := typeurl.UnmarshalAny(evt.Event)
|
||||||
return e.ExitStatus, nil
|
if err != nil {
|
||||||
|
chStatus <- ExitStatus{Code: UnknownExitStatus, Err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e := v.(*eventsapi.TaskExit)
|
||||||
|
if e.ID == p.id && e.ContainerID == p.task.id {
|
||||||
|
chStatus <- ExitStatus{Code: e.ExitStatus, ExitedAt: e.ExitedAt}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
|
|
||||||
|
<-chStatus // wait for the goroutine to be running
|
||||||
|
return chStatus, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *process) CloseIO(ctx context.Context, opts ...IOCloserOpts) error {
|
func (p *process) CloseIO(ctx context.Context, opts ...IOCloserOpts) error {
|
||||||
|
51
task.go
51
task.go
@ -201,15 +201,18 @@ func (t *task) Status(ctx context.Context) (Status, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *task) Wait(ctx context.Context) (uint32, error) {
|
func (t *task) Wait(ctx context.Context) (<-chan ExitStatus, error) {
|
||||||
cancellable, cancel := context.WithCancel(ctx)
|
cancellable, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
|
||||||
eventstream, err := t.client.EventService().Subscribe(cancellable, &eventsapi.SubscribeRequest{
|
eventstream, err := t.client.EventService().Subscribe(cancellable, &eventsapi.SubscribeRequest{
|
||||||
Filters: []string{"topic==" + runtime.TaskExitEventTopic},
|
Filters: []string{"topic==" + runtime.TaskExitEventTopic},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownExitStatus, errdefs.FromGRPC(err)
|
cancel()
|
||||||
|
return nil, errdefs.FromGRPC(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chStatus := make(chan ExitStatus, 1)
|
||||||
|
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
checkpoint := t.deferred != nil
|
checkpoint := t.deferred != nil
|
||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
@ -217,28 +220,42 @@ func (t *task) Wait(ctx context.Context) (uint32, error) {
|
|||||||
// first check if the task has exited
|
// first check if the task has exited
|
||||||
status, err := t.Status(ctx)
|
status, err := t.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownExitStatus, errdefs.FromGRPC(err)
|
cancel()
|
||||||
|
return nil, errdefs.FromGRPC(err)
|
||||||
}
|
}
|
||||||
if status.Status == Stopped {
|
if status.Status == Stopped {
|
||||||
return status.ExitStatus, nil
|
cancel()
|
||||||
|
chStatus <- ExitStatus{Code: status.ExitStatus, ExitedAt: status.ExitTime}
|
||||||
|
return chStatus, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for {
|
|
||||||
evt, err := eventstream.Recv()
|
go func() {
|
||||||
if err != nil {
|
defer cancel()
|
||||||
return UnknownExitStatus, errdefs.FromGRPC(err)
|
chStatus <- ExitStatus{} // signal that goroutine is running
|
||||||
}
|
for {
|
||||||
if typeurl.Is(evt.Event, &eventsapi.TaskExit{}) {
|
evt, err := eventstream.Recv()
|
||||||
v, err := typeurl.UnmarshalAny(evt.Event)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownExitStatus, err
|
chStatus <- ExitStatus{Code: UnknownExitStatus, Err: errdefs.FromGRPC(err)}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
e := v.(*eventsapi.TaskExit)
|
if typeurl.Is(evt.Event, &eventsapi.TaskExit{}) {
|
||||||
if e.ContainerID == t.id && e.Pid == t.pid {
|
v, err := typeurl.UnmarshalAny(evt.Event)
|
||||||
return e.ExitStatus, nil
|
if err != nil {
|
||||||
|
chStatus <- ExitStatus{Code: UnknownExitStatus, Err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e := v.(*eventsapi.TaskExit)
|
||||||
|
if e.ContainerID == t.id && e.Pid == t.pid {
|
||||||
|
chStatus <- ExitStatus{Code: e.ExitStatus, ExitedAt: e.ExitedAt}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
|
|
||||||
|
<-chStatus // wait for the goroutine to be running
|
||||||
|
return chStatus, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the task and its runtime state
|
// Delete deletes the task and its runtime state
|
||||||
|
@ -33,15 +33,14 @@ type ProcessDeleteOpts func(context.Context, Process) error
|
|||||||
|
|
||||||
// WithProcessKill will forcefully kill and delete a process
|
// WithProcessKill will forcefully kill and delete a process
|
||||||
func WithProcessKill(ctx context.Context, p Process) error {
|
func WithProcessKill(ctx context.Context, p Process) error {
|
||||||
s := make(chan struct{}, 1)
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
// ignore errors to wait and kill as we are forcefully killing
|
// ignore errors to wait and kill as we are forcefully killing
|
||||||
// the process and don't care about the exit status
|
// the process and don't care about the exit status
|
||||||
go func() {
|
s, err := p.Wait(ctx)
|
||||||
p.Wait(ctx)
|
if err != nil {
|
||||||
close(s)
|
return err
|
||||||
}()
|
}
|
||||||
if err := p.Kill(ctx, syscall.SIGKILL); err != nil {
|
if err := p.Kill(ctx, syscall.SIGKILL); err != nil {
|
||||||
if errdefs.IsFailedPrecondition(err) || errdefs.IsNotFound(err) {
|
if errdefs.IsFailedPrecondition(err) || errdefs.IsNotFound(err) {
|
||||||
return nil
|
return nil
|
||||||
|
Loading…
Reference in New Issue
Block a user