summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Howard <jhoward@microsoft.com>2019-02-12 12:19:50 -0800
committerJohn Howard <jhoward@microsoft.com>2019-03-07 10:28:11 -0800
commit34f36106d9eec204e70069f03275b0084b9c4243 (patch)
tree5c241cff97c5e3e29305dbfb6d17bf87265ae4a2
parentf8dd42606ecc33a697cee21ab636a7297df224b7 (diff)
downloaddocker-34f36106d9eec204e70069f03275b0084b9c4243.tar.gz
Windows: Fix handle leaks/logging if init proc start fails
Signed-off-by: John Howard <jhoward@microsoft.com> Fixes #38719 Fixes some subtle bugs on Windows - Fixes https://github.com/moby/moby/issues/38719. This one is the most important as failure to start the init process in a Windows container will cause leaked handles. (ie where the `ctr.hcsContainer.CreateProcess(...)` call fails). The solution to the leak is to split out the `reapContainer` part of `reapProcess` into a separate function. This ensures HCS resources are cleaned up correctly and not leaked. - Ensuring the reapProcess goroutine is started immediately the process is actually started, so we don't leak in the case of failures such as from `newIOFromProcess` or `attachStdio` - libcontainerd on Windows (local, not containerd) was not sending the EventCreate back to the monitor on Windows. Just LCOW. This was just an oversight from refactoring a couple of years ago by Mikael as far as I can tell. Technically not needed for functionality except for the logging being missing, but is correct.
-rw-r--r--daemon/errors.go3
-rw-r--r--libcontainerd/local/local_windows.go158
2 files changed, 94 insertions, 67 deletions
diff --git a/daemon/errors.go b/daemon/errors.go
index ed60ce7698..fcf8d8df11 100644
--- a/daemon/errors.go
+++ b/daemon/errors.go
@@ -133,7 +133,8 @@ func translateContainerdStartErr(cmd string, setExitCode func(int), err error) e
if contains(errDesc, cmd) &&
(contains(errDesc, "executable file not found") ||
contains(errDesc, "no such file or directory") ||
- contains(errDesc, "system cannot find the file specified")) {
+ contains(errDesc, "system cannot find the file specified") ||
+ contains(errDesc, "failed to run runc create/exec call")) {
setExitCode(127)
retErr = startInvalidConfigError(errDesc)
}
diff --git a/libcontainerd/local/local_windows.go b/libcontainerd/local/local_windows.go
index 7875337dd1..c92f203f91 100644
--- a/libcontainerd/local/local_windows.go
+++ b/libcontainerd/local/local_windows.go
@@ -152,21 +152,37 @@ func (c *client) Version(ctx context.Context) (containerd.Version, error) {
// },
//}
func (c *client) Create(_ context.Context, id string, spec *specs.Spec, runtimeOptions interface{}) error {
- if ctr := c.getContainer(id); ctr != nil {
+ ctr := c.getContainer(id)
+ if ctr != nil {
return errors.WithStack(libcontainerdtypes.NewConflictError("id already in use"))
}
- // spec.Linux must be nil for Windows containers, but spec.Windows
- // will be filled in regardless of container platform. This is a
- // temporary workaround due to LCOW requiring layer folder paths,
- // which are stored under spec.Windows.
- //
- // TODO: @darrenstahlmsft fix this once the OCI spec is updated to
- // support layer folder paths for LCOW
+ var err error
if spec.Linux == nil {
- return c.createWindows(id, spec, runtimeOptions)
+ err = c.createWindows(id, spec, runtimeOptions)
+ } else {
+ err = c.createLinux(id, spec, runtimeOptions)
+ }
+
+ if err == nil {
+ c.eventQ.Append(id, func() {
+ ei := libcontainerdtypes.EventInfo{
+ ContainerID: id,
+ }
+ c.logger.WithFields(logrus.Fields{
+ "container": id,
+ "event": libcontainerdtypes.EventCreate,
+ }).Info("sending event")
+ err := c.backend.ProcessEvent(id, libcontainerdtypes.EventCreate, ei)
+ if err != nil {
+ c.logger.WithError(err).WithFields(logrus.Fields{
+ "container": id,
+ "event": libcontainerdtypes.EventCreate,
+ }).Error("failed to process event")
+ }
+ })
}
- return c.createLinux(id, spec, runtimeOptions)
+ return err
}
func (c *client) createWindows(id string, spec *specs.Spec, runtimeOptions interface{}) error {
@@ -560,23 +576,6 @@ func (c *client) createLinux(id string, spec *specs.Spec, runtimeOptions interfa
c.containers[id] = ctr
c.Unlock()
- c.eventQ.Append(id, func() {
- ei := libcontainerdtypes.EventInfo{
- ContainerID: id,
- }
- c.logger.WithFields(logrus.Fields{
- "container": ctr.id,
- "event": libcontainerdtypes.EventCreate,
- }).Info("sending event")
- err := c.backend.ProcessEvent(id, libcontainerdtypes.EventCreate, ei)
- if err != nil {
- c.logger.WithError(err).WithFields(logrus.Fields{
- "container": id,
- "event": libcontainerdtypes.EventCreate,
- }).Error("failed to process event")
- }
- })
-
logger.Debug("createLinux() completed successfully")
return nil
}
@@ -654,7 +653,9 @@ func (c *client) Start(_ context.Context, id, _ string, withStdin bool, attachSt
// Configure the CommandLine/CommandArgs
setCommandLineAndArgs(ctr.isWindows, ctr.ociSpec.Process, createProcessParms)
- logger.Debugf("start commandLine: %s", createProcessParms.CommandLine)
+ if ctr.isWindows {
+ logger.Debugf("start commandLine: %s", createProcessParms.CommandLine)
+ }
createProcessParms.User = ctr.ociSpec.Process.User.Username
@@ -670,14 +671,31 @@ func (c *client) Start(_ context.Context, id, _ string, withStdin bool, attachSt
}
ctr.Lock()
- defer ctr.Unlock()
// Start the command running in the container.
newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
if err != nil {
logger.WithError(err).Error("CreateProcess() failed")
+ // Fix for https://github.com/moby/moby/issues/38719.
+ // If the init process failed to launch, we still need to reap the
+ // container to avoid leaking it.
+ //
+ // Note we use the explicit exit code of 127 which is the
+ // Linux shell equivalent of "command not found". Windows cannot
+ // know ahead of time whether or not the command exists, especially
+ // in the case of Hyper-V containers.
+ ctr.Unlock()
+ exitedAt := time.Now()
+ p := &process{
+ id: libcontainerdtypes.InitProcessName,
+ pid: 0,
+ }
+ c.reapContainer(ctr, p, 127, exitedAt, nil, logger)
return -1, err
}
+
+ defer ctr.Unlock()
+
defer func() {
if err != nil {
if err := newProcess.Kill(); err != nil {
@@ -700,6 +718,12 @@ func (c *client) Start(_ context.Context, id, _ string, withStdin bool, attachSt
}
logger.WithField("pid", p.pid).Debug("init process started")
+ ctr.status = libcontainerdtypes.StatusRunning
+ ctr.init = p
+
+ // Spin up a go routine waiting for exit to handle cleanup
+ go c.reapProcess(ctr, p)
+
dio, err := newIOFromProcess(newProcess, ctr.ociSpec.Process.Terminal)
if err != nil {
logger.WithError(err).Error("failed to get stdio pipes")
@@ -707,14 +731,9 @@ func (c *client) Start(_ context.Context, id, _ string, withStdin bool, attachSt
}
_, err = attachStdio(dio)
if err != nil {
- logger.WithError(err).Error("failed to attache stdio")
+ logger.WithError(err).Error("failed to attach stdio")
return -1, err
}
- ctr.status = libcontainerdtypes.StatusRunning
- ctr.init = p
-
- // Spin up a go routine waiting for exit to handle cleanup
- go c.reapProcess(ctr, p)
// Generate the associated event
c.eventQ.Append(id, func() {
@@ -1325,37 +1344,7 @@ func (c *client) reapProcess(ctr *container, p *process) int {
}
if p.id == libcontainerdtypes.InitProcessName {
- // Update container status
- ctr.Lock()
- ctr.status = libcontainerdtypes.StatusStopped
- ctr.exitedAt = exitedAt
- ctr.exitCode = uint32(exitCode)
- close(ctr.waitCh)
-
- if err := c.shutdownContainer(ctr); err != nil {
- exitCode = -1
- logger.WithError(err).Warn("failed to shutdown container")
- thisErr := fmt.Errorf("failed to shutdown container: %s", err)
- if eventErr != nil {
- eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
- } else {
- eventErr = thisErr
- }
- } else {
- logger.Debug("completed container shutdown")
- }
- ctr.Unlock()
-
- if err := ctr.hcsContainer.Close(); err != nil {
- exitCode = -1
- logger.WithError(err).Error("failed to clean hcs container resources")
- thisErr := fmt.Errorf("failed to terminate container: %s", err)
- if eventErr != nil {
- eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
- } else {
- eventErr = thisErr
- }
- }
+ exitCode, eventErr = c.reapContainer(ctr, p, exitCode, exitedAt, eventErr, logger)
}
c.eventQ.Append(ctr.id, func() {
@@ -1389,3 +1378,40 @@ func (c *client) reapProcess(ctr *container, p *process) int {
return exitCode
}
+
+// reapContainer shuts down the container and releases associated resources. It returns
+// the error to be logged in the eventInfo sent back to the monitor.
+func (c *client) reapContainer(ctr *container, p *process, exitCode int, exitedAt time.Time, eventErr error, logger *logrus.Entry) (int, error) {
+ // Update container status
+ ctr.Lock()
+ ctr.status = libcontainerdtypes.StatusStopped
+ ctr.exitedAt = exitedAt
+ ctr.exitCode = uint32(exitCode)
+ close(ctr.waitCh)
+
+ if err := c.shutdownContainer(ctr); err != nil {
+ exitCode = -1
+ logger.WithError(err).Warn("failed to shutdown container")
+ thisErr := fmt.Errorf("failed to shutdown container: %s", err)
+ if eventErr != nil {
+ eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
+ } else {
+ eventErr = thisErr
+ }
+ } else {
+ logger.Debug("completed container shutdown")
+ }
+ ctr.Unlock()
+
+ if err := ctr.hcsContainer.Close(); err != nil {
+ exitCode = -1
+ logger.WithError(err).Error("failed to clean hcs container resources")
+ thisErr := fmt.Errorf("failed to terminate container: %s", err)
+ if eventErr != nil {
+ eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
+ } else {
+ eventErr = thisErr
+ }
+ }
+ return exitCode, eventErr
+}