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 }