github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/container/stream/streams.go (about)

     1  package stream // import "github.com/demonoid81/moby/container/stream"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/containerd/containerd/cio"
    12  	"github.com/demonoid81/moby/pkg/broadcaster"
    13  	"github.com/demonoid81/moby/pkg/ioutils"
    14  	"github.com/demonoid81/moby/pkg/pools"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  // Config holds information about I/O streams managed together.
    19  //
    20  // config.StdinPipe returns a WriteCloser which can be used to feed data
    21  // to the standard input of the streamConfig's active process.
    22  // config.StdoutPipe and streamConfig.StderrPipe each return a ReadCloser
    23  // which can be used to retrieve the standard output (and error) generated
    24  // by the container's active process. The output (and error) are actually
    25  // copied and delivered to all StdoutPipe and StderrPipe consumers, using
    26  // a kind of "broadcaster".
    27  type Config struct {
    28  	wg        sync.WaitGroup
    29  	stdout    *broadcaster.Unbuffered
    30  	stderr    *broadcaster.Unbuffered
    31  	stdin     io.ReadCloser
    32  	stdinPipe io.WriteCloser
    33  	dio       *cio.DirectIO
    34  }
    35  
    36  // NewConfig creates a stream config and initializes
    37  // the standard err and standard out to new unbuffered broadcasters.
    38  func NewConfig() *Config {
    39  	return &Config{
    40  		stderr: new(broadcaster.Unbuffered),
    41  		stdout: new(broadcaster.Unbuffered),
    42  	}
    43  }
    44  
    45  // Stdout returns the standard output in the configuration.
    46  func (c *Config) Stdout() *broadcaster.Unbuffered {
    47  	return c.stdout
    48  }
    49  
    50  // Stderr returns the standard error in the configuration.
    51  func (c *Config) Stderr() *broadcaster.Unbuffered {
    52  	return c.stderr
    53  }
    54  
    55  // Stdin returns the standard input in the configuration.
    56  func (c *Config) Stdin() io.ReadCloser {
    57  	return c.stdin
    58  }
    59  
    60  // StdinPipe returns an input writer pipe as an io.WriteCloser.
    61  func (c *Config) StdinPipe() io.WriteCloser {
    62  	return c.stdinPipe
    63  }
    64  
    65  // StdoutPipe creates a new io.ReadCloser with an empty bytes pipe.
    66  // It adds this new out pipe to the Stdout broadcaster.
    67  // This will block stdout if unconsumed.
    68  func (c *Config) StdoutPipe() io.ReadCloser {
    69  	bytesPipe := ioutils.NewBytesPipe()
    70  	c.stdout.Add(bytesPipe)
    71  	return bytesPipe
    72  }
    73  
    74  // StderrPipe creates a new io.ReadCloser with an empty bytes pipe.
    75  // It adds this new err pipe to the Stderr broadcaster.
    76  // This will block stderr if unconsumed.
    77  func (c *Config) StderrPipe() io.ReadCloser {
    78  	bytesPipe := ioutils.NewBytesPipe()
    79  	c.stderr.Add(bytesPipe)
    80  	return bytesPipe
    81  }
    82  
    83  // NewInputPipes creates new pipes for both standard inputs, Stdin and StdinPipe.
    84  func (c *Config) NewInputPipes() {
    85  	c.stdin, c.stdinPipe = io.Pipe()
    86  }
    87  
    88  // NewNopInputPipe creates a new input pipe that will silently drop all messages in the input.
    89  func (c *Config) NewNopInputPipe() {
    90  	c.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard)
    91  }
    92  
    93  // CloseStreams ensures that the configured streams are properly closed.
    94  func (c *Config) CloseStreams() error {
    95  	var errors []string
    96  
    97  	if c.stdin != nil {
    98  		if err := c.stdin.Close(); err != nil {
    99  			errors = append(errors, fmt.Sprintf("error close stdin: %s", err))
   100  		}
   101  	}
   102  
   103  	if err := c.stdout.Clean(); err != nil {
   104  		errors = append(errors, fmt.Sprintf("error close stdout: %s", err))
   105  	}
   106  
   107  	if err := c.stderr.Clean(); err != nil {
   108  		errors = append(errors, fmt.Sprintf("error close stderr: %s", err))
   109  	}
   110  
   111  	if len(errors) > 0 {
   112  		return fmt.Errorf(strings.Join(errors, "\n"))
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  // CopyToPipe connects streamconfig with a libcontainerd.IOPipe
   119  func (c *Config) CopyToPipe(iop *cio.DirectIO) {
   120  	c.dio = iop
   121  	copyFunc := func(w io.Writer, r io.ReadCloser) {
   122  		c.wg.Add(1)
   123  		go func() {
   124  			if _, err := pools.Copy(w, r); err != nil {
   125  				logrus.Errorf("stream copy error: %v", err)
   126  			}
   127  			r.Close()
   128  			c.wg.Done()
   129  		}()
   130  	}
   131  
   132  	if iop.Stdout != nil {
   133  		copyFunc(c.Stdout(), iop.Stdout)
   134  	}
   135  	if iop.Stderr != nil {
   136  		copyFunc(c.Stderr(), iop.Stderr)
   137  	}
   138  
   139  	if stdin := c.Stdin(); stdin != nil {
   140  		if iop.Stdin != nil {
   141  			go func() {
   142  				pools.Copy(iop.Stdin, stdin)
   143  				if err := iop.Stdin.Close(); err != nil {
   144  					logrus.Warnf("failed to close stdin: %v", err)
   145  				}
   146  			}()
   147  		}
   148  	}
   149  }
   150  
   151  // Wait for the stream to close
   152  // Wait supports timeouts via the context to unblock and forcefully
   153  // close the io streams
   154  func (c *Config) Wait(ctx context.Context) {
   155  	done := make(chan struct{}, 1)
   156  	go func() {
   157  		c.wg.Wait()
   158  		close(done)
   159  	}()
   160  	select {
   161  	case <-done:
   162  	case <-ctx.Done():
   163  		if c.dio != nil {
   164  			c.dio.Cancel()
   165  			c.dio.Wait()
   166  			c.dio.Close()
   167  		}
   168  	}
   169  }