Merge pull request #21399 from LK4D4/fix_builder_race

builder: replace cancelled channel with net/context
This commit is contained in:
Brian Goff 2016-03-25 13:09:23 -04:00
commit 7307998a44
4 changed files with 42 additions and 45 deletions

View file

@ -16,5 +16,5 @@ type Backend interface {
// by the caller.
//
// TODO: make this return a reference instead of string
Build(clientCtx context.Context, config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer, clientGone <-chan bool) (string, error)
Build(clientCtx context.Context, config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer) (string, error)
}

View file

@ -161,17 +161,12 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
}
var (
context builder.ModifiableContext
dockerfileName string
out io.Writer
)
context, dockerfileName, err = builder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
buildContext, dockerfileName, err := builder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
if err != nil {
return errf(err)
}
defer func() {
if err := context.Close(); err != nil {
if err := buildContext.Close(); err != nil {
logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
}
}()
@ -181,7 +176,7 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
buildOptions.AuthConfigs = authConfigs
out = output
var out io.Writer = output
if buildOptions.SuppressOutput {
out = notVerboseBuffer
}
@ -189,15 +184,24 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
stdout := &streamformatter.StdoutFormatter{Writer: out, StreamFormatter: sf}
stderr := &streamformatter.StderrFormatter{Writer: out, StreamFormatter: sf}
closeNotifier := make(<-chan bool)
finished := make(chan struct{})
defer close(finished)
if notifier, ok := w.(http.CloseNotifier); ok {
closeNotifier = notifier.CloseNotify()
notifyContext, cancel := context.WithCancel(ctx)
closeNotifier := notifier.CloseNotify()
go func() {
select {
case <-closeNotifier:
cancel()
case <-finished:
}
}()
ctx = notifyContext
}
imgID, err := br.backend.Build(ctx, buildOptions,
builder.DockerIgnoreContext{ModifiableContext: context},
stdout, stderr, out,
closeNotifier)
builder.DockerIgnoreContext{ModifiableContext: buildContext},
stdout, stderr, out)
if err != nil {
return errf(err)
}

View file

@ -8,7 +8,6 @@ import (
"io/ioutil"
"os"
"strings"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/builder"
@ -56,6 +55,7 @@ type Builder struct {
docker builder.Backend
context builder.Context
clientCtx context.Context
cancel context.CancelFunc
dockerfile *parser.Node
runConfig *container.Config // runconfig for cmd, run, entrypoint etc.
@ -67,8 +67,6 @@ type Builder struct {
cmdSet bool
disableCommit bool
cacheBusted bool
cancelled chan struct{}
cancelOnce sync.Once
allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
// TODO: remove once docker.Commit can receive a tag
@ -88,23 +86,24 @@ func NewBuildManager(b builder.Backend) (bm *BuildManager) {
// NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
// If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
// will be read from the Context passed to Build().
func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, backend builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, backend builder.Backend, buildContext builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
if config == nil {
config = new(types.ImageBuildOptions)
}
if config.BuildArgs == nil {
config.BuildArgs = make(map[string]string)
}
ctx, cancel := context.WithCancel(clientCtx)
b = &Builder{
clientCtx: clientCtx,
clientCtx: ctx,
cancel: cancel,
options: config,
Stdout: os.Stdout,
Stderr: os.Stderr,
docker: backend,
context: context,
context: buildContext,
runConfig: new(container.Config),
tmpContainers: map[string]struct{}{},
cancelled: make(chan struct{}),
id: stringid.GenerateNonCryptoID(),
allowedBuildArgs: make(map[string]bool),
}
@ -161,12 +160,12 @@ func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
}
// Build creates a NewBuilder, which builds the image.
func (bm *BuildManager) Build(clientCtx context.Context, config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer, clientGone <-chan bool) (string, error) {
func (bm *BuildManager) Build(clientCtx context.Context, config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer) (string, error) {
b, err := NewBuilder(clientCtx, config, bm.backend, context, nil)
if err != nil {
return "", err
}
img, err := b.build(config, context, stdout, stderr, out, clientGone)
img, err := b.build(config, context, stdout, stderr, out)
return img, err
}
@ -184,7 +183,7 @@ func (bm *BuildManager) Build(clientCtx context.Context, config *types.ImageBuil
// * Tag image, if applicable.
// * Print a happy message and return the image ID.
//
func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer, clientGone <-chan bool) (string, error) {
func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer) (string, error) {
b.options = config
b.context = context
b.Stdout = stdout
@ -198,19 +197,6 @@ func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context
}
}
finished := make(chan struct{})
defer close(finished)
go func() {
select {
case <-finished:
case <-clientGone:
b.cancelOnce.Do(func() {
close(b.cancelled)
})
}
}()
repoAndTags, err := sanitizeRepoAndTags(config.Tags)
if err != nil {
return "", err
@ -223,7 +209,7 @@ func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context
b.addLabels()
}
select {
case <-b.cancelled:
case <-b.clientCtx.Done():
logrus.Debug("Builder: build cancelled!")
fmt.Fprintf(b.Stdout, "Build cancelled")
return "", fmt.Errorf("Build cancelled")
@ -271,9 +257,7 @@ func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context
// Cancel cancels an ongoing Dockerfile build.
func (b *Builder) Cancel() {
b.cancelOnce.Do(func() {
close(b.cancelled)
})
b.cancel()
}
// BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile

View file

@ -6,6 +6,7 @@ package dockerfile
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
@ -16,6 +17,7 @@ import (
"runtime"
"sort"
"strings"
"sync"
"time"
"github.com/Sirupsen/logrus"
@ -553,6 +555,8 @@ func (b *Builder) create() (string, error) {
return c.ID, nil
}
var errCancelled = errors.New("build cancelled")
func (b *Builder) run(cID string) (err error) {
errCh := make(chan error)
go func() {
@ -560,14 +564,19 @@ func (b *Builder) run(cID string) (err error) {
}()
finished := make(chan struct{})
defer close(finished)
var once sync.Once
finish := func() { close(finished) }
cancelErrCh := make(chan error, 1)
defer once.Do(finish)
go func() {
select {
case <-b.cancelled:
case <-b.clientCtx.Done():
logrus.Debugln("Build cancelled, killing and removing container:", cID)
b.docker.ContainerKill(cID, 0)
b.removeContainer(cID)
cancelErrCh <- errCancelled
case <-finished:
cancelErrCh <- nil
}
}()
@ -587,8 +596,8 @@ func (b *Builder) run(cID string) (err error) {
Code: ret,
}
}
return nil
once.Do(finish)
return <-cancelErrCh
}
func (b *Builder) removeContainer(c string) error {