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  }