in agent/engine/task_manager.go [834:925]
func (mtask *managedTask) handleEventError(containerChange dockerContainerChange, currentKnownStatus apicontainerstatus.ContainerStatus) bool {
container := containerChange.container
event := containerChange.event
if container.ApplyingError == nil {
container.ApplyingError = apierrors.NewNamedError(event.Error)
}
switch event.Status {
// event.Status is the desired container transition from container's known status
// (* -> event.Status)
case apicontainerstatus.ContainerPulled:
// If the agent pull behavior is always or once, we receive the error because
// the image pull fails, the task should fail. If we don't fail task here,
// then the cached image will probably be used for creating container, and we
// don't want to use cached image for both cases.
if mtask.cfg.ImagePullBehavior == config.ImagePullAlwaysBehavior ||
mtask.cfg.ImagePullBehavior == config.ImagePullOnceBehavior {
logger.Error("Error while pulling image; moving task to STOPPED", logger.Fields{
field.TaskARN: mtask.Arn,
field.Image: container.Image,
field.Container: container.Name,
field.Error: event.Error,
})
// The task should be stopped regardless of whether this container is
// essential or non-essential.
mtask.SetDesiredStatus(apitaskstatus.TaskStopped)
return false
}
// If the agent pull behavior is prefer-cached, we receive the error because
// the image pull fails and there is no cached image in local, we don't make
// the task fail here, will let create container handle it instead.
// If the agent pull behavior is default, use local image cache directly,
// assuming it exists.
logger.Error("Error while pulling image; will try to run anyway", logger.Fields{
field.TaskARN: mtask.Arn,
field.Image: container.Image,
field.Container: container.Name,
field.Error: event.Error,
})
// proceed anyway
return true
case apicontainerstatus.ContainerStopped:
// Container's desired transition was to 'STOPPED'
return mtask.handleContainerStoppedTransitionError(event, container, currentKnownStatus)
case apicontainerstatus.ContainerStatusNone:
fallthrough
case apicontainerstatus.ContainerCreated:
// No need to explicitly stop containers if this is a * -> NONE/CREATED transition
logger.Warn("Error creating container; marking its desired status as STOPPED", logger.Fields{
field.TaskARN: mtask.Arn,
field.Container: container.Name,
field.Error: event.Error,
})
container.SetKnownStatus(currentKnownStatus)
container.SetDesiredStatus(apicontainerstatus.ContainerStopped)
return false
default:
// If this is a * -> RUNNING / RESOURCES_PROVISIONED transition, we need to stop
// the container.
logger.Warn("Error starting/provisioning container[%s (Runtime ID: %s)];", logger.Fields{
field.TaskARN: mtask.Arn,
field.Container: container.Name,
field.RuntimeID: container.GetRuntimeID(),
field.Error: event.Error,
})
container.SetKnownStatus(currentKnownStatus)
container.SetDesiredStatus(apicontainerstatus.ContainerStopped)
errorName := event.Error.ErrorName()
errorStr := event.Error.Error()
shouldForceStop := false
if errorName == dockerapi.DockerTimeoutErrorName || errorName == dockerapi.CannotInspectContainerErrorName {
// If there's an error with inspecting the container or in case of timeout error,
// we'll assume that the container has transitioned to RUNNING and issue
// a stop. See #1043 for details
shouldForceStop = true
} else if errorName == dockerapi.CannotStartContainerErrorName && strings.HasSuffix(errorStr, io.EOF.Error()) {
// If we get an EOF error from Docker when starting the container, we don't really know whether the
// container is started anyway. So issuing a stop here as well. See #1708.
shouldForceStop = true
}
if shouldForceStop {
logger.Warn("Forcing container to stop", logger.Fields{
field.TaskARN: mtask.Arn,
field.Container: container.Name,
field.RuntimeID: container.GetRuntimeID(),
})
go mtask.engine.transitionContainer(mtask.Task, container, apicontainerstatus.ContainerStopped)
}
// Container known status not changed, no need for further processing
return false
}
}