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. // by the caller.
// //
// TODO: make this return a reference instead of string // 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) return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
} }
var ( buildContext, dockerfileName, err := builder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
context builder.ModifiableContext
dockerfileName string
out io.Writer
)
context, dockerfileName, err = builder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
if err != nil { if err != nil {
return errf(err) return errf(err)
} }
defer func() { defer func() {
if err := context.Close(); err != nil { if err := buildContext.Close(); err != nil {
logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err) 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 buildOptions.AuthConfigs = authConfigs
out = output var out io.Writer = output
if buildOptions.SuppressOutput { if buildOptions.SuppressOutput {
out = notVerboseBuffer out = notVerboseBuffer
} }
@ -189,15 +184,24 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
stdout := &streamformatter.StdoutFormatter{Writer: out, StreamFormatter: sf} stdout := &streamformatter.StdoutFormatter{Writer: out, StreamFormatter: sf}
stderr := &streamformatter.StderrFormatter{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 { 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, imgID, err := br.backend.Build(ctx, buildOptions,
builder.DockerIgnoreContext{ModifiableContext: context}, builder.DockerIgnoreContext{ModifiableContext: buildContext},
stdout, stderr, out, stdout, stderr, out)
closeNotifier)
if err != nil { if err != nil {
return errf(err) return errf(err)
} }

View file

@ -8,7 +8,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
"sync"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/builder" "github.com/docker/docker/builder"
@ -56,6 +55,7 @@ type Builder struct {
docker builder.Backend docker builder.Backend
context builder.Context context builder.Context
clientCtx context.Context clientCtx context.Context
cancel context.CancelFunc
dockerfile *parser.Node dockerfile *parser.Node
runConfig *container.Config // runconfig for cmd, run, entrypoint etc. runConfig *container.Config // runconfig for cmd, run, entrypoint etc.
@ -67,8 +67,6 @@ type Builder struct {
cmdSet bool cmdSet bool
disableCommit bool disableCommit bool
cacheBusted 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'. 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 // 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. // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
// If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
// will be read from the Context passed to Build(). // 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 { if config == nil {
config = new(types.ImageBuildOptions) config = new(types.ImageBuildOptions)
} }
if config.BuildArgs == nil { if config.BuildArgs == nil {
config.BuildArgs = make(map[string]string) config.BuildArgs = make(map[string]string)
} }
ctx, cancel := context.WithCancel(clientCtx)
b = &Builder{ b = &Builder{
clientCtx: clientCtx, clientCtx: ctx,
cancel: cancel,
options: config, options: config,
Stdout: os.Stdout, Stdout: os.Stdout,
Stderr: os.Stderr, Stderr: os.Stderr,
docker: backend, docker: backend,
context: context, context: buildContext,
runConfig: new(container.Config), runConfig: new(container.Config),
tmpContainers: map[string]struct{}{}, tmpContainers: map[string]struct{}{},
cancelled: make(chan struct{}),
id: stringid.GenerateNonCryptoID(), id: stringid.GenerateNonCryptoID(),
allowedBuildArgs: make(map[string]bool), allowedBuildArgs: make(map[string]bool),
} }
@ -161,12 +160,12 @@ func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
} }
// Build creates a NewBuilder, which builds the image. // 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) b, err := NewBuilder(clientCtx, config, bm.backend, context, nil)
if err != nil { if err != nil {
return "", err return "", err
} }
img, err := b.build(config, context, stdout, stderr, out, clientGone) img, err := b.build(config, context, stdout, stderr, out)
return img, err return img, err
} }
@ -184,7 +183,7 @@ func (bm *BuildManager) Build(clientCtx context.Context, config *types.ImageBuil
// * Tag image, if applicable. // * Tag image, if applicable.
// * Print a happy message and return the image ID. // * 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.options = config
b.context = context b.context = context
b.Stdout = stdout 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) repoAndTags, err := sanitizeRepoAndTags(config.Tags)
if err != nil { if err != nil {
return "", err return "", err
@ -223,7 +209,7 @@ func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context
b.addLabels() b.addLabels()
} }
select { select {
case <-b.cancelled: case <-b.clientCtx.Done():
logrus.Debug("Builder: build cancelled!") logrus.Debug("Builder: build cancelled!")
fmt.Fprintf(b.Stdout, "Build cancelled") fmt.Fprintf(b.Stdout, "Build cancelled")
return "", fmt.Errorf("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. // Cancel cancels an ongoing Dockerfile build.
func (b *Builder) Cancel() { func (b *Builder) Cancel() {
b.cancelOnce.Do(func() { b.cancel()
close(b.cancelled)
})
} }
// BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile // 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 ( import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -16,6 +17,7 @@ import (
"runtime" "runtime"
"sort" "sort"
"strings" "strings"
"sync"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
@ -553,6 +555,8 @@ func (b *Builder) create() (string, error) {
return c.ID, nil return c.ID, nil
} }
var errCancelled = errors.New("build cancelled")
func (b *Builder) run(cID string) (err error) { func (b *Builder) run(cID string) (err error) {
errCh := make(chan error) errCh := make(chan error)
go func() { go func() {
@ -560,14 +564,19 @@ func (b *Builder) run(cID string) (err error) {
}() }()
finished := make(chan struct{}) 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() { go func() {
select { select {
case <-b.cancelled: case <-b.clientCtx.Done():
logrus.Debugln("Build cancelled, killing and removing container:", cID) logrus.Debugln("Build cancelled, killing and removing container:", cID)
b.docker.ContainerKill(cID, 0) b.docker.ContainerKill(cID, 0)
b.removeContainer(cID) b.removeContainer(cID)
cancelErrCh <- errCancelled
case <-finished: case <-finished:
cancelErrCh <- nil
} }
}() }()
@ -587,8 +596,8 @@ func (b *Builder) run(cID string) (err error) {
Code: ret, Code: ret,
} }
} }
once.Do(finish)
return nil return <-cancelErrCh
} }
func (b *Builder) removeContainer(c string) error { func (b *Builder) removeContainer(c string) error {