From dc56a76bc9f16b2d57b9d64822e305c1e787fcf0 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Fri, 11 Mar 2016 09:59:50 +0800 Subject: [PATCH] Fix race condition with exec and resize When I use `docker exec -ti test ls`, I got error: ``` ERRO[0035] Handler for POST /v1.23/exec/9677ecd7aa9de96f8e9e667519ff266ad26a5be80e80021a997fff6084ed6d75/resize returned error: bad file descriptor ``` It's because `POST /exec//start` and `POST /exec//resize` are asynchronous, it is possible that exec process finishes and ternimal is closed before resize. Then `console.Fd()` will get a large invalid number and we got the above error. Fix it by adding synchronization between exec and resize. Signed-off-by: Qiang Huang --- daemon/exec.go | 3 +++ daemon/exec/exec.go | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/daemon/exec.go b/daemon/exec.go index 56798a5979..24b0bd11e0 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -305,6 +305,9 @@ func (d *Daemon) monitorExec(container *container.Container, execConfig *exec.Co } if execConfig.ProcessConfig.Terminal != nil { + if err := execConfig.WaitResize(); err != nil { + logrus.Errorf("Error waiting for resize: %v", err) + } if err := execConfig.ProcessConfig.Terminal.Close(); err != nil { logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err) } diff --git a/daemon/exec/exec.go b/daemon/exec/exec.go index 2efb20ee9a..7d10dd9f4e 100644 --- a/daemon/exec/exec.go +++ b/daemon/exec/exec.go @@ -29,6 +29,9 @@ type Config struct { // waitStart will be closed immediately after the exec is really started. waitStart chan struct{} + + // waitResize will be closed after Resize is finished. + waitResize chan struct{} } // NewConfig initializes the a new exec configuration @@ -37,6 +40,7 @@ func NewConfig() *Config { ID: stringid.GenerateNonCryptoID(), StreamConfig: runconfig.NewStreamConfig(), waitStart: make(chan struct{}), + waitResize: make(chan struct{}), } } @@ -106,13 +110,29 @@ func (c *Config) Wait(cErr chan error) error { return nil } +// WaitResize waits until terminal resize finishes or time out. +func (c *Config) WaitResize() error { + select { + case <-c.waitResize: + case <-time.After(time.Second): + return fmt.Errorf("Terminal resize for exec %s time out.", c.ID) + } + return nil +} + // Close closes the wait channel for the progress. func (c *Config) Close() { close(c.waitStart) } +// CloseResize closes the wait channel for resizing terminal. +func (c *Config) CloseResize() { + close(c.waitResize) +} + // Resize changes the size of the terminal for the exec process. func (c *Config) Resize(h, w int) error { + defer c.CloseResize() select { case <-c.waitStart: case <-time.After(time.Second):