github.com/rish1988/moby@v25.0.2+incompatible/pkg/ioutils/bytespipe.go (about)

     1  package ioutils // import "github.com/docker/docker/pkg/ioutils"
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"sync"
     7  )
     8  
     9  // maxCap is the highest capacity to use in byte slices that buffer data.
    10  const maxCap = 1e6
    11  
    12  // minCap is the lowest capacity to use in byte slices that buffer data
    13  const minCap = 64
    14  
    15  // blockThreshold is the minimum number of bytes in the buffer which will cause
    16  // a write to BytesPipe to block when allocating a new slice.
    17  const blockThreshold = 1e6
    18  
    19  var (
    20  	// ErrClosed is returned when Write is called on a closed BytesPipe.
    21  	ErrClosed = errors.New("write to closed BytesPipe")
    22  
    23  	bufPools     = make(map[int]*sync.Pool)
    24  	bufPoolsLock sync.Mutex
    25  )
    26  
    27  // BytesPipe is io.ReadWriteCloser which works similarly to pipe(queue).
    28  // All written data may be read at most once. Also, BytesPipe allocates
    29  // and releases new byte slices to adjust to current needs, so the buffer
    30  // won't be overgrown after peak loads.
    31  type BytesPipe struct {
    32  	mu        sync.Mutex
    33  	wait      *sync.Cond
    34  	buf       []*fixedBuffer
    35  	bufLen    int
    36  	closeErr  error // error to return from next Read. set to nil if not closed.
    37  	readBlock bool  // check read BytesPipe is Wait() or not
    38  }
    39  
    40  // NewBytesPipe creates new BytesPipe, initialized by specified slice.
    41  // If buf is nil, then it will be initialized with slice which cap is 64.
    42  // buf will be adjusted in a way that len(buf) == 0, cap(buf) == cap(buf).
    43  func NewBytesPipe() *BytesPipe {
    44  	bp := &BytesPipe{}
    45  	bp.buf = append(bp.buf, getBuffer(minCap))
    46  	bp.wait = sync.NewCond(&bp.mu)
    47  	return bp
    48  }
    49  
    50  // Write writes p to BytesPipe.
    51  // It can allocate new []byte slices in a process of writing.
    52  func (bp *BytesPipe) Write(p []byte) (int, error) {
    53  	bp.mu.Lock()
    54  	defer bp.mu.Unlock()
    55  
    56  	written := 0
    57  loop0:
    58  	for {
    59  		if bp.closeErr != nil {
    60  			return written, ErrClosed
    61  		}
    62  
    63  		if len(bp.buf) == 0 {
    64  			bp.buf = append(bp.buf, getBuffer(64))
    65  		}
    66  		// get the last buffer
    67  		b := bp.buf[len(bp.buf)-1]
    68  
    69  		n, err := b.Write(p)
    70  		written += n
    71  		bp.bufLen += n
    72  
    73  		// errBufferFull is an error we expect to get if the buffer is full
    74  		if err != nil && err != errBufferFull {
    75  			bp.wait.Broadcast()
    76  			return written, err
    77  		}
    78  
    79  		// if there was enough room to write all then break
    80  		if len(p) == n {
    81  			break
    82  		}
    83  
    84  		// more data: write to the next slice
    85  		p = p[n:]
    86  
    87  		// make sure the buffer doesn't grow too big from this write
    88  		for bp.bufLen >= blockThreshold {
    89  			if bp.readBlock {
    90  				bp.wait.Broadcast()
    91  			}
    92  			bp.wait.Wait()
    93  			if bp.closeErr != nil {
    94  				continue loop0
    95  			}
    96  		}
    97  
    98  		// add new byte slice to the buffers slice and continue writing
    99  		nextCap := b.Cap() * 2
   100  		if nextCap > maxCap {
   101  			nextCap = maxCap
   102  		}
   103  		bp.buf = append(bp.buf, getBuffer(nextCap))
   104  	}
   105  	bp.wait.Broadcast()
   106  	return written, nil
   107  }
   108  
   109  // CloseWithError causes further reads from a BytesPipe to return immediately.
   110  func (bp *BytesPipe) CloseWithError(err error) error {
   111  	bp.mu.Lock()
   112  	if err != nil {
   113  		bp.closeErr = err
   114  	} else {
   115  		bp.closeErr = io.EOF
   116  	}
   117  	bp.wait.Broadcast()
   118  	bp.mu.Unlock()
   119  	return nil
   120  }
   121  
   122  // Close causes further reads from a BytesPipe to return immediately.
   123  func (bp *BytesPipe) Close() error {
   124  	return bp.CloseWithError(nil)
   125  }
   126  
   127  // Read reads bytes from BytesPipe.
   128  // Data could be read only once.
   129  func (bp *BytesPipe) Read(p []byte) (n int, err error) {
   130  	bp.mu.Lock()
   131  	defer bp.mu.Unlock()
   132  	if bp.bufLen == 0 {
   133  		if bp.closeErr != nil {
   134  			return 0, bp.closeErr
   135  		}
   136  		bp.readBlock = true
   137  		bp.wait.Wait()
   138  		bp.readBlock = false
   139  		if bp.bufLen == 0 && bp.closeErr != nil {
   140  			return 0, bp.closeErr
   141  		}
   142  	}
   143  
   144  	for bp.bufLen > 0 {
   145  		b := bp.buf[0]
   146  		read, _ := b.Read(p) // ignore error since fixedBuffer doesn't really return an error
   147  		n += read
   148  		bp.bufLen -= read
   149  
   150  		if b.Len() == 0 {
   151  			// it's empty so return it to the pool and move to the next one
   152  			returnBuffer(b)
   153  			bp.buf[0] = nil
   154  			bp.buf = bp.buf[1:]
   155  		}
   156  
   157  		if len(p) == read {
   158  			break
   159  		}
   160  
   161  		p = p[read:]
   162  	}
   163  
   164  	bp.wait.Broadcast()
   165  	return
   166  }
   167  
   168  func returnBuffer(b *fixedBuffer) {
   169  	b.Reset()
   170  	bufPoolsLock.Lock()
   171  	pool := bufPools[b.Cap()]
   172  	bufPoolsLock.Unlock()
   173  	if pool != nil {
   174  		pool.Put(b)
   175  	}
   176  }
   177  
   178  func getBuffer(size int) *fixedBuffer {
   179  	bufPoolsLock.Lock()
   180  	pool, ok := bufPools[size]
   181  	if !ok {
   182  		pool = &sync.Pool{New: func() interface{} { return &fixedBuffer{buf: make([]byte, 0, size)} }}
   183  		bufPools[size] = pool
   184  	}
   185  	bufPoolsLock.Unlock()
   186  	return pool.Get().(*fixedBuffer)
   187  }