github.com/dinever/docker@v1.11.1/libcontainerd/container_windows.go (about)

     1  package libcontainerd
     2  
     3  import (
     4  	"io"
     5  	"strings"
     6  	"syscall"
     7  	"time"
     8  
     9  	"github.com/Microsoft/hcsshim"
    10  	"github.com/Sirupsen/logrus"
    11  )
    12  
    13  type container struct {
    14  	containerCommon
    15  
    16  	// Platform specific fields are below here. There are none presently on Windows.
    17  	options []CreateOption
    18  
    19  	// The ociSpec is required, as client.Create() needs a spec,
    20  	// but can be called from the RestartManager context which does not
    21  	// otherwise have access to the Spec
    22  	ociSpec Spec
    23  }
    24  
    25  func (ctr *container) newProcess(friendlyName string) *process {
    26  	return &process{
    27  		processCommon: processCommon{
    28  			containerID:  ctr.containerID,
    29  			friendlyName: friendlyName,
    30  			client:       ctr.client,
    31  		},
    32  	}
    33  }
    34  
    35  func (ctr *container) start() error {
    36  	var err error
    37  
    38  	// Start the container
    39  	logrus.Debugln("Starting container ", ctr.containerID)
    40  	if err = hcsshim.StartComputeSystem(ctr.containerID); err != nil {
    41  		logrus.Errorf("Failed to start compute system: %s", err)
    42  		return err
    43  	}
    44  
    45  	createProcessParms := hcsshim.CreateProcessParams{
    46  		EmulateConsole:   ctr.ociSpec.Process.Terminal,
    47  		WorkingDirectory: ctr.ociSpec.Process.Cwd,
    48  		ConsoleSize:      ctr.ociSpec.Process.InitialConsoleSize,
    49  	}
    50  
    51  	// Configure the environment for the process
    52  	createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
    53  	createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
    54  
    55  	iopipe := &IOPipe{Terminal: ctr.ociSpec.Process.Terminal}
    56  
    57  	// Start the command running in the container. Note we always tell HCS to
    58  	// create stdout as it's required regardless of '-i' or '-t' options, so that
    59  	// docker can always grab the output through logs. We also tell HCS to always
    60  	// create stdin, even if it's not used - it will be closed shortly. Stderr
    61  	// is only created if it we're not -t.
    62  	var pid uint32
    63  	var stdout, stderr io.ReadCloser
    64  	pid, iopipe.Stdin, stdout, stderr, err = hcsshim.CreateProcessInComputeSystem(
    65  		ctr.containerID,
    66  		true,
    67  		true,
    68  		!ctr.ociSpec.Process.Terminal,
    69  		createProcessParms)
    70  	if err != nil {
    71  		logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
    72  
    73  		// Explicitly terminate the compute system here.
    74  		if err2 := hcsshim.TerminateComputeSystem(ctr.containerID, hcsshim.TimeoutInfinite, "CreateProcessInComputeSystem failed"); err2 != nil {
    75  			// Ignore this error, there's not a lot we can do except log it
    76  			logrus.Warnf("Failed to TerminateComputeSystem after a failed CreateProcessInComputeSystem. Ignoring this.", err2)
    77  		} else {
    78  			logrus.Debugln("Cleaned up after failed CreateProcessInComputeSystem by calling TerminateComputeSystem")
    79  		}
    80  		return err
    81  	}
    82  	ctr.startedAt = time.Now()
    83  
    84  	// Convert io.ReadClosers to io.Readers
    85  	if stdout != nil {
    86  		iopipe.Stdout = openReaderFromPipe(stdout)
    87  	}
    88  	if stderr != nil {
    89  		iopipe.Stderr = openReaderFromPipe(stderr)
    90  	}
    91  
    92  	// Save the PID
    93  	logrus.Debugf("Process started - PID %d", pid)
    94  	ctr.systemPid = uint32(pid)
    95  
    96  	// Spin up a go routine waiting for exit to handle cleanup
    97  	go ctr.waitExit(pid, InitFriendlyName, true)
    98  
    99  	ctr.client.appendContainer(ctr)
   100  
   101  	if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil {
   102  		// OK to return the error here, as waitExit will handle tear-down in HCS
   103  		return err
   104  	}
   105  
   106  	// Tell the docker engine that the container has started.
   107  	si := StateInfo{
   108  		State: StateStart,
   109  		Pid:   ctr.systemPid, // Not sure this is needed? Double-check monitor.go in daemon BUGBUG @jhowardmsft
   110  	}
   111  	return ctr.client.backend.StateChanged(ctr.containerID, si)
   112  
   113  }
   114  
   115  // waitExit runs as a goroutine waiting for the process to exit. It's
   116  // equivalent to (in the linux containerd world) where events come in for
   117  // state change notifications from containerd.
   118  func (ctr *container) waitExit(pid uint32, processFriendlyName string, isFirstProcessToStart bool) error {
   119  	logrus.Debugln("waitExit on pid", pid)
   120  
   121  	// Block indefinitely for the process to exit.
   122  	exitCode, err := hcsshim.WaitForProcessInComputeSystem(ctr.containerID, pid, hcsshim.TimeoutInfinite)
   123  	if err != nil {
   124  		if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE {
   125  			logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err)
   126  		}
   127  		// Fall through here, do not return. This ensures we attempt to continue the
   128  		// shutdown in HCS nad tell the docker engine that the process/container
   129  		// has exited to avoid a container being dropped on the floor.
   130  	}
   131  
   132  	// Assume the container has exited
   133  	si := StateInfo{
   134  		State:     StateExit,
   135  		ExitCode:  uint32(exitCode),
   136  		Pid:       pid,
   137  		ProcessID: processFriendlyName,
   138  	}
   139  
   140  	// But it could have been an exec'd process which exited
   141  	if !isFirstProcessToStart {
   142  		si.State = StateExitProcess
   143  	}
   144  
   145  	// If this is the init process, always call into vmcompute.dll to
   146  	// shutdown the container after we have completed.
   147  	if isFirstProcessToStart {
   148  		logrus.Debugf("Shutting down container %s", ctr.containerID)
   149  		// Explicit timeout here rather than hcsshim.TimeoutInfinte to avoid a
   150  		// (remote) possibility that ShutdownComputeSystem hangs indefinitely.
   151  		const shutdownTimeout = 5 * 60 * 1000 // 5 minutes
   152  		if err := hcsshim.ShutdownComputeSystem(ctr.containerID, shutdownTimeout, "waitExit"); err != nil {
   153  			if herr, ok := err.(*hcsshim.HcsError); !ok ||
   154  				(herr.Err != hcsshim.ERROR_SHUTDOWN_IN_PROGRESS &&
   155  					herr.Err != ErrorBadPathname &&
   156  					herr.Err != syscall.ERROR_PATH_NOT_FOUND) {
   157  				logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err)
   158  			}
   159  		} else {
   160  			logrus.Debugf("Completed shutting down container %s", ctr.containerID)
   161  		}
   162  
   163  		// BUGBUG - Is taking the lock necessary here? Should it just be taken for
   164  		// the deleteContainer call, not for the restart logic? @jhowardmsft
   165  		ctr.client.lock(ctr.containerID)
   166  		defer ctr.client.unlock(ctr.containerID)
   167  
   168  		if si.State == StateExit && ctr.restartManager != nil {
   169  			restart, wait, err := ctr.restartManager.ShouldRestart(uint32(exitCode), false, time.Since(ctr.startedAt))
   170  			if err != nil {
   171  				logrus.Error(err)
   172  			} else if restart {
   173  				si.State = StateRestart
   174  				ctr.restarting = true
   175  				go func() {
   176  					err := <-wait
   177  					ctr.restarting = false
   178  					if err != nil {
   179  						si.State = StateExit
   180  						if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil {
   181  							logrus.Error(err)
   182  						}
   183  						logrus.Error(err)
   184  					} else {
   185  						ctr.client.Create(ctr.containerID, ctr.ociSpec, ctr.options...)
   186  					}
   187  				}()
   188  			}
   189  		}
   190  
   191  		// Remove process from list if we have exited
   192  		// We need to do so here in case the Message Handler decides to restart it.
   193  		if si.State == StateExit {
   194  			ctr.client.deleteContainer(ctr.friendlyName)
   195  		}
   196  	}
   197  
   198  	// Call into the backend to notify it of the state change.
   199  	logrus.Debugf("waitExit() calling backend.StateChanged %v", si)
   200  	if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil {
   201  		logrus.Error(err)
   202  	}
   203  
   204  	logrus.Debugln("waitExit() completed OK")
   205  	return nil
   206  }