
To avoid buffer bloat in long running processes, we try to use buffer pools where possible. This is meant to address shim memory usage issues, but may not be the root cause. Signed-off-by: Stephen J Day <stephen.day@docker.com>
137 lines
2.8 KiB
Go
137 lines
2.8 KiB
Go
package cio
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
|
|
winio "github.com/Microsoft/go-winio"
|
|
"github.com/containerd/containerd/log"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const pipeRoot = `\\.\pipe`
|
|
|
|
// NewFIFOSetInDir returns a new set of fifos for the task
|
|
func NewFIFOSetInDir(_, id string, terminal bool) (*FIFOSet, error) {
|
|
return NewFIFOSet(Config{
|
|
Terminal: terminal,
|
|
Stdin: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id),
|
|
Stdout: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id),
|
|
Stderr: fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id),
|
|
}, nil), nil
|
|
}
|
|
|
|
func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
|
|
var (
|
|
wg sync.WaitGroup
|
|
set []io.Closer
|
|
)
|
|
|
|
if fifos.Stdin != "" {
|
|
l, err := winio.ListenPipe(fifos.Stdin, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Stdin)
|
|
}
|
|
defer func(l net.Listener) {
|
|
if err != nil {
|
|
l.Close()
|
|
}
|
|
}(l)
|
|
set = append(set, l)
|
|
|
|
go func() {
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
log.L.WithError(err).Errorf("failed to accept stdin connection on %s", fifos.Stdin)
|
|
return
|
|
}
|
|
|
|
p := bufPool.Get().(*[]byte)
|
|
defer bufPool.Put(p)
|
|
|
|
io.CopyBuffer(c, ioset.Stdin, *p)
|
|
c.Close()
|
|
l.Close()
|
|
}()
|
|
}
|
|
|
|
if fifos.Stdout != "" {
|
|
l, err := winio.ListenPipe(fifos.Stdout, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Stdout)
|
|
}
|
|
defer func(l net.Listener) {
|
|
if err != nil {
|
|
l.Close()
|
|
}
|
|
}(l)
|
|
set = append(set, l)
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
log.L.WithError(err).Errorf("failed to accept stdout connection on %s", fifos.Stdout)
|
|
return
|
|
}
|
|
|
|
p := bufPool.Get().(*[]byte)
|
|
defer bufPool.Put(p)
|
|
|
|
io.CopyBuffer(ioset.Stdout, c, *p)
|
|
c.Close()
|
|
l.Close()
|
|
}()
|
|
}
|
|
|
|
if !fifos.Terminal && fifos.Stderr != "" {
|
|
l, err := winio.ListenPipe(fifos.Stderr, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create stderr pipe %s", fifos.Stderr)
|
|
}
|
|
defer func(l net.Listener) {
|
|
if err != nil {
|
|
l.Close()
|
|
}
|
|
}(l)
|
|
set = append(set, l)
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
log.L.WithError(err).Errorf("failed to accept stderr connection on %s", fifos.Stderr)
|
|
return
|
|
}
|
|
|
|
p := bufPool.Get().(*[]byte)
|
|
defer bufPool.Put(p)
|
|
|
|
io.CopyBuffer(ioset.Stderr, c, *p)
|
|
c.Close()
|
|
l.Close()
|
|
}()
|
|
}
|
|
|
|
return &cio{config: fifos.Config, closers: set}, nil
|
|
}
|
|
|
|
// NewDirectIO returns an IO implementation that exposes the IO streams as io.ReadCloser
|
|
// and io.WriteCloser.
|
|
func NewDirectIO(stdin io.WriteCloser, stdout, stderr io.ReadCloser, terminal bool) *DirectIO {
|
|
return &DirectIO{
|
|
pipes: pipes{
|
|
Stdin: stdin,
|
|
Stdout: stdout,
|
|
Stderr: stderr,
|
|
},
|
|
cio: cio{
|
|
config: Config{Terminal: terminal},
|
|
},
|
|
}
|
|
}
|