github.com/bhojpur/cache@v0.0.4/pkg/ioutils/bytespipe.go (about)

     1  package ioutils
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  import (
    24  	"errors"
    25  	"io"
    26  	"sync"
    27  )
    28  
    29  // maxCap is the highest capacity to use in byte slices that buffer data.
    30  const maxCap = 1e6
    31  
    32  // minCap is the lowest capacity to use in byte slices that buffer data
    33  const minCap = 64
    34  
    35  // blockThreshold is the minimum number of bytes in the buffer which will cause
    36  // a write to BytesPipe to block when allocating a new slice.
    37  const blockThreshold = 1e6
    38  
    39  var (
    40  	// ErrClosed is returned when Write is called on a closed BytesPipe.
    41  	ErrClosed = errors.New("write to closed BytesPipe")
    42  
    43  	bufPools     = make(map[int]*sync.Pool)
    44  	bufPoolsLock sync.Mutex
    45  )
    46  
    47  // BytesPipe is io.ReadWriteCloser which works similarly to pipe(queue).
    48  // All written data may be read at most once. Also, BytesPipe allocates
    49  // and releases new byte slices to adjust to current needs, so the buffer
    50  // won't be overgrown after peak loads.
    51  type BytesPipe struct {
    52  	mu       sync.Mutex
    53  	wait     *sync.Cond
    54  	buf      []*fixedBuffer
    55  	bufLen   int
    56  	closeErr error // error to return from next Read. set to nil if not closed.
    57  }
    58  
    59  // NewBytesPipe creates new BytesPipe, initialized by specified slice.
    60  // If buf is nil, then it will be initialized with slice which cap is 64.
    61  // buf will be adjusted in a way that len(buf) == 0, cap(buf) == cap(buf).
    62  func NewBytesPipe() *BytesPipe {
    63  	bp := &BytesPipe{}
    64  	bp.buf = append(bp.buf, getBuffer(minCap))
    65  	bp.wait = sync.NewCond(&bp.mu)
    66  	return bp
    67  }
    68  
    69  // Write writes p to BytesPipe.
    70  // It can allocate new []byte slices in a process of writing.
    71  func (bp *BytesPipe) Write(p []byte) (int, error) {
    72  	bp.mu.Lock()
    73  	defer bp.mu.Unlock()
    74  
    75  	written := 0
    76  loop0:
    77  	for {
    78  		if bp.closeErr != nil {
    79  			return written, ErrClosed
    80  		}
    81  
    82  		if len(bp.buf) == 0 {
    83  			bp.buf = append(bp.buf, getBuffer(64))
    84  		}
    85  		// get the last buffer
    86  		b := bp.buf[len(bp.buf)-1]
    87  
    88  		n, err := b.Write(p)
    89  		written += n
    90  		bp.bufLen += n
    91  
    92  		// errBufferFull is an error we expect to get if the buffer is full
    93  		if err != nil && err != errBufferFull {
    94  			bp.wait.Broadcast()
    95  			return written, err
    96  		}
    97  
    98  		// if there was enough room to write all then break
    99  		if len(p) == n {
   100  			break
   101  		}
   102  
   103  		// more data: write to the next slice
   104  		p = p[n:]
   105  
   106  		// make sure the buffer doesn't grow too big from this write
   107  		for bp.bufLen >= blockThreshold {
   108  			bp.wait.Wait()
   109  			if bp.closeErr != nil {
   110  				continue loop0
   111  			}
   112  		}
   113  
   114  		// add new byte slice to the buffers slice and continue writing
   115  		nextCap := b.Cap() * 2
   116  		if nextCap > maxCap {
   117  			nextCap = maxCap
   118  		}
   119  		bp.buf = append(bp.buf, getBuffer(nextCap))
   120  	}
   121  	bp.wait.Broadcast()
   122  	return written, nil
   123  }
   124  
   125  // CloseWithError causes further reads from a BytesPipe to return immediately.
   126  func (bp *BytesPipe) CloseWithError(err error) error {
   127  	bp.mu.Lock()
   128  	if err != nil {
   129  		bp.closeErr = err
   130  	} else {
   131  		bp.closeErr = io.EOF
   132  	}
   133  	bp.wait.Broadcast()
   134  	bp.mu.Unlock()
   135  	return nil
   136  }
   137  
   138  // Close causes further reads from a BytesPipe to return immediately.
   139  func (bp *BytesPipe) Close() error {
   140  	return bp.CloseWithError(nil)
   141  }
   142  
   143  // Read reads bytes from BytesPipe.
   144  // Data could be read only once.
   145  func (bp *BytesPipe) Read(p []byte) (n int, err error) {
   146  	bp.mu.Lock()
   147  	defer bp.mu.Unlock()
   148  	if bp.bufLen == 0 {
   149  		if bp.closeErr != nil {
   150  			return 0, bp.closeErr
   151  		}
   152  		bp.wait.Wait()
   153  		if bp.bufLen == 0 && bp.closeErr != nil {
   154  			return 0, bp.closeErr
   155  		}
   156  	}
   157  
   158  	for bp.bufLen > 0 {
   159  		b := bp.buf[0]
   160  		read, _ := b.Read(p) // ignore error since fixedBuffer doesn't really return an error
   161  		n += read
   162  		bp.bufLen -= read
   163  
   164  		if b.Len() == 0 {
   165  			// it's empty so return it to the pool and move to the next one
   166  			returnBuffer(b)
   167  			bp.buf[0] = nil
   168  			bp.buf = bp.buf[1:]
   169  		}
   170  
   171  		if len(p) == read {
   172  			break
   173  		}
   174  
   175  		p = p[read:]
   176  	}
   177  
   178  	bp.wait.Broadcast()
   179  	return
   180  }
   181  
   182  func returnBuffer(b *fixedBuffer) {
   183  	b.Reset()
   184  	bufPoolsLock.Lock()
   185  	pool := bufPools[b.Cap()]
   186  	bufPoolsLock.Unlock()
   187  	if pool != nil {
   188  		pool.Put(b)
   189  	}
   190  }
   191  
   192  func getBuffer(size int) *fixedBuffer {
   193  	bufPoolsLock.Lock()
   194  	pool, ok := bufPools[size]
   195  	if !ok {
   196  		pool = &sync.Pool{New: func() interface{} { return &fixedBuffer{buf: make([]byte, 0, size)} }}
   197  		bufPools[size] = pool
   198  	}
   199  	bufPoolsLock.Unlock()
   200  	return pool.Get().(*fixedBuffer)
   201  }