containerd/windows/process.go
Michael Crosby 9f13b414b9 Return exit status from Wait of stopped process
This changes Wait() from returning an error whenever you call wait on a
stopped process/task to returning the exit status from the process.

This also adds the exit status to the Status() call on a process/task so
that a user can Wait(), check status, then cancel the wait to avoid
races in event handling.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2017-08-03 17:22:33 -04:00

178 lines
4.1 KiB
Go

// +build windows
package windows
import (
"context"
"io"
"syscall"
"time"
"github.com/Microsoft/hcsshim"
eventsapi "github.com/containerd/containerd/api/services/events/v1"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/runtime"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// process implements containerd.Process and containerd.State
type process struct {
hcs hcsshim.Process
id string
pid uint32
io *pipeSet
status runtime.Status
task *task
exitCh chan struct{}
exitCode uint32
exitTime time.Time
conf *hcsshim.ProcessConfig
}
func (p *process) ID() string {
return p.id
}
func (p *process) State(ctx context.Context) (runtime.State, error) {
return runtime.State{
Status: p.Status(),
Pid: p.pid,
Stdin: p.io.src.Stdin,
Stdout: p.io.src.Stdout,
Stderr: p.io.src.Stderr,
Terminal: p.io.src.Terminal,
ExitStatus: p.exitCode,
}, nil
}
func (p *process) Status() runtime.Status {
if p.task.getStatus() == runtime.PausedStatus {
return runtime.PausedStatus
}
var status runtime.Status
select {
case <-p.exitCh:
status = runtime.StoppedStatus
default:
status = runtime.RunningStatus
}
return status
}
func (p *process) Kill(ctx context.Context, sig uint32, all bool) error {
// On windows all signals kill the process
return errors.Wrap(p.hcs.Kill(), "failed to kill process")
}
func (p *process) ResizePty(ctx context.Context, size runtime.ConsoleSize) error {
err := p.hcs.ResizeConsole(uint16(size.Width), uint16(size.Height))
return errors.Wrap(err, "failed to resize process console")
}
func (p *process) CloseIO(ctx context.Context) error {
return errors.Wrap(p.hcs.CloseStdin(), "failed to close stdin")
}
func (p *process) Pid() uint32 {
return p.pid
}
func (p *process) ExitCode() (uint32, time.Time, error) {
if p.Status() != runtime.StoppedStatus {
return 255, time.Time{}, errors.Wrap(errdefs.ErrFailedPrecondition, "process is not stopped")
}
return p.exitCode, p.exitTime, nil
}
func (p *process) Start(ctx context.Context) (err error) {
// If we fail, close the io right now
defer func() {
if err != nil {
p.io.Close()
}
}()
var hp hcsshim.Process
if hp, err = p.task.hcsContainer.CreateProcess(p.conf); err != nil {
return errors.Wrapf(err, "failed to create process")
}
stdin, stdout, stderr, err := hp.Stdio()
if err != nil {
hp.Kill()
return errors.Wrapf(err, "failed to retrieve init process stdio")
}
ioCopy := func(name string, dst io.WriteCloser, src io.ReadCloser) {
log.G(ctx).WithFields(logrus.Fields{"id": p.id, "pid": p.pid}).
Debugf("%s: copy started", name)
io.Copy(dst, src)
log.G(ctx).WithFields(logrus.Fields{"id": p.id, "pid": p.pid}).
Debugf("%s: copy done", name)
dst.Close()
src.Close()
}
if p.io.stdin != nil {
go ioCopy("stdin", stdin, p.io.stdin)
}
if p.io.stdout != nil {
go ioCopy("stdout", p.io.stdout, stdout)
}
if p.io.stderr != nil {
go ioCopy("stderr", p.io.stderr, stderr)
}
// Wait for the process to exit to get the exit status
go func() {
if err := hp.Wait(); err != nil {
herr, ok := err.(*hcsshim.ProcessError)
if ok && herr.Err != syscall.ERROR_BROKEN_PIPE {
log.G(ctx).
WithError(err).
WithFields(logrus.Fields{"id": p.id, "pid": p.pid}).
Warnf("hcsshim wait failed (process may have been killed)")
}
// Try to get the exit code nonetheless
}
p.exitTime = time.Now()
ec, err := hp.ExitCode()
if err != nil {
log.G(ctx).
WithError(err).
WithFields(logrus.Fields{"id": p.id, "pid": p.pid}).
Warnf("hcsshim could not retrieve exit code")
// Use the unknown exit code
ec = 255
}
p.exitCode = uint32(ec)
p.task.publisher.Publish(ctx,
runtime.TaskExitEventTopic,
&eventsapi.TaskExit{
ContainerID: p.task.id,
ID: p.id,
Pid: p.pid,
ExitStatus: p.exitCode,
ExitedAt: p.exitTime,
})
close(p.exitCh)
// Ensure io's are closed
p.io.Close()
// Cleanup HCS resources
hp.Close()
}()
p.status = runtime.RunningStatus
p.hcs = hp
return nil
}