github.com/zhiqiangxu/util@v0.0.0-20230112053021-0a7aee056cd5/mapped/file.go (about) 1 package mapped 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "net" 8 "os" 9 "sync" 10 "sync/atomic" 11 "syscall" 12 "time" 13 14 "github.com/zhiqiangxu/util" 15 "github.com/zhiqiangxu/util/logger" 16 "go.uber.org/zap" 17 ) 18 19 type fileInterface interface { 20 Flags() int 21 Resize(newSize int64) (err error) 22 Write(data []byte) (n int, err error) 23 WriteBuffers(*net.Buffers) (int64, error) 24 GetWrotePosition() int64 25 Read(offset int64, data []byte) (n int, err error) 26 ReadRLocked(offset int64, data []byte) (n int, err error) 27 RLock() 28 RUnlock() 29 Commit() int64 30 DoneWrite() int64 31 MLock() (err error) 32 MUnlock() (err error) 33 IsFull() bool 34 Shrink() (err error) 35 Sync() (err error) 36 LastModified() (t time.Time, err error) 37 Close() (err error) 38 Remove() error 39 MappedBytes() []byte 40 } 41 42 var _ fileInterface = (*File)(nil) 43 44 // File for mmaped file 45 // Write/Resize should be called sequentially 46 // Commit/Write is concurrent safe 47 type File struct { 48 49 // 有些字段仅在可写时有意义,trade some memory for better locality 50 cwmu sync.Mutex 51 wrotePosition int64 52 commitPosition int64 // 仅在有写缓冲的情况使用 53 writeBuffer *bytes.Buffer 54 pool *sync.Pool 55 56 mu sync.RWMutex 57 fileSize int64 58 fileName string 59 fmap []byte 60 flock sync.RWMutex 61 file *os.File 62 flags int 63 wmm bool 64 } 65 66 // OpenFile opens a mmaped file 67 func OpenFile(fileName string, fileSize int64, flags int, wmm bool, pool *sync.Pool) (f *File, err error) { 68 f = &File{fileSize: fileSize, fileName: fileName, flags: flags, wmm: wmm} 69 if flags&(os.O_RDWR|os.O_WRONLY) != 0 { 70 // 写场景可配置缓冲池 71 if pool != nil { 72 f.pool = pool 73 f.writeBuffer = pool.Get().(*bytes.Buffer) 74 } 75 } else { 76 // 只读场景不需要缓冲池 77 if pool != nil { 78 err = errPoolForReadonly 79 return 80 } 81 } 82 83 // 新建,或打开已有文件,取决于flags 84 err = f.init() 85 return 86 } 87 88 // CreateFile creates a mmaped file 89 func CreateFile(fileName string, fileSize int64, wmm bool, pool *sync.Pool) (f *File, err error) { 90 // 新建 91 return OpenFile(fileName, fileSize, os.O_RDWR|os.O_CREATE|os.O_EXCL, wmm, pool) 92 } 93 94 // Flags for get file flags 95 func (f *File) Flags() int { 96 return f.flags 97 } 98 99 var ( 100 errPoolForReadonly = errors.New("pool for readonly file") 101 // ErrWriteBeyond when write beyond 102 ErrWriteBeyond = errors.New("write beyond") 103 // ErrReadBeyond when read beyond 104 ErrReadBeyond = errors.New("read beyond") 105 ) 106 107 // init仅在构造函数中调用,所以不需要考虑并发 108 func (f *File) init() (err error) { 109 110 f.file, err = os.OpenFile(f.fileName, f.flags, 0600) 111 if err != nil { 112 return 113 } 114 defer func() { 115 if err != nil { 116 // 如果出错,及时释放资源 117 f.file.Close() 118 f.returnWriteBuffer() 119 } 120 }() 121 122 stat, err := f.file.Stat() 123 if err != nil { 124 return 125 } 126 127 // 此时f.fileSize的意义: 128 // 如果是新建,表示期望的大小 129 // 如果是打开已有文件,表示从哪里继续写 130 if f.flags&os.O_EXCL != 0 { 131 // 新建 132 err = f.file.Truncate(f.fileSize) 133 if err != nil { 134 return 135 } 136 } else { 137 // 打开已有文件 138 139 fileSize := stat.Size() 140 offset := f.fileSize 141 142 // offset > 实际大小,写超 143 if offset > fileSize { 144 err = ErrWriteBeyond 145 return 146 } 147 148 // offset <= 实际大小,以实际大小为准 149 f.fileSize = fileSize 150 151 if !f.wmm { 152 _, err = f.file.Seek(offset, io.SeekStart) 153 if err != nil { 154 return 155 } 156 } 157 f.wrotePosition = offset 158 if f.writeBuffer != nil { 159 f.commitPosition = offset 160 } 161 } 162 163 f.fmap, err = util.Mmap(f.file, f.wmm, f.fileSize) 164 if err != nil { 165 return 166 } 167 168 return 169 } 170 171 // MLock for the whole file 172 func (f *File) MLock() (err error) { 173 err = util.MLock(f.fmap, len(f.fmap)) 174 return 175 } 176 177 // MUnlock for the whole file 178 // The memory lock on an address range is automatically removed if the address range is unmapped via munmap(2). 179 func (f *File) MUnlock() (err error) { 180 err = util.MUnlock(f.fmap, len(f.fmap)) 181 return 182 } 183 184 // IsFull tells whether file is full 185 func (f *File) IsFull() bool { 186 return f.wrotePosition >= f.fileSize 187 } 188 189 // Resize will do truncate and remmap 190 func (f *File) Resize(newSize int64) (err error) { 191 f.mu.Lock() 192 defer f.mu.RUnlock() 193 194 if f.fileSize == newSize { 195 return 196 } 197 198 err = f.file.Truncate(newSize) 199 if err != nil { 200 return 201 } 202 203 if f.fmap != nil { 204 err = util.MSync(f.fmap, int64(len(f.fmap)), syscall.MS_SYNC) 205 if err != nil { 206 return 207 } 208 209 err = util.Munmap(f.fmap) 210 if err != nil { 211 return 212 } 213 } 214 215 f.fmap, err = util.Mmap(f.file, f.wmm, newSize) 216 if err != nil { 217 return 218 } 219 220 f.fileSize = newSize 221 if f.wrotePosition > newSize { 222 f.wrotePosition = newSize 223 } 224 return 225 } 226 227 // GetWrotePosition for wrote position 228 func (f *File) GetWrotePosition() int64 { 229 return atomic.LoadInt64(&f.wrotePosition) 230 } 231 232 func (f *File) addAndGetWrotePosition(n int64) (new int64) { 233 new = f.wrotePosition + n 234 atomic.StoreInt64(&f.wrotePosition, new) 235 return 236 } 237 238 func (f *File) getReadPosition() int64 { 239 if f.writeBuffer != nil { 240 return f.getCommitPosition() 241 } 242 243 return f.GetWrotePosition() 244 } 245 246 func (f *File) getCommitPosition() int64 { 247 return atomic.LoadInt64(&f.commitPosition) 248 } 249 250 func (f *File) addAndGetCommitPosition(n int64) (new int64) { 251 new = f.commitPosition + n 252 atomic.StoreInt64(&f.commitPosition, new) 253 return 254 } 255 256 func (f *File) Write(data []byte) (n int, err error) { 257 if f.wrotePosition+int64(len(data)) > f.fileSize { 258 err = ErrWriteBeyond 259 return 260 } 261 262 n, err = f.doWrite(data) 263 return 264 } 265 266 // WriteBuffers for writev 267 func (f *File) WriteBuffers(buffs *net.Buffers) (n int64, err error) { 268 total := 0 269 for _, buf := range *buffs { 270 total += len(buf) 271 } 272 273 if f.wrotePosition+int64(total) > f.fileSize { 274 err = ErrWriteBeyond 275 return 276 } 277 278 if f.writeBuffer != nil { 279 f.cwmu.Lock() 280 n, err = buffs.WriteTo(f.writeBuffer) 281 f.cwmu.Unlock() 282 f.addAndGetWrotePosition(n) 283 284 return 285 } 286 287 // 写共享内存 288 if f.wmm { 289 for _, buf := range *buffs { 290 copy(f.fmap[f.wrotePosition+n:], buf) 291 n += int64(len(buf)) 292 } 293 f.addAndGetWrotePosition(n) 294 nbuf := len(*buffs) 295 *buffs = (*buffs)[nbuf-1:] 296 297 return 298 } 299 300 // 写文件 301 n, err = buffs.WriteTo(f.file) 302 f.addAndGetWrotePosition(n) 303 304 return 305 } 306 307 func (f *File) doWrite(data []byte) (n int, err error) { 308 309 // 写缓冲区 310 if f.writeBuffer != nil { 311 f.cwmu.Lock() 312 n, err = f.writeBuffer.Write(data) 313 f.cwmu.Unlock() 314 f.addAndGetWrotePosition(int64(n)) 315 return 316 } 317 318 // 写共享内存 319 if f.wmm { 320 copy(f.fmap[f.wrotePosition:], data) 321 n = len(data) 322 f.addAndGetWrotePosition(int64(n)) 323 return 324 } 325 326 // 写文件 327 n, err = f.file.Write(data) 328 f.addAndGetWrotePosition(int64(n)) 329 return 330 331 } 332 333 func (f *File) commitLocked() (commitOffset int64) { 334 335 if /*returnWriteBuffer may have been called*/ f.writeBuffer == nil || f.writeBuffer.Len() == 0 { 336 commitOffset = atomic.LoadInt64(&f.wrotePosition) 337 return 338 } 339 340 n := int64(f.writeBuffer.Len()) 341 // 从缓冲区到共享内存或者文件 342 343 if f.wmm { 344 copy(f.fmap[f.commitPosition:], f.writeBuffer.Bytes()) 345 commitOffset = f.addAndGetCommitPosition(n) 346 f.writeBuffer.Reset() 347 return 348 } 349 350 util.TryUntilSuccess(func() bool { 351 _, err := f.writeBuffer.WriteTo(f.file) 352 if err != nil { 353 logger.Instance().Error("Commit WriteTo", zap.Error(err)) 354 return false 355 } 356 return true 357 }, time.Second) 358 359 return f.addAndGetCommitPosition(n) 360 } 361 362 // Commit buffer to os if any 363 func (f *File) Commit() int64 { 364 if f.writeBuffer == nil { 365 return f.GetWrotePosition() 366 } 367 368 f.cwmu.Lock() 369 defer f.cwmu.Unlock() 370 371 return f.commitLocked() 372 373 } 374 375 // DoneWrite = Commit + returnWriteBuffer 376 func (f *File) DoneWrite() (commitOffset int64) { 377 if f.writeBuffer == nil { 378 commitOffset = f.GetWrotePosition() 379 return 380 } 381 382 f.cwmu.Lock() 383 defer f.cwmu.Unlock() 384 385 commitOffset = f.commitLocked() 386 387 f.returnWriteBuffer() 388 return 389 } 390 391 // Shrink resize file to wrote position 392 func (f *File) Shrink() (err error) { 393 f.Commit() 394 395 err = f.Resize(f.wrotePosition) 396 return 397 } 398 399 // Sync from os to disk 400 func (f *File) Sync() (err error) { 401 if f.wmm { 402 err = util.MSync(f.fmap, 0, len(f.fmap)) 403 return 404 } 405 406 err = f.file.Sync() 407 return 408 } 409 410 // LastModified returns last modified time 411 func (f *File) LastModified() (t time.Time, err error) { 412 stat, err := f.file.Stat() 413 if err != nil { 414 return 415 } 416 t = stat.ModTime() 417 return 418 } 419 420 // Read bytes from offset 421 func (f *File) Read(offset int64, data []byte) (int, error) { 422 f.mu.RLock() 423 defer f.mu.RUnlock() 424 425 return f.ReadRLocked(offset, data) 426 } 427 428 // ReadRLocked when already holding the lock 429 func (f *File) ReadRLocked(offset int64, data []byte) (n int, err error) { 430 readPosition := f.getReadPosition() 431 if offset > readPosition { 432 err = ErrReadBeyond 433 return 434 } 435 436 readTo := offset + int64(len(data)) - 1 437 if readTo > readPosition { 438 readTo = readPosition 439 } 440 copy(data, f.fmap[offset:readTo+1]) 441 n = int(readTo - offset + 1) 442 443 return 444 } 445 446 // RLock for read 447 func (f *File) RLock() { 448 f.mu.RLock() 449 } 450 451 // RUnlock for read 452 func (f *File) RUnlock() { 453 f.mu.RUnlock() 454 } 455 456 // Close the mapped file 457 func (f *File) Close() (err error) { 458 err = f.file.Close() 459 if err != nil { 460 return 461 } 462 err = util.Munmap(f.fmap) 463 if err != nil { 464 return 465 } 466 f.fmap = nil 467 468 f.cwmu.Lock() 469 f.returnWriteBuffer() 470 f.cwmu.Unlock() 471 return 472 } 473 474 // Remove the file 475 func (f *File) Remove() error { 476 return os.Remove(f.fileName) 477 } 478 479 // MappedBytes is valid until next Resize 480 func (f *File) MappedBytes() []byte { 481 f.mu.RLock() 482 defer f.mu.RUnlock() 483 484 return f.fmap 485 } 486 487 // return stuff to pools for write mode 488 func (f *File) returnWriteBuffer() { 489 if f.writeBuffer == nil { 490 return 491 } 492 493 f.writeBuffer.Reset() 494 f.pool.Put(f.writeBuffer) 495 f.writeBuffer = nil 496 f.pool = nil 497 498 return 499 }