From a4811daf31844476ff0f160285f0e6c088db4662 Mon Sep 17 00:00:00 2001 From: Andy Goldstein Date: Fri, 23 Jun 2017 16:06:07 -0400 Subject: [PATCH] Fix initial exec terminal dimensions Delay attempting to send a terminal resize request to docker until after the exec has started; otherwise, the initial resize request will fail. --- pkg/kubelet/dockershim/exec.go | 24 ++++++++++++++++--- .../libdocker/kube_docker_client.go | 10 ++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/pkg/kubelet/dockershim/exec.go b/pkg/kubelet/dockershim/exec.go index 6d4b229cd8..a861bb47f0 100644 --- a/pkg/kubelet/dockershim/exec.go +++ b/pkg/kubelet/dockershim/exec.go @@ -135,6 +135,9 @@ func (*NsenterExecHandler) ExecInContainer(client libdocker.Interface, container type NativeExecHandler struct{} func (*NativeExecHandler) ExecInContainer(client libdocker.Interface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error { + done := make(chan struct{}) + defer close(done) + createOpts := dockertypes.ExecConfig{ Cmd: cmd, AttachStdin: stdin != nil, @@ -149,9 +152,23 @@ func (*NativeExecHandler) ExecInContainer(client libdocker.Interface, container // Have to start this before the call to client.StartExec because client.StartExec is a blocking // call :-( Otherwise, resize events don't get processed and the terminal never resizes. - kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) { - client.ResizeExecTTY(execObj.ID, uint(size.Height), uint(size.Width)) - }) + // + // We also have to delay attempting to send a terminal resize request to docker until after the + // exec has started; otherwise, the initial resize request will fail. + execStarted := make(chan struct{}) + go func() { + select { + case <-execStarted: + // client.StartExec has started the exec, so we can start resizing + case <-done: + // ExecInContainer has returned, so short-circuit + return + } + + kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) { + client.ResizeExecTTY(execObj.ID, uint(size.Height), uint(size.Width)) + }) + }() startOpts := dockertypes.ExecStartCheck{Detach: false, Tty: tty} streamOpts := libdocker.StreamOptions{ @@ -159,6 +176,7 @@ func (*NativeExecHandler) ExecInContainer(client libdocker.Interface, container OutputStream: stdout, ErrorStream: stderr, RawTerminal: tty, + ExecStarted: execStarted, } err = client.StartExec(execObj.ID, startOpts, streamOpts) if err != nil { diff --git a/pkg/kubelet/dockershim/libdocker/kube_docker_client.go b/pkg/kubelet/dockershim/libdocker/kube_docker_client.go index b74a5d0435..d9d09750ff 100644 --- a/pkg/kubelet/dockershim/libdocker/kube_docker_client.go +++ b/pkg/kubelet/dockershim/libdocker/kube_docker_client.go @@ -454,6 +454,15 @@ func (d *kubeDockerClient) StartExec(startExec string, opts dockertypes.ExecStar return err } defer resp.Close() + + if sopts.ExecStarted != nil { + // Send a message to the channel indicating that the exec has started. This is needed so + // interactive execs can handle resizing correctly - the request to resize the TTY has to happen + // after the call to d.client.ContainerExecAttach, and because d.holdHijackedConnection below + // blocks, we use sopts.ExecStarted to signal the caller that it's ok to resize. + sopts.ExecStarted <- struct{}{} + } + return d.holdHijackedConnection(sopts.RawTerminal || opts.Tty, sopts.InputStream, sopts.OutputStream, sopts.ErrorStream, resp) } @@ -584,6 +593,7 @@ type StreamOptions struct { InputStream io.Reader OutputStream io.Writer ErrorStream io.Writer + ExecStarted chan struct{} } // operationTimeout is the error returned when the docker operations are timeout.