github.com/flower-corp/rosedb@v1.1.2-0.20230117132829-21dc4f7b319a/logfile/log_file.go (about) 1 package logfile 2 3 import ( 4 "errors" 5 "fmt" 6 "hash/crc32" 7 "path/filepath" 8 "sync" 9 "sync/atomic" 10 11 "github.com/flower-corp/rosedb/ioselector" 12 ) 13 14 var ( 15 // ErrInvalidCrc invalid crc. 16 ErrInvalidCrc = errors.New("logfile: invalid crc") 17 18 // ErrWriteSizeNotEqual write size is not equal to entry size. 19 ErrWriteSizeNotEqual = errors.New("logfile: write size is not equal to entry size") 20 21 // ErrEndOfEntry end of entry in log file. 22 ErrEndOfEntry = errors.New("logfile: end of entry in log file") 23 24 // ErrUnsupportedIoType unsupported io type, only mmap and fileIO now. 25 ErrUnsupportedIoType = errors.New("unsupported io type") 26 27 // ErrUnsupportedLogFileType unsupported log file type, only WAL and ValueLog now. 28 ErrUnsupportedLogFileType = errors.New("unsupported log file type") 29 ) 30 31 const ( 32 // InitialLogFileId initial log file id: 0. 33 InitialLogFileId = 0 34 35 // FilePrefix log file prefix. 36 FilePrefix = "log." 37 ) 38 39 // FileType represents different types of log file: wal and value log. 40 type FileType int8 41 42 const ( 43 Strs FileType = iota 44 List 45 Hash 46 Sets 47 ZSet 48 ) 49 50 var ( 51 FileNamesMap = map[FileType]string{ 52 Strs: "log.strs.", 53 List: "log.list.", 54 Hash: "log.hash.", 55 Sets: "log.sets.", 56 ZSet: "log.zset.", 57 } 58 59 // FileTypesMap name->type 60 FileTypesMap = map[string]FileType{ 61 "strs": Strs, 62 "list": List, 63 "hash": Hash, 64 "sets": Sets, 65 "zset": ZSet, 66 } 67 ) 68 69 // IOType represents different types of file io: FileIO(standard file io) and MMap(Memory Map). 70 type IOType int8 71 72 const ( 73 // FileIO standard file io. 74 FileIO IOType = iota 75 // MMap Memory Map. 76 MMap 77 ) 78 79 // LogFile is an abstraction of a disk file, entry`s read and write will go through it. 80 type LogFile struct { 81 sync.RWMutex 82 Fid uint32 83 WriteAt int64 84 IoSelector ioselector.IOSelector 85 } 86 87 // OpenLogFile open an existing or create a new log file. 88 // fsize must be a postitive number.And we will create io selector according to ioType. 89 func OpenLogFile(path string, fid uint32, fsize int64, ftype FileType, ioType IOType) (lf *LogFile, err error) { 90 lf = &LogFile{Fid: fid} 91 fileName, err := lf.getLogFileName(path, fid, ftype) 92 if err != nil { 93 return nil, err 94 } 95 96 var selector ioselector.IOSelector 97 switch ioType { 98 case FileIO: 99 if selector, err = ioselector.NewFileIOSelector(fileName, fsize); err != nil { 100 return 101 } 102 case MMap: 103 if selector, err = ioselector.NewMMapSelector(fileName, fsize); err != nil { 104 return 105 } 106 default: 107 return nil, ErrUnsupportedIoType 108 } 109 110 lf.IoSelector = selector 111 return 112 } 113 114 // ReadLogEntry read a LogEntry from log file at offset. 115 // It returns a LogEntry, entry size and an error, if any. 116 // If offset is invalid, the err is io.EOF. 117 func (lf *LogFile) ReadLogEntry(offset int64) (*LogEntry, int64, error) { 118 // read entry header. 119 headerBuf, err := lf.readBytes(offset, MaxHeaderSize) 120 if err != nil { 121 return nil, 0, err 122 } 123 header, size := decodeHeader(headerBuf) 124 // the end of entries. 125 if header.crc32 == 0 && header.kSize == 0 && header.vSize == 0 { 126 return nil, 0, ErrEndOfEntry 127 } 128 129 e := &LogEntry{ 130 ExpiredAt: header.expiredAt, 131 Type: header.typ, 132 } 133 kSize, vSize := int64(header.kSize), int64(header.vSize) 134 var entrySize = size + kSize + vSize 135 136 // read entry key and value. 137 if kSize > 0 || vSize > 0 { 138 kvBuf, err := lf.readBytes(offset+size, kSize+vSize) 139 if err != nil { 140 return nil, 0, err 141 } 142 e.Key = kvBuf[:kSize] 143 e.Value = kvBuf[kSize:] 144 } 145 146 // crc32 check. 147 if crc := getEntryCrc(e, headerBuf[crc32.Size:size]); crc != header.crc32 { 148 return nil, 0, ErrInvalidCrc 149 } 150 return e, entrySize, nil 151 } 152 153 // Read a byte slice in the log file at offset, slice length is the given size. 154 // It returns the byte slice and error, if any. 155 func (lf *LogFile) Read(offset int64, size uint32) ([]byte, error) { 156 if size <= 0 { 157 return []byte{}, nil 158 } 159 buf := make([]byte, size) 160 if _, err := lf.IoSelector.Read(buf, offset); err != nil { 161 return nil, err 162 } 163 return buf, nil 164 } 165 166 // Write a byte slice at the end of log file. 167 // Returns an error, if any. 168 func (lf *LogFile) Write(buf []byte) error { 169 if len(buf) <= 0 { 170 return nil 171 } 172 offset := atomic.LoadInt64(&lf.WriteAt) 173 n, err := lf.IoSelector.Write(buf, offset) 174 if err != nil { 175 return err 176 } 177 if n != len(buf) { 178 return ErrWriteSizeNotEqual 179 } 180 181 atomic.AddInt64(&lf.WriteAt, int64(n)) 182 return nil 183 } 184 185 // Sync commits the current contents of the log file to stable storage. 186 func (lf *LogFile) Sync() error { 187 return lf.IoSelector.Sync() 188 } 189 190 // Close current log file. 191 func (lf *LogFile) Close() error { 192 return lf.IoSelector.Close() 193 } 194 195 // Delete delete current log file. 196 // File can`t be retrieved if do this, so use it carefully. 197 func (lf *LogFile) Delete() error { 198 return lf.IoSelector.Delete() 199 } 200 201 func (lf *LogFile) readBytes(offset, n int64) (buf []byte, err error) { 202 buf = make([]byte, n) 203 _, err = lf.IoSelector.Read(buf, offset) 204 return 205 } 206 207 func (lf *LogFile) getLogFileName(path string, fid uint32, ftype FileType) (name string, err error) { 208 if _, ok := FileNamesMap[ftype]; !ok { 209 return "", ErrUnsupportedLogFileType 210 } 211 212 fname := FileNamesMap[ftype] + fmt.Sprintf("%09d", fid) 213 name = filepath.Join(path, fname) 214 return 215 }