github.com/zhiqiangxu/util@v0.0.0-20230112053021-0a7aee056cd5/diskqueue/qfile.go (about) 1 package diskqueue 2 3 import ( 4 "context" 5 "encoding/binary" 6 "fmt" 7 "net" 8 "os" 9 "path/filepath" 10 "sync" 11 "sync/atomic" 12 13 "github.com/zhiqiangxu/util/logger" 14 "github.com/zhiqiangxu/util/mapped" 15 "go.uber.org/zap" 16 ) 17 18 type refCountInterface interface { 19 IncrRef() int32 20 DecrRef() int32 21 } 22 type qfileInterface interface { 23 refCountInterface 24 Shrink() error 25 writeBuffers(buffs *net.Buffers) (int64, error) 26 WrotePosition() int64 27 DoneWrite() int64 28 Commit() int64 29 Read(ctx context.Context, offset int64) ([]byte, error) 30 StreamRead(ctx context.Context, offset int64, ch chan<- StreamBytes) (bool, error) 31 StreamOffsetRead(ctx context.Context, offset int64, offsetCh <-chan int64, ch chan<- StreamBytes) (bool, int64, error) 32 Sync() error 33 Close() error 34 } 35 36 // qfile has no write-write races, but has read-write races 37 type qfile struct { 38 ref int32 39 q *Queue 40 idx int 41 startOffset int64 42 mappedFile *mapped.File 43 notLatest bool 44 readLockedFunc func(ctx context.Context, r *QfileSizeReader) (otherFile bool, startOffset int64, dataBytes []byte, err error) 45 } 46 47 const ( 48 qfSubDir = "qf" 49 qfileDefaultSize = 1024 * 1024 * 1024 50 ) 51 52 var _ qfileInterface = (*qfile)(nil) 53 54 func qfilePath(startOffset int64, conf *Conf) string { 55 return filepath.Join(conf.Directory, qfSubDir, fmt.Sprintf("%020d", startOffset)) 56 } 57 58 func openQfile(q *Queue, idx int, isLatest bool) (qf *qfile, err error) { 59 fm := q.meta.FileMeta(idx) 60 61 qf = &qfile{q: q, idx: idx, startOffset: fm.StartOffset, ref: 1} 62 var pool *sync.Pool 63 if isLatest { 64 pool = q.writeBufferPool() 65 } 66 qf.mappedFile, err = mapped.OpenFile(qfilePath(fm.StartOffset, &q.conf), int64(fm.EndOffset-fm.StartOffset), os.O_RDWR, q.conf.WriteMmap, pool) 67 if err != nil { 68 return 69 } 70 qf.init() 71 72 return 73 } 74 75 func createQfile(q *Queue, idx int, startOffset int64) (qf *qfile, err error) { 76 qf = &qfile{q: q, idx: idx, startOffset: startOffset, ref: 1} 77 var pool *sync.Pool 78 if q.conf.EnableWriteBuffer { 79 pool = q.writeBufferPool() 80 } 81 qf.mappedFile, err = mapped.CreateFile(qfilePath(startOffset, &q.conf), q.conf.MaxFileSize, q.conf.WriteMmap, pool) 82 if err != nil { 83 return 84 } 85 qf.init() 86 87 if q.meta.NumFiles() != idx { 88 logger.Instance().Fatal("createQfile idx != NumFiles", zap.Int("NumFiles", q.meta.NumFiles()), zap.Int("idx", idx)) 89 } 90 91 nowNano := NowNano() 92 q.meta.AddFile(FileMeta{StartOffset: startOffset, EndOffset: startOffset, StartTime: nowNano, EndTime: nowNano}) 93 94 return 95 } 96 97 func (qf *qfile) init() { 98 if qf.q.conf.customDecoder { 99 qf.readLockedFunc = qf.readLockedCustom 100 } else { 101 qf.readLockedFunc = qf.readLockedDefault 102 } 103 } 104 105 func (qf *qfile) IncrRef() int32 { 106 return atomic.AddInt32(&qf.ref, 1) 107 } 108 109 func (qf *qfile) DecrRef() (newRef int32) { 110 newRef = atomic.AddInt32(&qf.ref, -1) 111 if newRef > 0 { 112 return 113 } 114 115 err := qf.Close() 116 if err != nil { 117 logger.Instance().Error("qf.Close", zap.Error(err)) 118 } 119 120 err = qf.remove() 121 if err != nil { 122 logger.Instance().Error("qf.remove", zap.Error(err)) 123 } 124 125 return 126 } 127 128 func (qf *qfile) writeBuffers(buffs *net.Buffers) (n int64, err error) { 129 n, err = qf.mappedFile.WriteBuffers(buffs) 130 return 131 // n, err = buffs.WriteTo(qf.mappedFile) 132 // return 133 } 134 135 func (qf *qfile) WrotePosition() int64 { 136 return qf.startOffset + qf.mappedFile.GetWrotePosition() 137 } 138 139 func (qf *qfile) DoneWrite() int64 { 140 return qf.startOffset + qf.mappedFile.DoneWrite() 141 } 142 143 func (qf *qfile) Commit() int64 { 144 return qf.startOffset + qf.mappedFile.Commit() 145 } 146 147 // isLatest can be called concurrently :) 148 func (qf *qfile) isLatest() bool { 149 if qf.notLatest { 150 return false 151 } 152 isLatest := qf.idx == qf.q.meta.NumFiles()-1 153 if !isLatest { 154 qf.notLatest = true 155 } 156 return isLatest 157 } 158 159 func (qf *qfile) readLockedCustom(ctx context.Context, r *QfileSizeReader) (otherFile bool, startOffset int64, dataBytes []byte, err error) { 160 161 startOffset = r.NextOffset() 162 otherFile, dataBytes, err = qf.q.conf.CustomDecoder(ctx, r) 163 if err == mapped.ErrReadBeyond && !qf.isLatest() { 164 otherFile = true 165 } 166 return 167 } 168 169 func (qf *qfile) readLockedDefault(ctx context.Context, r *QfileSizeReader) (otherFile bool, startOffset int64, dataBytes []byte, err error) { 170 171 startOffset = r.NextOffset() 172 var sizeBytes [sizeLength]byte 173 err = r.Read(ctx, sizeBytes[:]) 174 if err != nil { 175 if err == mapped.ErrReadBeyond && !qf.isLatest() { 176 otherFile = true 177 } 178 return 179 } 180 181 size := int(binary.BigEndian.Uint32(sizeBytes[:])) 182 if size > qf.q.conf.MaxMsgSize { 183 err = errInvalidOffset 184 return 185 } 186 187 dataBytes = make([]byte, size) 188 err = r.Read(ctx, dataBytes) 189 return 190 } 191 192 func (qf *qfile) calcFileOffset(offset int64) (fileOffset int64, err error) { 193 fileOffset = offset - qf.startOffset 194 if fileOffset < 0 { 195 logger.Instance().Error("calcFileOffset negative fileOffset", zap.Int64("offset", offset), zap.Int64("startOffset", qf.startOffset)) 196 err = errInvalidOffset 197 return 198 } 199 200 return 201 } 202 203 func (qf *qfile) Read(ctx context.Context, offset int64) (data []byte, err error) { 204 fileOffset, err := qf.calcFileOffset(offset) 205 if err != nil { 206 return 207 } 208 209 qf.mappedFile.RLock() 210 defer qf.mappedFile.RUnlock() 211 212 r := qf.getSizeReader(fileOffset) 213 _, _, data, err = qf.readLockedFunc(ctx, r) 214 qf.putSizeReader(r) 215 return 216 } 217 218 var sizeReaderPool = sync.Pool{ 219 New: func() interface{} { 220 return &QfileSizeReader{} 221 }, 222 } 223 224 func (qf *qfile) getSizeReader(fileOffset int64) *QfileSizeReader { 225 r := sizeReaderPool.Get().(*QfileSizeReader) 226 r.qf = qf 227 r.fileOffset = fileOffset 228 r.isLatest = qf.isLatest() 229 return r 230 } 231 232 func (qf *qfile) putSizeReader(r *QfileSizeReader) { 233 r.qf = nil 234 sizeReaderPool.Put(r) 235 } 236 237 // when StreamRead returns , err is guaranteed not nil 238 func (qf *qfile) StreamRead(ctx context.Context, offset int64, ch chan<- StreamBytes) (otherFile bool, err error) { 239 fileOffset, err := qf.calcFileOffset(offset) 240 if err != nil { 241 logger.Instance().Fatal("calcFileOffset err", zap.Int64("offset", offset), zap.Int64("startOffset", qf.startOffset)) 242 return 243 } 244 245 qf.mappedFile.RLock() 246 defer qf.mappedFile.RUnlock() 247 248 r := qf.getSizeReader(fileOffset) 249 defer qf.putSizeReader(r) 250 251 var ( 252 dataBytes []byte 253 startOffset int64 254 ) 255 256 for { 257 258 otherFile, startOffset, dataBytes, err = qf.readLockedFunc(ctx, r) 259 if err != nil { 260 return 261 } 262 263 select { 264 case ch <- StreamBytes{Bytes: dataBytes, Offset: startOffset}: 265 case <-ctx.Done(): 266 err = ctx.Err() 267 return 268 } 269 270 } 271 272 } 273 274 func (qf *qfile) StreamOffsetRead(ctx context.Context, offset int64, offsetCh <-chan int64, ch chan<- StreamBytes) (otherFile bool, lastOffset int64, err error) { 275 fileOffset, err := qf.calcFileOffset(offset) 276 if err != nil { 277 logger.Instance().Fatal("calcFileOffset err", zap.Int64("offset", offset), zap.Int64("startOffset", qf.startOffset)) 278 return 279 } 280 281 qf.mappedFile.RLock() 282 defer qf.mappedFile.RUnlock() 283 284 r := qf.getSizeReader(fileOffset) 285 defer func() { 286 lastOffset = r.fileOffset + qf.startOffset 287 qf.putSizeReader(r) 288 }() 289 290 var ( 291 dataBytes []byte 292 nextOffset int64 293 ok bool 294 startOffset int64 295 ) 296 297 for { 298 299 otherFile, startOffset, dataBytes, err = qf.readLockedFunc(ctx, r) 300 if err != nil { 301 return 302 } 303 304 select { 305 case ch <- StreamBytes{Bytes: dataBytes, Offset: startOffset}: 306 case <-ctx.Done(): 307 err = ctx.Err() 308 return 309 } 310 311 select { 312 case nextOffset, ok = <-offsetCh: 313 if !ok { 314 err = errOffsetChClosed 315 return 316 } 317 r.fileOffset = nextOffset - qf.startOffset 318 if r.fileOffset < 0 { 319 err = errInvalidOffset 320 otherFile = true 321 return 322 } 323 case <-ctx.Done(): 324 err = ctx.Err() 325 return 326 } 327 328 } 329 } 330 331 func (qf *qfile) Shrink() error { 332 return qf.mappedFile.Shrink() 333 } 334 335 func (qf *qfile) Sync() error { 336 return qf.mappedFile.Sync() 337 } 338 339 func (qf *qfile) Close() error { 340 return qf.mappedFile.Close() 341 } 342 343 func (qf *qfile) remove() (err error) { 344 err = qf.mappedFile.Remove() 345 return 346 }