github.com/thanos-io/thanos@v0.32.5/pkg/store/io.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package store
     5  
     6  import (
     7  	"bufio"
     8  	"io"
     9  
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  const (
    14  	readerBufferSize = 32 * 1024
    15  )
    16  
    17  // byteRange holds information about a single byte range.
    18  type byteRange struct {
    19  	offset int
    20  	length int
    21  }
    22  
    23  // byteRanges holds a list of non-overlapping byte ranges sorted by offset.
    24  type byteRanges []byteRange
    25  
    26  // size returns the total number of bytes in the byte ranges.
    27  func (r byteRanges) size() int {
    28  	size := 0
    29  	for _, c := range r {
    30  		size += c.length
    31  	}
    32  	return size
    33  }
    34  
    35  // areContiguous returns whether all byte ranges are contiguous (no gaps).
    36  func (r byteRanges) areContiguous() bool {
    37  	if len(r) < 2 {
    38  		return true
    39  	}
    40  
    41  	for off, idx := r[0].offset+r[0].length, 1; idx < len(r); idx++ {
    42  		if r[idx].offset != off {
    43  			return false
    44  		}
    45  		off += r[idx].length
    46  	}
    47  	return true
    48  }
    49  
    50  // readByteRanges reads the provided byteRanges from src and append them to dst. The provided
    51  // byteRanges must be sorted by offset and non overlapping. The byteRanges offset must be
    52  // relative to the beginning of the provided src (offset 0 == first byte will be read from src).
    53  func readByteRanges(src io.Reader, dst []byte, byteRanges byteRanges) ([]byte, error) {
    54  	if len(byteRanges) == 0 {
    55  		return nil, nil
    56  	}
    57  
    58  	// Ensure the provided dst buffer has enough capacity.
    59  	expectedSize := byteRanges.size()
    60  	if cap(dst) < expectedSize {
    61  		return nil, io.ErrShortBuffer
    62  	}
    63  
    64  	// Size the destination buffer accordingly.
    65  	dst = dst[0:expectedSize]
    66  
    67  	// Optimisation for the case all ranges are contiguous.
    68  	if byteRanges[0].offset == 0 && byteRanges.areContiguous() {
    69  		// We get an ErrUnexpectedEOF if EOF is reached before we fill allocated dst slice.
    70  		// Due to how the reading logic works in the bucket store, we may try to overread at
    71  		// the end of an object, so we consider it legit.
    72  		if _, err := io.ReadFull(src, dst); err != nil && err != io.ErrUnexpectedEOF {
    73  			return nil, err
    74  		}
    75  		return dst, nil
    76  	}
    77  
    78  	// To keep implementation easier we frequently call Read() for short lengths.
    79  	// In such scenario, having a buffered reader improves performances at the cost
    80  	// of 1 more buffer allocation and memory copy.
    81  	reader := bufio.NewReaderSize(src, readerBufferSize)
    82  
    83  	for dstOffset, idx := 0, 0; idx < len(byteRanges); idx++ {
    84  		curr := byteRanges[idx]
    85  
    86  		// Read and discard all bytes before the current chunk offset.
    87  		discard := 0
    88  		if idx == 0 {
    89  			discard = curr.offset
    90  		} else {
    91  			prev := byteRanges[idx-1]
    92  			discard = curr.offset - (prev.offset + prev.length)
    93  		}
    94  
    95  		if _, err := reader.Discard(discard); err != nil {
    96  			if err == io.EOF {
    97  				err = io.ErrUnexpectedEOF
    98  			}
    99  			return nil, errors.Wrap(err, "discard bytes")
   100  		}
   101  
   102  		// At this point the next byte to read from the reader is the current chunk,
   103  		// so we'll read it fully. io.ReadFull() returns an error if less bytes than
   104  		// expected have been read.
   105  		readBytes, err := io.ReadFull(reader, dst[dstOffset:dstOffset+curr.length])
   106  		if readBytes > 0 {
   107  			dstOffset += readBytes
   108  		}
   109  		if err != nil {
   110  			// We get an ErrUnexpectedEOF if EOF is reached before we fill the slice.
   111  			// Due to how the reading logic works in the bucket store, we may try to overread
   112  			// the last byte range so, if the error occurrs on the last one, we consider it legit.
   113  			if err == io.ErrUnexpectedEOF && idx == len(byteRanges)-1 {
   114  				return dst, nil
   115  			}
   116  
   117  			if err == io.EOF {
   118  				err = io.ErrUnexpectedEOF
   119  			}
   120  			return nil, errors.Wrap(err, "read byte range")
   121  		}
   122  	}
   123  
   124  	return dst, nil
   125  }