|
@@ -3,11 +3,12 @@ package stream // import "github.com/docker/docker/container/stream"
|
|
|
import (
|
|
|
"context"
|
|
|
"io"
|
|
|
- "sync"
|
|
|
|
|
|
"github.com/docker/docker/pkg/pools"
|
|
|
"github.com/docker/docker/pkg/term"
|
|
|
+ "github.com/pkg/errors"
|
|
|
"github.com/sirupsen/logrus"
|
|
|
+ "golang.org/x/sync/errgroup"
|
|
|
)
|
|
|
|
|
|
var defaultEscapeSequence = []byte{16, 17} // ctrl-p, ctrl-q
|
|
@@ -57,117 +58,107 @@ func (c *Config) AttachStreams(cfg *AttachConfig) {
|
|
|
|
|
|
// CopyStreams starts goroutines to copy data in and out to/from the container
|
|
|
func (c *Config) CopyStreams(ctx context.Context, cfg *AttachConfig) <-chan error {
|
|
|
- var (
|
|
|
- wg sync.WaitGroup
|
|
|
- errors = make(chan error, 3)
|
|
|
- )
|
|
|
+ var group errgroup.Group
|
|
|
|
|
|
+ // Connect stdin of container to the attach stdin stream.
|
|
|
if cfg.Stdin != nil {
|
|
|
- wg.Add(1)
|
|
|
- }
|
|
|
-
|
|
|
- if cfg.Stdout != nil {
|
|
|
- wg.Add(1)
|
|
|
- }
|
|
|
-
|
|
|
- if cfg.Stderr != nil {
|
|
|
- wg.Add(1)
|
|
|
- }
|
|
|
+ group.Go(func() error {
|
|
|
+ logrus.Debug("attach: stdin: begin")
|
|
|
+ defer logrus.Debug("attach: stdin: end")
|
|
|
|
|
|
- // Connect stdin of container to the attach stdin stream.
|
|
|
- go func() {
|
|
|
- if cfg.Stdin == nil {
|
|
|
- return
|
|
|
- }
|
|
|
- logrus.Debug("attach: stdin: begin")
|
|
|
+ defer func() {
|
|
|
+ if cfg.CloseStdin && !cfg.TTY {
|
|
|
+ cfg.CStdin.Close()
|
|
|
+ } else {
|
|
|
+ // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
|
|
+ if cfg.CStdout != nil {
|
|
|
+ cfg.CStdout.Close()
|
|
|
+ }
|
|
|
+ if cfg.CStderr != nil {
|
|
|
+ cfg.CStderr.Close()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }()
|
|
|
|
|
|
- var err error
|
|
|
- if cfg.TTY {
|
|
|
- _, err = copyEscapable(cfg.CStdin, cfg.Stdin, cfg.DetachKeys)
|
|
|
- } else {
|
|
|
- _, err = pools.Copy(cfg.CStdin, cfg.Stdin)
|
|
|
- }
|
|
|
- if err == io.ErrClosedPipe {
|
|
|
- err = nil
|
|
|
- }
|
|
|
- if err != nil {
|
|
|
- logrus.Errorf("attach: stdin: %s", err)
|
|
|
- errors <- err
|
|
|
- }
|
|
|
- if cfg.CloseStdin && !cfg.TTY {
|
|
|
- cfg.CStdin.Close()
|
|
|
- } else {
|
|
|
- // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
|
|
- if cfg.CStdout != nil {
|
|
|
- cfg.CStdout.Close()
|
|
|
+ var err error
|
|
|
+ if cfg.TTY {
|
|
|
+ _, err = copyEscapable(cfg.CStdin, cfg.Stdin, cfg.DetachKeys)
|
|
|
+ } else {
|
|
|
+ _, err = pools.Copy(cfg.CStdin, cfg.Stdin)
|
|
|
}
|
|
|
- if cfg.CStderr != nil {
|
|
|
- cfg.CStderr.Close()
|
|
|
+ if err == io.ErrClosedPipe {
|
|
|
+ err = nil
|
|
|
}
|
|
|
- }
|
|
|
- logrus.Debug("attach: stdin: end")
|
|
|
- wg.Done()
|
|
|
- }()
|
|
|
-
|
|
|
- attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) {
|
|
|
- if stream == nil {
|
|
|
- return
|
|
|
- }
|
|
|
+ if err != nil {
|
|
|
+ logrus.WithError(err).Debug("error on attach stdin")
|
|
|
+ return errors.Wrap(err, "error on attach stdin")
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
+ attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) error {
|
|
|
logrus.Debugf("attach: %s: begin", name)
|
|
|
+ defer logrus.Debugf("attach: %s: end", name)
|
|
|
+ defer func() {
|
|
|
+ // Make sure stdin gets closed
|
|
|
+ if cfg.Stdin != nil {
|
|
|
+ cfg.Stdin.Close()
|
|
|
+ }
|
|
|
+ streamPipe.Close()
|
|
|
+ }()
|
|
|
+
|
|
|
_, err := pools.Copy(stream, streamPipe)
|
|
|
if err == io.ErrClosedPipe {
|
|
|
err = nil
|
|
|
}
|
|
|
if err != nil {
|
|
|
- logrus.Errorf("attach: %s: %v", name, err)
|
|
|
- errors <- err
|
|
|
- }
|
|
|
- // Make sure stdin gets closed
|
|
|
- if cfg.Stdin != nil {
|
|
|
- cfg.Stdin.Close()
|
|
|
+ logrus.WithError(err).Debugf("attach: %s", name)
|
|
|
+ return errors.Wrapf(err, "error attaching %s stream", name)
|
|
|
}
|
|
|
- streamPipe.Close()
|
|
|
- logrus.Debugf("attach: %s: end", name)
|
|
|
- wg.Done()
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
- go attachStream("stdout", cfg.Stdout, cfg.CStdout)
|
|
|
- go attachStream("stderr", cfg.Stderr, cfg.CStderr)
|
|
|
+ if cfg.Stdout != nil {
|
|
|
+ group.Go(func() error {
|
|
|
+ return attachStream("stdout", cfg.Stdout, cfg.CStdout)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if cfg.Stderr != nil {
|
|
|
+ group.Go(func() error {
|
|
|
+ return attachStream("stderr", cfg.Stderr, cfg.CStderr)
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
errs := make(chan error, 1)
|
|
|
-
|
|
|
go func() {
|
|
|
- defer close(errs)
|
|
|
- errs <- func() error {
|
|
|
- done := make(chan struct{})
|
|
|
- go func() {
|
|
|
- wg.Wait()
|
|
|
- close(done)
|
|
|
- }()
|
|
|
- select {
|
|
|
- case <-done:
|
|
|
- case <-ctx.Done():
|
|
|
- // close all pipes
|
|
|
- if cfg.CStdin != nil {
|
|
|
- cfg.CStdin.Close()
|
|
|
- }
|
|
|
- if cfg.CStdout != nil {
|
|
|
- cfg.CStdout.Close()
|
|
|
- }
|
|
|
- if cfg.CStderr != nil {
|
|
|
- cfg.CStderr.Close()
|
|
|
- }
|
|
|
- <-done
|
|
|
+ defer logrus.Debug("attach done")
|
|
|
+ groupErr := make(chan error, 1)
|
|
|
+ go func() {
|
|
|
+ groupErr <- group.Wait()
|
|
|
+ }()
|
|
|
+ select {
|
|
|
+ case <-ctx.Done():
|
|
|
+ // close all pipes
|
|
|
+ if cfg.CStdin != nil {
|
|
|
+ cfg.CStdin.Close()
|
|
|
}
|
|
|
- close(errors)
|
|
|
- for err := range errors {
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
+ if cfg.CStdout != nil {
|
|
|
+ cfg.CStdout.Close()
|
|
|
}
|
|
|
- return nil
|
|
|
- }()
|
|
|
+ if cfg.CStderr != nil {
|
|
|
+ cfg.CStderr.Close()
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now with these closed, wait should return.
|
|
|
+ if err := group.Wait(); err != nil {
|
|
|
+ errs <- err
|
|
|
+ return
|
|
|
+ }
|
|
|
+ errs <- ctx.Err()
|
|
|
+ case err := <-groupErr:
|
|
|
+ errs <- err
|
|
|
+ }
|
|
|
}()
|
|
|
|
|
|
return errs
|