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  }