github.com/noxiouz/docker@v0.7.3-0.20160629055221-3d231c78e8c5/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 manualStopRequested bool 25 hcsContainer hcsshim.Container 26 } 27 28 func (ctr *container) newProcess(friendlyName string) *process { 29 return &process{ 30 processCommon: processCommon{ 31 containerID: ctr.containerID, 32 friendlyName: friendlyName, 33 client: ctr.client, 34 }, 35 } 36 } 37 38 func (ctr *container) start() error { 39 var err error 40 isServicing := false 41 42 for _, option := range ctr.options { 43 if s, ok := option.(*ServicingOption); ok && s.IsServicing { 44 isServicing = true 45 } 46 } 47 48 // Start the container. If this is a servicing container, this call will block 49 // until the container is done with the servicing execution. 50 logrus.Debugln("Starting container ", ctr.containerID) 51 if err = ctr.hcsContainer.Start(); err != nil { 52 logrus.Errorf("Failed to start container: %s", err) 53 if err := ctr.terminate(); err != nil { 54 logrus.Errorf("Failed to cleanup after a failed Start. %s", err) 55 } else { 56 logrus.Debugln("Cleaned up after failed Start by calling Terminate") 57 } 58 return err 59 } 60 61 // Note we always tell HCS to 62 // create stdout as it's required regardless of '-i' or '-t' options, so that 63 // docker can always grab the output through logs. We also tell HCS to always 64 // create stdin, even if it's not used - it will be closed shortly. Stderr 65 // is only created if it we're not -t. 66 createProcessParms := &hcsshim.ProcessConfig{ 67 EmulateConsole: ctr.ociSpec.Process.Terminal, 68 WorkingDirectory: ctr.ociSpec.Process.Cwd, 69 ConsoleSize: ctr.ociSpec.Process.InitialConsoleSize, 70 CreateStdInPipe: !isServicing, 71 CreateStdOutPipe: !isServicing, 72 CreateStdErrPipe: !ctr.ociSpec.Process.Terminal && !isServicing, 73 } 74 75 // Configure the environment for the process 76 createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env) 77 createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ") 78 79 // Start the command running in the container. 80 hcsProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms) 81 if err != nil { 82 logrus.Errorf("CreateProcess() failed %s", err) 83 if err := ctr.terminate(); err != nil { 84 logrus.Errorf("Failed to cleanup after a failed CreateProcess. %s", err) 85 } else { 86 logrus.Debugln("Cleaned up after failed CreateProcess by calling Terminate") 87 } 88 return err 89 } 90 ctr.startedAt = time.Now() 91 92 // Save the hcs Process and PID 93 ctr.process.friendlyName = InitFriendlyName 94 pid := hcsProcess.Pid() 95 ctr.process.hcsProcess = hcsProcess 96 97 // If this is a servicing container, wait on the process synchronously here and 98 // immediately call shutdown/terminate when it returns. 99 if isServicing { 100 exitCode := ctr.waitProcessExitCode(&ctr.process) 101 102 if exitCode != 0 { 103 logrus.Warnf("Servicing container %s returned non-zero exit code %d", ctr.containerID, exitCode) 104 return ctr.terminate() 105 } 106 107 return ctr.shutdown() 108 } 109 110 var stdout, stderr io.ReadCloser 111 var stdin io.WriteCloser 112 stdin, stdout, stderr, err = hcsProcess.Stdio() 113 if err != nil { 114 logrus.Errorf("failed to get stdio pipes: %s", err) 115 if err := ctr.terminate(); err != nil { 116 logrus.Errorf("Failed to cleanup after a failed Stdio. %s", err) 117 } 118 return err 119 } 120 121 iopipe := &IOPipe{Terminal: ctr.ociSpec.Process.Terminal} 122 123 iopipe.Stdin = createStdInCloser(stdin, hcsProcess) 124 125 // TEMP: Work around Windows BS/DEL behavior. 126 iopipe.Stdin = fixStdinBackspaceBehavior(iopipe.Stdin, ctr.ociSpec.Platform.OSVersion, ctr.ociSpec.Process.Terminal) 127 128 // Convert io.ReadClosers to io.Readers 129 if stdout != nil { 130 iopipe.Stdout = openReaderFromPipe(stdout) 131 } 132 if stderr != nil { 133 iopipe.Stderr = openReaderFromPipe(stderr) 134 } 135 136 // Save the PID 137 logrus.Debugf("Process started - PID %d", pid) 138 ctr.systemPid = uint32(pid) 139 140 // Spin up a go routine waiting for exit to handle cleanup 141 go ctr.waitExit(&ctr.process, true) 142 143 ctr.client.appendContainer(ctr) 144 145 if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil { 146 // OK to return the error here, as waitExit will handle tear-down in HCS 147 return err 148 } 149 150 // Tell the docker engine that the container has started. 151 si := StateInfo{ 152 CommonStateInfo: CommonStateInfo{ 153 State: StateStart, 154 Pid: ctr.systemPid, // Not sure this is needed? Double-check monitor.go in daemon BUGBUG @jhowardmsft 155 }} 156 return ctr.client.backend.StateChanged(ctr.containerID, si) 157 158 } 159 160 // waitProcessExitCode will wait for the given process to exit and return its error code. 161 func (ctr *container) waitProcessExitCode(process *process) int { 162 // Block indefinitely for the process to exit. 163 err := process.hcsProcess.Wait() 164 if err != nil { 165 if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { 166 logrus.Warnf("Wait failed (container may have been killed): %s", err) 167 } 168 // Fall through here, do not return. This ensures we attempt to continue the 169 // shutdown in HCS and tell the docker engine that the process/container 170 // has exited to avoid a container being dropped on the floor. 171 } 172 173 exitCode, err := process.hcsProcess.ExitCode() 174 if err != nil { 175 if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { 176 logrus.Warnf("Unable to get exit code from container %s", ctr.containerID) 177 } 178 // Since we got an error retrieving the exit code, make sure that the code we return 179 // doesn't incorrectly indicate success. 180 exitCode = -1 181 182 // Fall through here, do not return. This ensures we attempt to continue the 183 // shutdown in HCS and tell the docker engine that the process/container 184 // has exited to avoid a container being dropped on the floor. 185 } 186 187 if err := process.hcsProcess.Close(); err != nil { 188 logrus.Error(err) 189 } 190 191 return exitCode 192 } 193 194 // waitExit runs as a goroutine waiting for the process to exit. It's 195 // equivalent to (in the linux containerd world) where events come in for 196 // state change notifications from containerd. 197 func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) error { 198 logrus.Debugln("waitExit on pid", process.systemPid) 199 200 exitCode := ctr.waitProcessExitCode(process) 201 202 // Assume the container has exited 203 si := StateInfo{ 204 CommonStateInfo: CommonStateInfo{ 205 State: StateExit, 206 ExitCode: uint32(exitCode), 207 Pid: process.systemPid, 208 ProcessID: process.friendlyName, 209 }, 210 UpdatePending: false, 211 } 212 213 // But it could have been an exec'd process which exited 214 if !isFirstProcessToStart { 215 si.State = StateExitProcess 216 } else { 217 updatePending, err := ctr.hcsContainer.HasPendingUpdates() 218 if err != nil { 219 logrus.Warnf("HasPendingUpdates failed (container may have been killed): %s", err) 220 } else { 221 si.UpdatePending = updatePending 222 } 223 224 logrus.Debugf("Shutting down container %s", ctr.containerID) 225 if err := ctr.shutdown(); err != nil { 226 logrus.Debugf("Failed to shutdown container %s", ctr.containerID) 227 } else { 228 logrus.Debugf("Completed shutting down container %s", ctr.containerID) 229 } 230 if err := ctr.hcsContainer.Close(); err != nil { 231 logrus.Error(err) 232 } 233 234 if !ctr.manualStopRequested && ctr.restartManager != nil { 235 restart, wait, err := ctr.restartManager.ShouldRestart(uint32(exitCode), false, time.Since(ctr.startedAt)) 236 if err != nil { 237 logrus.Error(err) 238 } else if restart { 239 si.State = StateRestart 240 ctr.restarting = true 241 go func() { 242 err := <-wait 243 ctr.restarting = false 244 ctr.client.deleteContainer(ctr.friendlyName) 245 if err != nil { 246 si.State = StateExit 247 if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil { 248 logrus.Error(err) 249 } 250 logrus.Error(err) 251 } else { 252 ctr.client.Create(ctr.containerID, ctr.ociSpec, ctr.options...) 253 } 254 }() 255 } 256 } 257 258 // Remove process from list if we have exited 259 // We need to do so here in case the Message Handler decides to restart it. 260 if si.State == StateExit { 261 ctr.client.deleteContainer(ctr.friendlyName) 262 } 263 } 264 265 // Call into the backend to notify it of the state change. 266 logrus.Debugf("waitExit() calling backend.StateChanged %+v", si) 267 if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil { 268 logrus.Error(err) 269 } 270 271 logrus.Debugf("waitExit() completed OK, %+v", si) 272 return nil 273 } 274 275 func (ctr *container) shutdown() error { 276 const shutdownTimeout = time.Minute * 5 277 err := ctr.hcsContainer.Shutdown() 278 if err == hcsshim.ErrVmcomputeOperationPending { 279 // Explicit timeout to avoid a (remote) possibility that shutdown hangs indefinitely. 280 err = ctr.hcsContainer.WaitTimeout(shutdownTimeout) 281 } else if err == hcsshim.ErrVmcomputeAlreadyStopped { 282 err = nil 283 } 284 285 if err != nil { 286 logrus.Debugf("error shutting down container %s %v calling terminate", ctr.containerID, err) 287 if err := ctr.terminate(); err != nil { 288 return err 289 } 290 return err 291 } 292 293 return nil 294 } 295 296 func (ctr *container) terminate() error { 297 const terminateTimeout = time.Minute * 5 298 err := ctr.hcsContainer.Terminate() 299 300 if err == hcsshim.ErrVmcomputeOperationPending { 301 err = ctr.hcsContainer.WaitTimeout(terminateTimeout) 302 } else if err == hcsshim.ErrVmcomputeAlreadyStopped { 303 err = nil 304 } 305 306 if err != nil { 307 logrus.Debugf("error terminating container %s %v", ctr.containerID, err) 308 return err 309 } 310 311 return nil 312 }