gitee.com/lh-her-team/common@v1.5.1/wal/wal.go (about) 1 package wal 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/binary" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "sync" 16 "unicode/utf8" 17 "unsafe" 18 19 "github.com/tidwall/gjson" 20 "github.com/tidwall/tinylru" 21 ) 22 23 var ( 24 // ErrCorrupt is returns when the log is corrupt. 25 ErrCorrupt = errors.New("log corrupt") 26 27 // ErrClosed is returned when an operation cannot be completed because 28 // the log is closed. 29 ErrClosed = errors.New("log closed") 30 31 // ErrNotFound is returned when an entry is not found. 32 ErrNotFound = errors.New("not found") 33 34 // ErrOutOfOrder is returned from Write() when the index is not equal to 35 // LastIndex()+1. It's required that log monotonically grows by one and has 36 // no gaps. Thus, the series 10,11,12,13,14 is valid, but 10,11,13,14 is 37 // not because there's a gap between 11 and 13. Also, 10,12,11,13 is not 38 // valid because 12 and 11 are out of order. 39 ErrOutOfOrder = errors.New("out of order") 40 41 // ErrOutOfRange is returned from TruncateFront() and TruncateBack() when 42 // the index not in the range of the log's first and last index. Or, this 43 // may be returned when the caller is attempting to remove *all* entries; 44 // The log requires that at least one entry exists following a truncate. 45 ErrOutOfRange = errors.New("out of range") 46 ) 47 48 // LogFormat is the format of the log files. 49 type LogFormat byte 50 51 const ( 52 // Binary format writes entries in binary. This is the default and, unless 53 // a good reason otherwise, should be used in production. 54 Binary LogFormat = 0 55 // JSON format writes entries as JSON lines. This causes larger, human 56 // readable files. 57 JSON LogFormat = 1 58 ) 59 60 // Options for Log 61 type Options struct { 62 // NoSync disables fsync after writes. This is less durable and puts the 63 // log at risk of data loss when there's a server crash. 64 NoSync bool 65 // SegmentSize of each segment. This is just a target value, actual size 66 // may differ. Default is 20 MB. 67 SegmentSize int 68 // LogFormat is the format of the log files. Default is Binary. 69 LogFormat LogFormat 70 // SegmentCacheSize is the maximum number of segments that will be held in 71 // memory for caching. Increasing this value may enhance performance for 72 // concurrent read operations. Default is 1 73 SegmentCacheSize int 74 // NoCopy allows for the Read() operation to return the raw underlying data 75 // slice. This is an optimization to help minimize allocations. When this 76 // option is set, do not modify the returned data because it may affect 77 // other Read calls. Default false 78 NoCopy bool 79 } 80 81 // DefaultOptions for Open(). 82 var DefaultOptions = &Options{ 83 NoSync: false, // Fsync after every write 84 SegmentSize: 20971520, // 20 MB log segment files. 85 LogFormat: Binary, // Binary format is small and fast. 86 SegmentCacheSize: 2, // Number of cached in-memory segments 87 NoCopy: false, // Make a new copy of data for every Read call. 88 } 89 90 // Log represents a write ahead log 91 type Log struct { 92 mu sync.RWMutex 93 path string // absolute path to log directory 94 opts Options // log options 95 closed bool // log is closed 96 corrupt bool // log may be corrupt 97 segments []*segment // all known log segments 98 firstIndex uint64 // index of the first entry in log 99 lastIndex uint64 // index of the last entry in log 100 sfile *os.File // tail segment file handle 101 wbatch Batch // reusable write batch 102 scache tinylru.LRU // segment entries cache 103 } 104 105 // segment represents a single segment file. 106 type segment struct { 107 path string // path of segment file 108 index uint64 // first index of segment 109 ebuf []byte // cached entries buffer, storage format of one log entry: checksum|data_size|data 110 epos []bpos // cached entries positions in buffer 111 } 112 113 type bpos struct { 114 pos int // byte position 115 end int // one byte past pos 116 } 117 118 // Open a new write ahead log 119 func Open(path string, opts *Options) (*Log, error) { 120 if opts == nil { 121 opts = DefaultOptions 122 } 123 if opts.SegmentCacheSize <= 0 { 124 opts.SegmentCacheSize = DefaultOptions.SegmentCacheSize 125 } 126 if opts.SegmentSize <= 0 { 127 opts.SegmentSize = DefaultOptions.SegmentSize 128 } 129 var err error 130 path, err = abs(path) 131 if err != nil { 132 return nil, err 133 } 134 l := &Log{path: path, opts: *opts} 135 l.scache.Resize(l.opts.SegmentCacheSize) 136 if err := os.MkdirAll(path, 0777); err != nil { 137 return nil, err 138 } 139 if err := l.load(); err != nil { 140 return nil, err 141 } 142 return l, nil 143 } 144 145 func abs(path string) (string, error) { 146 if path == ":memory:" { 147 return "", errors.New("in-memory log not supported") 148 } 149 return filepath.Abs(path) 150 } 151 152 func (l *Log) pushCache(segIdx int) { 153 _, _, _, v, evicted := 154 l.scache.SetEvicted(segIdx, l.segments[segIdx]) 155 if evicted { 156 // nolint 157 s := v.(*segment) 158 s.ebuf = nil 159 s.epos = nil 160 } 161 } 162 163 // nolint load all the segments. This operation also cleans up any START/END segments. 164 func (l *Log) load() error { 165 fis, err := ioutil.ReadDir(l.path) 166 if err != nil { 167 return err 168 } 169 startIdx := -1 170 endIdx := -1 171 var index uint64 172 // during the restart, wal files are loaded to log.segments 173 for _, fi := range fis { 174 name := fi.Name() 175 if fi.IsDir() || len(name) < 20 { 176 continue 177 } 178 index, err = strconv.ParseUint(name[:20], 10, 64) 179 if err != nil || index == 0 { 180 continue 181 } 182 isStart := len(name) == 26 && strings.HasSuffix(name, ".START") 183 isEnd := len(name) == 24 && strings.HasSuffix(name, ".END") 184 if len(name) == 20 || isStart || isEnd { 185 if isStart { 186 startIdx = len(l.segments) 187 } else if isEnd && endIdx == -1 { 188 endIdx = len(l.segments) 189 } 190 // only load index and path 191 l.segments = append(l.segments, &segment{ 192 index: index, 193 path: filepath.Join(l.path, name), 194 }) 195 } 196 } 197 // for the first time to start 198 if len(l.segments) == 0 { 199 // Create a new log 200 l.segments = append(l.segments, &segment{ 201 index: 1, 202 path: filepath.Join(l.path, segmentName(1)), 203 }) 204 l.firstIndex = 1 205 l.lastIndex = 0 206 l.sfile, err = os.Create(l.segments[0].path) 207 return err 208 } 209 // Open existing log. Clean up log if START of END segments exists. 210 if startIdx != -1 { 211 if endIdx != -1 { 212 // There should not be a START and END at the same time 213 return ErrCorrupt 214 } 215 // Delete all files leading up to START 216 for i := 0; i < startIdx; i++ { 217 if err = os.Remove(l.segments[i].path); err != nil { 218 return err 219 } 220 } 221 l.segments = append([]*segment{}, l.segments[startIdx:]...) 222 // Rename the START segment 223 orgPath := l.segments[0].path 224 finalPath := orgPath[:len(orgPath)-len(".START")] 225 err = os.Rename(orgPath, finalPath) 226 if err != nil { 227 return err 228 } 229 l.segments[0].path = finalPath 230 } 231 if endIdx != -1 { 232 // Delete all files following END 233 for i := len(l.segments) - 1; i > endIdx; i-- { 234 if err = os.Remove(l.segments[i].path); err != nil { 235 return err 236 } 237 } 238 l.segments = append([]*segment{}, l.segments[:endIdx+1]...) 239 if len(l.segments) > 1 && l.segments[len(l.segments)-2].index == 240 l.segments[len(l.segments)-1].index { 241 // remove the segment prior to the END segment because it shares 242 // the same starting index. 243 l.segments[len(l.segments)-2] = l.segments[len(l.segments)-1] 244 l.segments = l.segments[:len(l.segments)-1] 245 } 246 // Rename the END segment 247 orgPath := l.segments[len(l.segments)-1].path 248 finalPath := orgPath[:len(orgPath)-len(".END")] 249 err = os.Rename(orgPath, finalPath) 250 if err != nil { 251 return err 252 } 253 l.segments[len(l.segments)-1].path = finalPath 254 } 255 256 // process restarting 257 l.firstIndex = l.segments[0].index 258 // Open the last segment for appending 259 lseg := l.segments[len(l.segments)-1] 260 l.sfile, err = os.OpenFile(lseg.path, os.O_WRONLY, 0666) 261 if err != nil { 262 return err 263 } 264 if _, err = l.sfile.Seek(0, 2); err != nil { 265 return err 266 } 267 // Customize part start 268 // Load the last segment, only load uncorrupted log entries 269 if err = l.loadSegmentEntriesForRestarting(lseg); err != nil { 270 return err 271 } 272 // Customize part end 273 l.lastIndex = lseg.index + uint64(len(lseg.epos)) - 1 274 return nil 275 } 276 277 // segmentName returns a 20-byte textual representation of an index 278 // for lexical ordering. This is used for the file names of log segments. 279 func segmentName(index uint64) string { 280 return fmt.Sprintf("%020d", index) 281 } 282 283 // Close the log. 284 func (l *Log) Close() error { 285 l.mu.Lock() 286 defer l.mu.Unlock() 287 if l.closed { 288 if l.corrupt { 289 return ErrCorrupt 290 } 291 return ErrClosed 292 } 293 if err := l.sfile.Sync(); err != nil { 294 return err 295 } 296 if err := l.sfile.Close(); err != nil { 297 return err 298 } 299 l.closed = true 300 if l.corrupt { 301 return ErrCorrupt 302 } 303 return nil 304 } 305 306 // Write an entry to the log. 307 func (l *Log) Write(index uint64, data []byte) error { 308 l.mu.Lock() 309 defer l.mu.Unlock() 310 if l.corrupt { 311 return ErrCorrupt 312 } else if l.closed { 313 return ErrClosed 314 } 315 l.wbatch.Clear() 316 l.wbatch.Write(index, data) 317 return l.writeBatch(&l.wbatch) 318 } 319 320 func (l *Log) appendEntry(dst []byte, index uint64, data []byte) (out []byte, 321 epos bpos) { 322 if l.opts.LogFormat == JSON { 323 return appendJSONEntry(dst, index, data) 324 } 325 return appendBinaryEntry(dst, data) 326 } 327 328 // Cycle the old segment for a new segment. 329 func (l *Log) cycle() error { 330 if err := l.sfile.Sync(); err != nil { 331 return err 332 } 333 if err := l.sfile.Close(); err != nil { 334 return err 335 } 336 // cache the previous segment 337 l.pushCache(len(l.segments) - 1) 338 s := &segment{ 339 index: l.lastIndex + 1, 340 path: filepath.Join(l.path, segmentName(l.lastIndex+1)), 341 } 342 var err error 343 l.sfile, err = os.Create(s.path) 344 if err != nil { 345 return err 346 } 347 l.segments = append(l.segments, s) 348 return nil 349 } 350 351 func appendJSONEntry(dst []byte, index uint64, data []byte) (out []byte, 352 epos bpos) { 353 // {"index":number,"checksum":checksum,"data":string} 354 mark := len(dst) 355 dst = append(dst, `{"index":"`...) 356 dst = strconv.AppendUint(dst, index, 10) 357 // Customize part start 358 dst = append(dst, `","checksum":"`...) 359 dst = strconv.AppendUint(dst, uint64(NewCRC(data).Value()), 10) // checksum is calculated by original data 360 // Customize part end 361 dst = append(dst, `","data":`...) 362 dst = appendJSONData(dst, data) 363 dst = append(dst, '}', '\n') 364 return dst, bpos{mark, len(dst)} 365 } 366 367 func appendJSONData(dst []byte, s []byte) []byte { 368 if utf8.Valid(s) { 369 b, _ := json.Marshal(*(*string)(unsafe.Pointer(&s))) 370 dst = append(dst, '"', '+') 371 return append(dst, b[1:]...) 372 } 373 dst = append(dst, '"', '$') 374 dst = append(dst, base64.URLEncoding.EncodeToString(s)...) 375 return append(dst, '"') 376 } 377 378 func appendBinaryEntry(dst []byte, data []byte) (out []byte, epos bpos) { 379 // checksum + data_size + data 380 pos := len(dst) 381 // Customize part start 382 dst = appendChecksum(dst, NewCRC(data).Value()) 383 // Customize part end 384 dst = appendUvarint(dst, uint64(len(data))) 385 dst = append(dst, data...) 386 return dst, bpos{pos, len(dst)} 387 } 388 389 // Customize part start 390 func appendChecksum(dst []byte, checksum uint32) []byte { 391 dst = append(dst, []byte("0000")...) 392 binary.LittleEndian.PutUint32(dst[len(dst)-4:], checksum) 393 return dst 394 } 395 396 // Customize part end 397 398 func appendUvarint(dst []byte, x uint64) []byte { 399 var buf [10]byte 400 n := binary.PutUvarint(buf[:], x) 401 dst = append(dst, buf[:n]...) 402 return dst 403 } 404 405 // Batch of entries. Used to write multiple entries at once using WriteBatch(). 406 type Batch struct { 407 entries []batchEntry 408 datas []byte 409 } 410 411 type batchEntry struct { 412 index uint64 413 size int 414 } 415 416 // Write an entry to the batch 417 func (b *Batch) Write(index uint64, data []byte) { 418 b.entries = append(b.entries, batchEntry{index, len(data)}) 419 b.datas = append(b.datas, data...) 420 } 421 422 // Clear the batch for reuse. 423 func (b *Batch) Clear() { 424 b.entries = b.entries[:0] 425 b.datas = b.datas[:0] 426 } 427 428 // WriteBatch writes the entries in the batch to the log in the order that they 429 // were added to the batch. The batch is cleared upon a successful return. 430 func (l *Log) WriteBatch(b *Batch) error { 431 l.mu.Lock() 432 defer l.mu.Unlock() 433 if l.corrupt { 434 return ErrCorrupt 435 } else if l.closed { 436 return ErrClosed 437 } 438 if len(b.entries) == 0 { 439 return nil 440 } 441 return l.writeBatch(b) 442 } 443 444 func (l *Log) writeBatch(b *Batch) error { 445 // check that all indexes in batch are sane 446 for i := 0; i < len(b.entries); i++ { 447 if b.entries[i].index != l.lastIndex+uint64(i+1) { 448 return fmt.Errorf(fmt.Sprintf("out of order, b.entries[%d].index: %d and l.lastIndex+uint64(%d+1): %d", 449 i, b.entries[i].index, i, l.lastIndex+uint64(i+1))) //ErrOutOfOrder 450 } 451 } 452 // load the tail segment 453 s := l.segments[len(l.segments)-1] 454 if len(s.ebuf) > l.opts.SegmentSize { 455 // tail segment has reached capacity. Close it and create a new one. 456 if err := l.cycle(); err != nil { 457 return err 458 } 459 s = l.segments[len(l.segments)-1] 460 } 461 mark := len(s.ebuf) 462 datas := b.datas 463 for i := 0; i < len(b.entries); i++ { 464 data := datas[:b.entries[i].size] 465 var epos bpos 466 s.ebuf, epos = l.appendEntry(s.ebuf, b.entries[i].index, data) 467 s.epos = append(s.epos, epos) 468 if len(s.ebuf) >= l.opts.SegmentSize { 469 // segment has reached capacity, cycle now 470 if _, err := l.sfile.Write(s.ebuf[mark:]); err != nil { 471 return err 472 } 473 l.lastIndex = b.entries[i].index 474 if err := l.cycle(); err != nil { 475 return err 476 } 477 s = l.segments[len(l.segments)-1] 478 mark = 0 479 } 480 datas = datas[b.entries[i].size:] 481 } 482 if len(s.ebuf)-mark > 0 { 483 if _, err := l.sfile.Write(s.ebuf[mark:]); err != nil { 484 return err 485 } 486 l.lastIndex = b.entries[len(b.entries)-1].index 487 } 488 if !l.opts.NoSync { 489 if err := l.sfile.Sync(); err != nil { 490 return err 491 } 492 } 493 b.Clear() 494 return nil 495 } 496 497 // FirstIndex returns the index of the first entry in the log. Returns zero 498 // when log has no entries. 499 func (l *Log) FirstIndex() (index uint64, err error) { 500 l.mu.RLock() 501 defer l.mu.RUnlock() 502 if l.corrupt { 503 return 0, ErrCorrupt 504 } else if l.closed { 505 return 0, ErrClosed 506 } 507 // We check the lastIndex for zero because the firstIndex is always one or 508 // more, even when there's no entries 509 if l.lastIndex == 0 { 510 return 0, nil 511 } 512 return l.firstIndex, nil 513 } 514 515 // LastIndex returns the index of the last entry in the log. Returns zero when 516 // log has no entries. 517 func (l *Log) LastIndex() (index uint64, err error) { 518 l.mu.RLock() 519 defer l.mu.RUnlock() 520 if l.corrupt { 521 return 0, ErrCorrupt 522 } else if l.closed { 523 return 0, ErrClosed 524 } 525 if l.lastIndex == 0 { 526 return 0, nil 527 } 528 return l.lastIndex, nil 529 } 530 531 // findSegment performs a bsearch on the segments 532 func (l *Log) findSegment(index uint64) int { 533 i, j := 0, len(l.segments) 534 for i < j { 535 h := i + (j-i)/2 536 if index >= l.segments[h].index { 537 i = h + 1 538 } else { 539 j = h 540 } 541 } 542 return i - 1 543 } 544 545 // loadSegmentEntries loads ebuf and epos in the segment for normal 546 func (l *Log) loadSegmentEntries(s *segment) error { 547 data, err := ioutil.ReadFile(s.path) 548 if err != nil { 549 return err 550 } 551 ebuf := data 552 var epos []bpos 553 var pos int 554 for exidx := s.index; len(data) > 0; exidx++ { 555 var n int 556 if l.opts.LogFormat == JSON { 557 n, err = loadNextJSONEntry(data) 558 } else { 559 n, err = loadNextBinaryEntry(data) 560 } 561 if err != nil { 562 return err 563 } 564 data = data[n:] 565 epos = append(epos, bpos{pos, pos + n}) 566 pos += n 567 } 568 s.ebuf = ebuf 569 s.epos = epos 570 return nil 571 } 572 573 // loadSegmentEntriesForRestarting loads ebuf and epos in the segment when restarting 574 func (l *Log) loadSegmentEntriesForRestarting(s *segment) error { 575 data, err := ioutil.ReadFile(s.path) 576 if err != nil { 577 return err 578 } 579 ebuf := data 580 var epos []bpos 581 var pos int 582 for exidx := s.index; len(data) > 0; exidx++ { 583 var n int 584 if l.opts.LogFormat == JSON { 585 n, err = loadNextJSONEntry(data) 586 } else { 587 n, err = loadNextBinaryEntry(data) 588 } 589 // if there are corrupted log entries, the corrupted and subsequent data are discarded 590 if err != nil { 591 break 592 } 593 data = data[n:] 594 epos = append(epos, bpos{pos, pos + n}) 595 pos += n 596 // load uncorrupted data 597 s.ebuf = ebuf[0:pos] 598 s.epos = epos 599 } 600 return nil 601 } 602 603 func loadNextJSONEntry(data []byte) (n int, err error) { 604 // {"index":number,"checksum":checksum,"data":string} 605 idx := bytes.IndexByte(data, '\n') 606 if idx == -1 { 607 return 0, ErrCorrupt 608 } 609 line := data[:idx] 610 dres := gjson.Get(*(*string)(unsafe.Pointer(&line)), "data") 611 if dres.Type != gjson.String { 612 return 0, ErrCorrupt 613 } 614 // Customize part start 615 // verify checksum 616 cs := gjson.Get(*(*string)(unsafe.Pointer(&line)), "checksum").String() 617 // Customize part end 618 dt := gjson.Get(*(*string)(unsafe.Pointer(&line)), "data").String() 619 if cs != strconv.FormatUint(uint64(NewCRC([]byte(dt[1:])).Value()), 10) { 620 return 0, ErrCorrupt 621 } 622 return idx + 1, nil 623 } 624 625 func loadNextBinaryEntry(data []byte) (n int, err error) { 626 // Customize part start 627 // checksum + data_size + data 628 // checksum read 629 checksum := binary.LittleEndian.Uint32(data[:4]) 630 // binary read 631 data = data[4:] 632 // Customize part end 633 size, n := binary.Uvarint(data) 634 if n <= 0 { 635 return 0, ErrCorrupt 636 } 637 if uint64(len(data)-n) < size { 638 return 0, ErrCorrupt 639 } 640 // Customize part start 641 // verify checksum 642 if checksum != NewCRC(data[n:uint64(n)+size]).Value() { 643 return 0, ErrCorrupt 644 } 645 return 4 + n + int(size), nil 646 // Customize part end 647 } 648 649 // loadSegment loads the segment entries into memory, pushes it to the front 650 // of the lru cache, and returns it. 651 func (l *Log) loadSegment(index uint64) (*segment, error) { 652 // check the last segment first. 653 lseg := l.segments[len(l.segments)-1] 654 if index >= lseg.index { 655 return lseg, nil 656 } 657 // check the most recent cached segment 658 var rseg *segment 659 l.scache.Range(func(_, v interface{}) bool { 660 // nolint 661 s := v.(*segment) 662 if index >= s.index && index < s.index+uint64(len(s.epos)) { 663 rseg = s 664 } 665 return false 666 }) 667 if rseg != nil { 668 return rseg, nil 669 } 670 // find in the segment array 671 idx := l.findSegment(index) 672 s := l.segments[idx] 673 if len(s.epos) == 0 { 674 // load the entries from cache 675 if err := l.loadSegmentEntries(s); err != nil { 676 return nil, err 677 } 678 } 679 // push the segment to the front of the cache 680 l.pushCache(idx) 681 return s, nil 682 } 683 684 // Read an entry from the log. Returns a byte slice containing the data entry. 685 func (l *Log) Read(index uint64) (data []byte, err error) { 686 l.mu.RLock() 687 defer l.mu.RUnlock() 688 if l.corrupt { 689 return nil, ErrCorrupt 690 } else if l.closed { 691 return nil, ErrClosed 692 } 693 if index == 0 || index < l.firstIndex || index > l.lastIndex { 694 return nil, ErrNotFound 695 } 696 s, err := l.loadSegment(index) 697 if err != nil { 698 return nil, err 699 } 700 epos := s.epos[index-s.index] 701 edata := s.ebuf[epos.pos:epos.end] 702 if l.opts.LogFormat == JSON { 703 return readJSON(edata) 704 } 705 // Customize part start 706 // checksum read 707 checksum := binary.LittleEndian.Uint32(edata[:4]) 708 // binary read 709 edata = edata[4:] 710 // Customize part end 711 size, n := binary.Uvarint(edata) 712 if n <= 0 { 713 return nil, ErrCorrupt 714 } 715 if uint64(len(edata)-n) < size { 716 return nil, ErrCorrupt 717 } 718 // Customize part start 719 if checksum != NewCRC(edata[n:]).Value() { 720 return nil, ErrCorrupt 721 } 722 // Customize part end 723 if l.opts.NoCopy { 724 data = edata[n : uint64(n)+size] 725 } else { 726 data = make([]byte, size) 727 copy(data, edata[n:]) 728 } 729 return data, nil 730 } 731 732 //go:noinline 733 func readJSON(edata []byte) ([]byte, error) { 734 var data []byte 735 // Customize part start 736 cs := gjson.Get(*(*string)(unsafe.Pointer(&edata)), "checksum").String() 737 // Customize part end 738 s := gjson.Get(*(*string)(unsafe.Pointer(&edata)), "data").String() 739 if len(s) > 0 && s[0] == '$' { 740 var err error 741 data, err = base64.URLEncoding.DecodeString(s[1:]) 742 if err != nil { 743 return nil, ErrCorrupt 744 } 745 // Customize part start 746 if cs != strconv.FormatUint(uint64(NewCRC(data).Value()), 10) { 747 return nil, ErrCorrupt 748 } 749 // Customize part end 750 } else if len(s) > 0 && s[0] == '+' { 751 data = make([]byte, len(s[1:])) 752 copy(data, s[1:]) 753 // Customize part start 754 if cs != strconv.FormatUint(uint64(NewCRC(data).Value()), 10) { 755 return nil, ErrCorrupt 756 } 757 // Customize part end 758 } else { 759 return nil, ErrCorrupt 760 } 761 return data, nil 762 } 763 764 // ClearCache clears the segment cache 765 func (l *Log) ClearCache() error { 766 l.mu.Lock() 767 defer l.mu.Unlock() 768 if l.corrupt { 769 return ErrCorrupt 770 } else if l.closed { 771 return ErrClosed 772 } 773 l.clearCache() 774 return nil 775 } 776 777 func (l *Log) clearCache() { 778 l.scache.Range(func(_, v interface{}) bool { 779 // nolint 780 s := v.(*segment) 781 s.ebuf = nil 782 s.epos = nil 783 return true 784 }) 785 l.scache = tinylru.LRU{} 786 l.scache.Resize(l.opts.SegmentCacheSize) 787 } 788 789 // TruncateFront truncates the front of the log by removing all entries that 790 // are before the provided `index`. In other words the entry at 791 // `index` becomes the first entry in the log. 792 func (l *Log) TruncateFront(index uint64) error { 793 l.mu.Lock() 794 defer l.mu.Unlock() 795 if l.corrupt { 796 return ErrCorrupt 797 } else if l.closed { 798 return ErrClosed 799 } 800 return l.truncateFront(index) 801 } 802 803 // nolint 804 func (l *Log) truncateFront(index uint64) (err error) { 805 if index == 0 || l.lastIndex == 0 || 806 index < l.firstIndex || index > l.lastIndex { 807 return ErrOutOfRange 808 } 809 if index == l.firstIndex { 810 // nothing to truncate 811 return nil 812 } 813 segIdx := l.findSegment(index) 814 var s *segment 815 s, err = l.loadSegment(index) 816 if err != nil { 817 return err 818 } 819 epos := s.epos[index-s.index:] 820 ebuf := s.ebuf[epos[0].pos:] 821 // Create a temp file contains the truncated segment. 822 tempName := filepath.Join(l.path, "TEMP") 823 err = func() error { 824 var f *os.File 825 f, err = os.Create(tempName) 826 if err != nil { 827 return err 828 } 829 defer f.Close() 830 if _, err = f.Write(ebuf); err != nil { 831 return err 832 } 833 if err = f.Sync(); err != nil { 834 return err 835 } 836 return f.Close() 837 }() 838 // Rename the TEMP file to it's START file name. 839 startName := filepath.Join(l.path, segmentName(index)+".START") 840 if err = os.Rename(tempName, startName); err != nil { 841 return err 842 } 843 // The log was truncated but still needs some file cleanup. Any errors 844 // following this message will not cause an on-disk data ocorruption, but 845 // may cause an inconsistency with the current program, so we'll return 846 // ErrCorrupt so the the user can attempt a recover by calling Close() 847 // followed by Open(). 848 defer func() { 849 if v := recover(); v != nil { 850 err = ErrCorrupt 851 l.corrupt = true 852 } 853 }() 854 if segIdx == len(l.segments)-1 { 855 // Close the tail segment file 856 if err = l.sfile.Close(); err != nil { 857 return err 858 } 859 } 860 // Delete truncated segment files 861 for i := 0; i <= segIdx; i++ { 862 if err = os.Remove(l.segments[i].path); err != nil { 863 return err 864 } 865 } 866 // Rename the START file to the final truncated segment name. 867 newName := filepath.Join(l.path, segmentName(index)) 868 if err = os.Rename(startName, newName); err != nil { 869 return err 870 } 871 s.path = newName 872 s.index = index 873 if segIdx == len(l.segments)-1 { 874 // Reopen the tail segment file 875 if l.sfile, err = os.OpenFile(newName, os.O_WRONLY, 0666); err != nil { 876 return err 877 } 878 var n int64 879 if n, err = l.sfile.Seek(0, 2); err != nil { 880 return err 881 } 882 if n != int64(len(ebuf)) { 883 err = errors.New("invalid seek") 884 return err 885 } 886 // Load the last segment entries 887 if err = l.loadSegmentEntries(s); err != nil { 888 return err 889 } 890 } 891 l.segments = append([]*segment{}, l.segments[segIdx:]...) 892 l.firstIndex = index 893 l.clearCache() 894 return nil 895 } 896 897 // TruncateBack truncates the back of the log by removing all entries that 898 // are after the provided `index`. In other words the entry at `index` 899 // becomes the last entry in the log. 900 func (l *Log) TruncateBack(index uint64) error { 901 l.mu.Lock() 902 defer l.mu.Unlock() 903 if l.corrupt { 904 return ErrCorrupt 905 } else if l.closed { 906 return ErrClosed 907 } 908 return l.truncateBack(index) 909 } 910 911 func (l *Log) truncateBack(index uint64) (err error) { 912 if index == 0 || l.lastIndex == 0 || 913 index < l.firstIndex || index > l.lastIndex { 914 return ErrOutOfRange 915 } 916 if index == l.lastIndex { 917 // nothing to truncate 918 return nil 919 } 920 segIdx := l.findSegment(index) 921 var s *segment 922 s, err = l.loadSegment(index) 923 if err != nil { 924 return err 925 } 926 epos := s.epos[:index-s.index+1] 927 ebuf := s.ebuf[:epos[len(epos)-1].end] 928 // Create a temp file contains the truncated segment. 929 tempName := filepath.Join(l.path, "TEMP") 930 err = func() error { 931 var f *os.File 932 f, err = os.Create(tempName) 933 if err != nil { 934 return err 935 } 936 defer f.Close() 937 if _, err = f.Write(ebuf); err != nil { 938 return err 939 } 940 if err = f.Sync(); err != nil { 941 return err 942 } 943 return f.Close() 944 }() 945 // Rename the TEMP file to it's END file name. 946 endName := filepath.Join(l.path, segmentName(s.index)+".END") 947 if err = os.Rename(tempName, endName); err != nil { 948 return err 949 } 950 // The log was truncated but still needs some file cleanup. Any errors 951 // following this message will not cause an on-disk data ocorruption, but 952 // may cause an inconsistency with the current program, so we'll return 953 // ErrCorrupt so the the user can attempt a recover by calling Close() 954 // followed by Open(). 955 defer func() { 956 if v := recover(); v != nil { 957 err = ErrCorrupt 958 l.corrupt = true 959 } 960 }() 961 // Close the tail segment file 962 if err = l.sfile.Close(); err != nil { 963 return err 964 } 965 // Delete truncated segment files 966 for i := segIdx; i < len(l.segments); i++ { 967 if err = os.Remove(l.segments[i].path); err != nil { 968 return err 969 } 970 } 971 // Rename the END file to the final truncated segment name. 972 newName := filepath.Join(l.path, segmentName(s.index)) 973 if err = os.Rename(endName, newName); err != nil { 974 return err 975 } 976 // Reopen the tail segment file 977 if l.sfile, err = os.OpenFile(newName, os.O_WRONLY, 0666); err != nil { 978 return err 979 } 980 var n int64 981 n, err = l.sfile.Seek(0, 2) 982 if err != nil { 983 return err 984 } 985 if n != int64(len(ebuf)) { 986 err = errors.New("invalid seek") 987 return err 988 } 989 s.path = newName 990 l.segments = append([]*segment{}, l.segments[:segIdx+1]...) 991 l.lastIndex = index 992 l.clearCache() 993 err = l.loadSegmentEntries(s) 994 if err != nil { 995 return err 996 } 997 return nil 998 } 999 1000 // Sync performs an fsync on the log. This is not necessary when the 1001 // NoSync option is set to false. 1002 func (l *Log) Sync() error { 1003 l.mu.Lock() 1004 defer l.mu.Unlock() 1005 if l.corrupt { 1006 return ErrCorrupt 1007 } else if l.closed { 1008 return ErrClosed 1009 } 1010 return l.sfile.Sync() 1011 } 1012 1013 func must(v interface{}, err error) interface{} { 1014 if err != nil { 1015 panic(err) 1016 } 1017 return v 1018 }