github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/common/ledger/blkstorage/fsblkstorage/block_stream.go (about) 1 /* 2 Copyright IBM Corp. 2016 All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package fsblkstorage 18 19 import ( 20 "bufio" 21 "errors" 22 "fmt" 23 "io" 24 "os" 25 26 "github.com/golang/protobuf/proto" 27 ) 28 29 // ErrUnexpectedEndOfBlockfile error used to indicate an unexpected end of a file segment 30 // this can happen mainly if a crash occurs during appening a block and partial block contents 31 // get written towards the end of the file 32 var ErrUnexpectedEndOfBlockfile = errors.New("unexpected end of blockfile") 33 34 // blockfileStream reads blocks sequentially from a single file. 35 // It starts from the given offset and can traverse till the end of the file 36 type blockfileStream struct { 37 fileNum int 38 file *os.File 39 reader *bufio.Reader 40 currentOffset int64 41 } 42 43 // blockStream reads blocks sequentially from multiple files. 44 // it starts from a given file offset and continues with the next 45 // file segment until the end of the last segment (`endFileNum`) 46 type blockStream struct { 47 rootDir string 48 currentFileNum int 49 endFileNum int 50 currentFileStream *blockfileStream 51 } 52 53 // blockPlacementInfo captures the information related 54 // to block's placement in the file. 55 type blockPlacementInfo struct { 56 fileNum int 57 blockStartOffset int64 58 blockBytesOffset int64 59 } 60 61 /////////////////////////////////// 62 // blockfileStream functions 63 //////////////////////////////////// 64 func newBlockfileStream(rootDir string, fileNum int, startOffset int64) (*blockfileStream, error) { 65 filePath := deriveBlockfilePath(rootDir, fileNum) 66 logger.Debugf("newBlockfileStream(): filePath=[%s], startOffset=[%d]", filePath, startOffset) 67 var file *os.File 68 var err error 69 if file, err = os.OpenFile(filePath, os.O_RDONLY, 0600); err != nil { 70 return nil, err 71 } 72 var newPosition int64 73 if newPosition, err = file.Seek(startOffset, 0); err != nil { 74 // file.Seek does not raise an error - simply seeks to the new position 75 return nil, err 76 } 77 if newPosition != startOffset { 78 panic(fmt.Sprintf("Could not seek file [%s] to given startOffset [%d]. New position = [%d]", 79 filePath, startOffset, newPosition)) 80 } 81 s := &blockfileStream{fileNum, file, bufio.NewReader(file), startOffset} 82 return s, nil 83 } 84 85 func (s *blockfileStream) nextBlockBytes() ([]byte, error) { 86 blockBytes, _, err := s.nextBlockBytesAndPlacementInfo() 87 return blockBytes, err 88 } 89 90 // nextBlockBytesAndPlacementInfo returns bytes for the next block 91 // along with the offset information in the block file. 92 // An error `ErrUnexpectedEndOfBlockfile` is returned if a partial written data is detected 93 // which is possible towards the tail of the file if a crash had taken place during appending of a block 94 func (s *blockfileStream) nextBlockBytesAndPlacementInfo() ([]byte, *blockPlacementInfo, error) { 95 var lenBytes []byte 96 var err error 97 var fileInfo os.FileInfo 98 moreContentAvailable := true 99 100 if fileInfo, err = s.file.Stat(); err != nil { 101 return nil, nil, err 102 } 103 if s.currentOffset == fileInfo.Size() { 104 logger.Debugf("Finished reading file number [%d]", s.fileNum) 105 return nil, nil, nil 106 } 107 remainingBytes := fileInfo.Size() - s.currentOffset 108 // Peek 8 or smaller number of bytes (if remaining bytes are less than 8) 109 // Assumption is that a block size would be small enough to be represented in 8 bytes varint 110 peekBytes := 8 111 if remainingBytes < int64(peekBytes) { 112 peekBytes = int(remainingBytes) 113 moreContentAvailable = false 114 } 115 logger.Debugf("Remaining bytes=[%d], Going to peek [%d] bytes", remainingBytes, peekBytes) 116 if lenBytes, err = s.reader.Peek(peekBytes); err != nil { 117 return nil, nil, err 118 } 119 length, n := proto.DecodeVarint(lenBytes) 120 if n == 0 { 121 // proto.DecodeVarint did not consume any byte at all which means that the bytes 122 // representing the size of the block are partial bytes 123 if !moreContentAvailable { 124 return nil, nil, ErrUnexpectedEndOfBlockfile 125 } 126 panic(fmt.Errorf("Error in decoding varint bytes [%#v]", lenBytes)) 127 } 128 bytesExpected := int64(n) + int64(length) 129 if bytesExpected > remainingBytes { 130 logger.Debugf("At least [%d] bytes expected. Remaining bytes = [%d]. Returning with error [%s]", 131 bytesExpected, remainingBytes, ErrUnexpectedEndOfBlockfile) 132 return nil, nil, ErrUnexpectedEndOfBlockfile 133 } 134 // skip the bytes representing the block size 135 if _, err = s.reader.Discard(n); err != nil { 136 return nil, nil, err 137 } 138 blockBytes := make([]byte, length) 139 if _, err = io.ReadAtLeast(s.reader, blockBytes, int(length)); err != nil { 140 logger.Debugf("Error while trying to read [%d] bytes from fileNum [%d]: %s", length, s.fileNum, err) 141 return nil, nil, err 142 } 143 blockPlacementInfo := &blockPlacementInfo{ 144 fileNum: s.fileNum, 145 blockStartOffset: s.currentOffset, 146 blockBytesOffset: s.currentOffset + int64(n)} 147 s.currentOffset += int64(n) + int64(length) 148 logger.Debugf("Returning blockbytes - length=[%d], placementInfo={%s}", len(blockBytes), blockPlacementInfo) 149 return blockBytes, blockPlacementInfo, nil 150 } 151 152 func (s *blockfileStream) close() error { 153 return s.file.Close() 154 } 155 156 /////////////////////////////////// 157 // blockStream functions 158 //////////////////////////////////// 159 func newBlockStream(rootDir string, startFileNum int, startOffset int64, endFileNum int) (*blockStream, error) { 160 startFileStream, err := newBlockfileStream(rootDir, startFileNum, startOffset) 161 if err != nil { 162 return nil, err 163 } 164 return &blockStream{rootDir, startFileNum, endFileNum, startFileStream}, nil 165 } 166 167 func (s *blockStream) moveToNextBlockfileStream() error { 168 var err error 169 if err = s.currentFileStream.close(); err != nil { 170 return err 171 } 172 s.currentFileNum++ 173 if s.currentFileStream, err = newBlockfileStream(s.rootDir, s.currentFileNum, 0); err != nil { 174 return err 175 } 176 return nil 177 } 178 179 func (s *blockStream) nextBlockBytes() ([]byte, error) { 180 blockBytes, _, err := s.nextBlockBytesAndPlacementInfo() 181 return blockBytes, err 182 } 183 184 func (s *blockStream) nextBlockBytesAndPlacementInfo() ([]byte, *blockPlacementInfo, error) { 185 var blockBytes []byte 186 var blockPlacementInfo *blockPlacementInfo 187 var err error 188 if blockBytes, blockPlacementInfo, err = s.currentFileStream.nextBlockBytesAndPlacementInfo(); err != nil { 189 logger.Debugf("current file [%d]", s.currentFileNum) 190 logger.Debugf("blockbytes [%d]. Err:%s", len(blockBytes), err) 191 return nil, nil, err 192 } 193 logger.Debugf("blockbytes [%d] read from file [%d]", len(blockBytes), s.currentFileNum) 194 if blockBytes == nil && (s.currentFileNum < s.endFileNum || s.endFileNum < 0) { 195 logger.Debugf("current file [%d] exhausted. Moving to next file", s.currentFileNum) 196 if err = s.moveToNextBlockfileStream(); err != nil { 197 return nil, nil, err 198 } 199 return s.nextBlockBytesAndPlacementInfo() 200 } 201 return blockBytes, blockPlacementInfo, nil 202 } 203 204 func (s *blockStream) close() error { 205 return s.currentFileStream.close() 206 } 207 208 func (i *blockPlacementInfo) String() string { 209 return fmt.Sprintf("fileNum=[%d], startOffset=[%d], bytesOffset=[%d]", 210 i.fileNum, i.blockStartOffset, i.blockBytesOffset) 211 }