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 }