github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/multiplexing/ring/buffer.go (about) 1 package ring 2 3 import ( 4 "errors" 5 "io" 6 ) 7 8 var ( 9 // ErrBufferFull is the error returned by Buffer if a storage operation 10 // can't be completed due to a lack of space in the buffer. 11 ErrBufferFull = errors.New("buffer full") 12 ) 13 14 // min returns the lesser of a or b. 15 func min(a, b int) int { 16 if a < b { 17 return a 18 } 19 return b 20 } 21 22 // Buffer is a fixed-size ring buffer for storing bytes. Its behavior is 23 // designed to match that of bytes.Buffer as closely as possible. The zero value 24 // for Buffer is a buffer with zero capacity. 25 type Buffer struct { 26 // storage is the buffer's underlying storage. There are eight possible data 27 // layout states within the storage buffer depending on the buffer size and 28 // operational history: 29 // 30 // - [] (Buffers of length 0 only) 31 // - [FREE1] (Buffers of length >= 1, start always reset to 0 in this case) 32 // - [DATA1] (Buffers of length >= 1) 33 // - [DATA1|FREE1] (Buffers of length >= 2) 34 // - [FREE1|DATA1] (Buffers of length >= 2) 35 // - [DATA2|DATA1] (Buffers of length >= 2) 36 // - The corresponding [FREE2|FREE1] layout is prohibited by an optimizing 37 // reset operation whenever the buffer is fully drained. 38 // - [FREE2|DATA1|FREE1] (Buffers of length >= 3) 39 // - [DATA2|FREE1|DATA1] (Buffers of length >= 3) 40 // 41 // No additional states with further fragmentation of data or free space are 42 // possible under the invariants of the buffer's algorithms (nor would they 43 // be encodable by this data structure). 44 storage []byte 45 // size is the storage buffer size. It is cached for better performance. 46 size int 47 // start is the data start index. It is restricted to the range [0, size). 48 start int 49 // used is the number of bytes currently stored in the buffer. It is 50 // restricted to the range [0, size]. 51 used int 52 } 53 54 // NewBuffer creates a new ring buffer with the specified size. If size is less 55 // than or equal to 0, then a buffer with zero capacity is created. 56 func NewBuffer(size int) *Buffer { 57 if size <= 0 { 58 return &Buffer{} 59 } 60 return &Buffer{ 61 storage: make([]byte, size), 62 size: size, 63 } 64 } 65 66 // Size returns the size of the buffer. 67 func (b *Buffer) Size() int { 68 return b.size 69 } 70 71 // Used returns how many bytes currently reside in the buffer. 72 func (b *Buffer) Used() int { 73 return b.used 74 } 75 76 // Free returns the unused buffer capacity. 77 func (b *Buffer) Free() int { 78 return b.size - b.used 79 } 80 81 // Reset clears all data within the buffer. 82 func (b *Buffer) Reset() { 83 b.start = 0 84 b.used = 0 85 } 86 87 // Write implements io.Writer.Write. 88 func (b *Buffer) Write(data []byte) (int, error) { 89 // Loop until we've consumed the data buffer or run out of storage. 90 var result int 91 for len(data) > 0 && b.used != b.size { 92 // Compute the first available contiguous free storage segment. 93 freeStart := (b.start + b.used) % b.size 94 free := b.storage[freeStart:min(freeStart+(b.size-b.used), b.size)] 95 96 // Copy data into storage. 97 copied := copy(free, data) 98 99 // Update indices and tracking. 100 result += copied 101 data = data[copied:] 102 b.used += copied 103 } 104 105 // If we couldn't fully consume the source buffer due to a lack of storage, 106 // then we need to return an error. 107 if len(data) > 0 && b.used == b.size { 108 return result, ErrBufferFull 109 } 110 111 // Success. 112 return result, nil 113 } 114 115 // WriteByte implements io.ByteWriter.WriteByte. 116 func (b *Buffer) WriteByte(value byte) error { 117 // If there's no space available, then we can't write the byte. 118 if b.used == b.size { 119 return ErrBufferFull 120 } 121 122 // Compute the start of the first available free storage segment. 123 freeStart := (b.start + b.used) % b.size 124 125 // Store the byte. 126 b.storage[freeStart] = value 127 128 // Update tracking. 129 b.used += 1 130 131 // Success. 132 return nil 133 } 134 135 // ReadNFrom is similar to using io.ReaderFrom.ReadFrom with io.LimitedReader, 136 // but it is designed to support a limited-capacity buffer, which can't reliably 137 // detect EOF without potentially wasting data from the stream. In particular, 138 // Buffer can't reliably detect the case that EOF is reached right as its 139 // storage is filled because io.Reader is not required to return io.EOF until 140 // the next call, and most implementations (including io.LimitedReader) will 141 // only return io.EOF on a subsequent call. Moreover, io.Reader isn't required 142 // to return an EOF indication on a zero-length read, so even a follow-up 143 // zero-length read can't be used to reliably detect EOF. As such, this method 144 // provides a more explicit definition of the number of bytes to read, and it 145 // will return io.EOF if encountered, unless it occurs simultaneously with 146 // request completion. 147 func (b *Buffer) ReadNFrom(reader io.Reader, n int) (int, error) { 148 // Loop until we've filled completed the read, run out of storage, or 149 // encountered a read error. 150 var read, result int 151 var err error 152 for n > 0 && b.used != b.size && err == nil { 153 // Compute the first available contiguous free storage segment. 154 freeStart := (b.start + b.used) % b.size 155 free := b.storage[freeStart:min(freeStart+(b.size-b.used), b.size)] 156 157 // If the storage segment is larger than we need, then truncate it. 158 if len(free) > n { 159 free = free[:n] 160 } 161 162 // Perform the read. 163 read, err = reader.Read(free) 164 165 // Update indices and tracking. 166 result += read 167 b.used += read 168 n -= read 169 } 170 171 // If we couldn't complete the read due to a lack of storage, then we need 172 // to return an error. However, if a read error occurred simultaneously with 173 // running out of storage, then we don't overwrite it. 174 if n > 0 && b.used == b.size && err == nil { 175 err = ErrBufferFull 176 } 177 178 // If we encountered io.EOF simultaneously with completing the read, then we 179 // can clear the error. 180 if err == io.EOF && n == 0 { 181 err = nil 182 } 183 184 // Done. 185 return result, err 186 } 187 188 // Read implements io.Reader.Read. 189 func (b *Buffer) Read(buffer []byte) (int, error) { 190 // If the destination buffer is zero-length, then we return with no error, 191 // even if we have no data available. Otherwise, if we don't have any data 192 // available, then return EOF. 193 if len(buffer) == 0 { 194 return 0, nil 195 } else if b.used == 0 { 196 return 0, io.EOF 197 } 198 199 // Loop until we've filled the destination buffer or drained storage. 200 var result int 201 for len(buffer) > 0 && b.used > 0 { 202 // Compute the first available contiguous data segment. 203 data := b.storage[b.start:min(b.start+b.used, b.size)] 204 205 // Copy the data. 206 copied := copy(buffer, data) 207 208 // Update indices and tracking. 209 result += copied 210 buffer = buffer[copied:] 211 b.start += copied 212 b.start %= b.size 213 b.used -= copied 214 } 215 216 // Reset to an optimal layout if possible. 217 if b.used == 0 { 218 b.start = 0 219 } 220 221 // Success. 222 return result, nil 223 } 224 225 // ReadByte implements io.ByteReader.ReadByte. 226 func (b *Buffer) ReadByte() (byte, error) { 227 // If we don't have any data available, then return EOF. 228 if b.used == 0 { 229 return 0, io.EOF 230 } 231 232 // Extract the first byte of data. 233 result := b.storage[b.start] 234 235 // Update indices and tracking. 236 b.start += 1 237 b.start %= b.size 238 b.used -= 1 239 240 // Reset to an optimal layout if possible. 241 if b.used == 0 { 242 b.start = 0 243 } 244 245 // Success. 246 return result, nil 247 } 248 249 // WriteTo implements io.WriterTo.WriteTo. 250 func (b *Buffer) WriteTo(writer io.Writer) (int64, error) { 251 // Loop until we've drained the storage buffer or encountered a write error. 252 var written int 253 var result int64 254 var err error 255 for b.used > 0 && err == nil { 256 // Compute the first available contiguous data segment. 257 data := b.storage[b.start:min(b.start+b.used, b.size)] 258 259 // Write the data. 260 written, err = writer.Write(data) 261 262 // Update indices and tracking. 263 result += int64(written) 264 b.start += written 265 b.start %= b.size 266 b.used -= written 267 } 268 269 // Reset to an optimal layout if possible. 270 if b.used == 0 { 271 b.start = 0 272 } 273 274 // Done. 275 return result, err 276 }