
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>
178 lines
4.1 KiB
Go
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
|
|
}
|