container_windows.go 7.0 KB

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