Skip to content

Commit

Permalink
Bugfix: client hangs after run container
Browse files Browse the repository at this point in the history
New codes introduced a new method to wait container's exit code and
removal via Events API, but it specified a "since" filter based on
client side clock, which sometimes isn't synced up with daemon's clock,
hence leads the client to hang on waiting container's exit status.

This commit brings the Events call before start, and removes the time
filter, so that it can catch daemon events correctly without care of the
clock issue.

Signed-off-by: Zhang Wei <[email protected]>
  • Loading branch information
WeiZhang555 committed Aug 10, 2016
1 parent b2b41b2 commit a422813
Showing 2 changed files with 48 additions and 38 deletions.
21 changes: 8 additions & 13 deletions api/client/container/run.go
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ import (
"runtime"
"strings"
"syscall"
"time"

"golang.org/x/net/context"

@@ -144,7 +143,6 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
}

startTime := time.Now()
ctx, cancelFun := context.WithCancel(context.Background())

createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
@@ -221,6 +219,11 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
})
}

statusChan, err := waitExitOrRemoved(dockerCli, context.Background(), createResponse.ID, hostConfig.AutoRemove)
if err != nil {
return fmt.Errorf("Error waiting container's exit code: %v", err)
}

//start the container
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
// If we have holdHijackedConnection, we should notify
@@ -233,9 +236,8 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,

reportError(stderr, cmdPath, err.Error(), false)
if hostConfig.AutoRemove {
if _, errWait := waitExitOrRemoved(dockerCli, context.Background(), createResponse.ID, hostConfig.AutoRemove, startTime); errWait != nil {
logrus.Debugf("Error waiting container's removal: %v", errWait)
}
// wait container to be removed
<-statusChan
}
return runStartContainerErr(err)
}
@@ -260,14 +262,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
return nil
}

var status int

// Attached mode
status, err = waitExitOrRemoved(dockerCli, ctx, createResponse.ID, hostConfig.AutoRemove, startTime)
if err != nil {
return fmt.Errorf("Error waiting container to exit: %v", err)
}

status := <-statusChan
if status != 0 {
return cli.StatusError{StatusCode: status}
}
65 changes: 40 additions & 25 deletions api/client/container/utils.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ package container
import (
"fmt"
"strconv"
"time"

"golang.org/x/net/context"

@@ -16,16 +15,18 @@ import (
"github.com/docker/engine-api/types/filters"
)

func waitExitOrRemoved(dockerCli *client.DockerCli, ctx context.Context, containerID string, waitRemove bool, since time.Time) (int, error) {
func waitExitOrRemoved(dockerCli *client.DockerCli, ctx context.Context, containerID string, waitRemove bool) (chan int, error) {
if len(containerID) == 0 {
// containerID can never be empty
panic("Internal Error: waitExitOrRemoved needs a containerID as parameter")
}

var exitCode int
statusChan := make(chan int)
exitChan := make(chan struct{})
detachChan := make(chan struct{})
destroyChan := make(chan struct{})
eventsErr := make(chan error)

// Start watch events
eh := system.InitEventHandler()
@@ -54,45 +55,59 @@ func waitExitOrRemoved(dockerCli *client.DockerCli, ctx context.Context, contain

eventChan := make(chan events.Message)
go eh.Watch(eventChan)
defer close(eventChan)

// Get events via Events API
f := filters.NewArgs()
f.Add("type", "container")
f.Add("container", containerID)
options := types.EventsOptions{
Since: fmt.Sprintf("%d", since.Unix()),
Filters: f,
}
resBody, err := dockerCli.Client().Events(ctx, options)
if err != nil {
return -1, fmt.Errorf("can't get events from daemon: %v", err)
return nil, fmt.Errorf("can't get events from daemon: %v", err)
}
defer resBody.Close()

go system.DecodeEvents(resBody, func(event events.Message, err error) error {
if err != nil {
go func() {
eventsErr <- system.DecodeEvents(resBody, func(event events.Message, err error) error {
if err != nil {
return fmt.Errorf("decode events error: %v", err)
}
eventChan <- event
return nil
}
eventChan <- event
return nil
})
})
close(eventChan)
}()

if waitRemove {
select {
case <-destroyChan:
return exitCode, nil
case <-detachChan:
return 0, nil
go func() {
var waitErr error
if waitRemove {
select {
case <-destroyChan:
// keep exitcode and return
case <-detachChan:
exitCode = 0
case waitErr = <-eventsErr:
exitCode = 125
}
} else {
select {
case <-exitChan:
// keep exitcode and return
case <-detachChan:
exitCode = 0
case waitErr = <-eventsErr:
exitCode = 125
}
}
} else {
select {
case <-exitChan:
return exitCode, nil
case <-detachChan:
return 0, nil
if waitErr != nil {
logrus.Errorf("%v", waitErr)
}
}
statusChan <- exitCode

resBody.Close()
}()
return statusChan, nil
}

// getExitCode performs an inspect on the container. It returns

0 comments on commit a422813

Please sign in to comment.