 9f13b414b9
			
		
	
	9f13b414b9
	
	
	
		
			
			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
 | |
| }
 |