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 }