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/<id>/start` and
`POST /exec/<id>/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 <h.huangqiang@huawei.com>
This commit is contained in:
Qiang Huang 2016-03-11 09:59:50 +08:00
parent edf3c0f112
commit dc56a76bc9
2 changed files with 23 additions and 0 deletions

View File

@ -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)
}

View File

@ -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):