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