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 }