github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/data/cache/fastcache/file.go (about)

     1  package fastcache
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  
    13  	"github.com/golang/snappy"
    14  )
    15  
    16  // SaveToFile atomically saves cache data to the given filePath using a single
    17  // CPU core.
    18  //
    19  // SaveToFile may be called concurrently with other operations on the cache.
    20  //
    21  // The saved data may be loaded with LoadFromFile*.
    22  //
    23  // See also SaveToFileConcurrent for faster saving to file.
    24  func (c *Cache) SaveToFile(filePath string) error {
    25  	return c.SaveToFileConcurrent(filePath, 1)
    26  }
    27  
    28  // SaveToFileConcurrent saves cache data to the given filePath using concurrency
    29  // CPU cores.
    30  //
    31  // SaveToFileConcurrent may be called concurrently with other operations
    32  // on the cache.
    33  //
    34  // The saved data may be loaded with LoadFromFile*.
    35  //
    36  // See also SaveToFile.
    37  func (c *Cache) SaveToFileConcurrent(filePath string, concurrency int) error {
    38  	// Create dir if it doesn't exist.
    39  	dir := filepath.Dir(filePath)
    40  	if _, err := os.Stat(dir); err != nil {
    41  		if !os.IsNotExist(err) {
    42  			return fmt.Errorf("cannot stat %q: %s", dir, err)
    43  		}
    44  		if err := os.MkdirAll(dir, 0755); err != nil {
    45  			return fmt.Errorf("cannot create dir %q: %s", dir, err)
    46  		}
    47  	}
    48  
    49  	// Save cache data into a temporary directory.
    50  	tmpDir, err := ioutil.TempDir(dir, "fastcache.tmp.")
    51  	if err != nil {
    52  		return fmt.Errorf("cannot create temporary dir inside %q: %s", dir, err)
    53  	}
    54  	defer func() {
    55  		if tmpDir != "" {
    56  			_ = os.RemoveAll(tmpDir)
    57  		}
    58  	}()
    59  	gomaxprocs := runtime.GOMAXPROCS(-1)
    60  	if concurrency <= 0 || concurrency > gomaxprocs {
    61  		concurrency = gomaxprocs
    62  	}
    63  	if err := c.save(tmpDir, concurrency); err != nil {
    64  		return fmt.Errorf("cannot save cache data to temporary dir %q: %s", tmpDir, err)
    65  	}
    66  
    67  	// Remove old filePath contents, since os.Rename may return
    68  	// error if filePath dir exists.
    69  	if err := os.RemoveAll(filePath); err != nil {
    70  		return fmt.Errorf("cannot remove old contents at %q: %s", filePath, err)
    71  	}
    72  	if err := os.Rename(tmpDir, filePath); err != nil {
    73  		return fmt.Errorf("cannot move temporary dir %q to %q: %s", tmpDir, filePath, err)
    74  	}
    75  	tmpDir = ""
    76  	return nil
    77  }
    78  
    79  // LoadFromFile loads cache data from the given filePath.
    80  //
    81  // See SaveToFile* for saving cache data to file.
    82  func LoadFromFile(filePath string) (*Cache, error) {
    83  	return load(filePath, 0)
    84  }
    85  
    86  // LoadFromFileOrNew tries loading cache data from the given filePath.
    87  //
    88  // The function falls back to creating new cache with the given maxBytes
    89  // capacity if error occurs during loading the cache from file.
    90  func LoadFromFileOrNew(filePath string, maxBytes int) *Cache {
    91  	c, err := load(filePath, maxBytes)
    92  	if err == nil {
    93  		return c
    94  	}
    95  	return New(maxBytes)
    96  }
    97  
    98  func (c *Cache) save(dir string, workersCount int) error {
    99  	if err := saveMetadata(c, dir); err != nil {
   100  		return err
   101  	}
   102  
   103  	// Save buckets by workersCount concurrent workers.
   104  	workCh := make(chan int, workersCount)
   105  	results := make(chan error)
   106  	for i := 0; i < workersCount; i++ {
   107  		go func(workerNum int) {
   108  			results <- saveBuckets(c.buckets[:], workCh, dir, workerNum)
   109  		}(i)
   110  	}
   111  	// Feed workers with work
   112  	for i := range c.buckets[:] {
   113  		workCh <- i
   114  	}
   115  	close(workCh)
   116  
   117  	// Read results.
   118  	var err error
   119  	for i := 0; i < workersCount; i++ {
   120  		result := <-results
   121  		if result != nil && err == nil {
   122  			err = result
   123  		}
   124  	}
   125  	return err
   126  }
   127  
   128  func load(filePath string, maxBytes int) (*Cache, error) {
   129  	maxBucketChunks, err := loadMetadata(filePath)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	if maxBytes > 0 {
   134  		maxBucketBytes := uint64((maxBytes + bucketsCount - 1) / bucketsCount)
   135  		expectedBucketChunks := (maxBucketBytes + chunkSize - 1) / chunkSize
   136  		if maxBucketChunks != expectedBucketChunks {
   137  			return nil, fmt.Errorf("cache file %s contains maxBytes=%d; want %d", filePath, maxBytes, expectedBucketChunks*chunkSize*bucketsCount)
   138  		}
   139  	}
   140  
   141  	// Read bucket files from filePath dir.
   142  	d, err := os.Open(filePath)
   143  	if err != nil {
   144  		return nil, fmt.Errorf("cannot open %q: %s", filePath, err)
   145  	}
   146  	defer func() {
   147  		_ = d.Close()
   148  	}()
   149  	fis, err := d.Readdir(-1)
   150  	if err != nil {
   151  		return nil, fmt.Errorf("cannot read files from %q: %s", filePath, err)
   152  	}
   153  	results := make(chan error)
   154  	workersCount := 0
   155  	var c Cache
   156  	for _, fi := range fis {
   157  		fn := fi.Name()
   158  		if fi.IsDir() || !dataFileRegexp.MatchString(fn) {
   159  			continue
   160  		}
   161  		workersCount++
   162  		go func(dataPath string) {
   163  			results <- loadBuckets(c.buckets[:], dataPath, maxBucketChunks)
   164  		}(filePath + "/" + fn)
   165  	}
   166  	err = nil
   167  	for i := 0; i < workersCount; i++ {
   168  		result := <-results
   169  		if result != nil && err == nil {
   170  			err = result
   171  		}
   172  	}
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	// Initialize buckets, which could be missing due to incomplete or corrupted files in the cache.
   177  	// It is better initializing such buckets instead of returning error, since the rest of buckets
   178  	// contain valid data.
   179  	for i := range c.buckets[:] {
   180  		b := &c.buckets[i]
   181  		if len(b.chunks) == 0 {
   182  			b.chunks = make([][]byte, maxBucketChunks)
   183  			b.m = make(map[uint64]uint64)
   184  		}
   185  	}
   186  	return &c, nil
   187  }
   188  
   189  func saveMetadata(c *Cache, dir string) error {
   190  	metadataPath := dir + "/metadata.bin"
   191  	metadataFile, err := os.Create(metadataPath)
   192  	if err != nil {
   193  		return fmt.Errorf("cannot create %q: %s", metadataPath, err)
   194  	}
   195  	defer func() {
   196  		_ = metadataFile.Close()
   197  	}()
   198  	maxBucketChunks := uint64(cap(c.buckets[0].chunks))
   199  	if err := writeUint64(metadataFile, maxBucketChunks); err != nil {
   200  		return fmt.Errorf("cannot write maxBucketChunks=%d to %q: %s", maxBucketChunks, metadataPath, err)
   201  	}
   202  	return nil
   203  }
   204  
   205  func loadMetadata(dir string) (uint64, error) {
   206  	metadataPath := dir + "/metadata.bin"
   207  	metadataFile, err := os.Open(metadataPath)
   208  	if err != nil {
   209  		return 0, fmt.Errorf("cannot open %q: %s", metadataPath, err)
   210  	}
   211  	defer func() {
   212  		_ = metadataFile.Close()
   213  	}()
   214  	maxBucketChunks, err := readUint64(metadataFile)
   215  	if err != nil {
   216  		return 0, fmt.Errorf("cannot read maxBucketChunks from %q: %s", metadataPath, err)
   217  	}
   218  	if maxBucketChunks == 0 {
   219  		return 0, fmt.Errorf("invalid maxBucketChunks=0 read from %q", metadataPath)
   220  	}
   221  	return maxBucketChunks, nil
   222  }
   223  
   224  var dataFileRegexp = regexp.MustCompile(`^data\.\d+\.bin$`)
   225  
   226  func saveBuckets(buckets []bucket, workCh <-chan int, dir string, workerNum int) error {
   227  	dataPath := fmt.Sprintf("%s/data.%d.bin", dir, workerNum)
   228  	dataFile, err := os.Create(dataPath)
   229  	if err != nil {
   230  		return fmt.Errorf("cannot create %q: %s", dataPath, err)
   231  	}
   232  	defer func() {
   233  		_ = dataFile.Close()
   234  	}()
   235  	zw := snappy.NewBufferedWriter(dataFile)
   236  	for bucketNum := range workCh {
   237  		if err := writeUint64(zw, uint64(bucketNum)); err != nil {
   238  			return fmt.Errorf("cannot write bucketNum=%d to %q: %s", bucketNum, dataPath, err)
   239  		}
   240  		if err := buckets[bucketNum].Save(zw); err != nil {
   241  			return fmt.Errorf("cannot save bucket[%d] to %q: %s", bucketNum, dataPath, err)
   242  		}
   243  	}
   244  	if err := zw.Close(); err != nil {
   245  		return fmt.Errorf("cannot close snappy.Writer for %q: %s", dataPath, err)
   246  	}
   247  	return nil
   248  }
   249  
   250  func loadBuckets(buckets []bucket, dataPath string, maxChunks uint64) error {
   251  	dataFile, err := os.Open(dataPath)
   252  	if err != nil {
   253  		return fmt.Errorf("cannot open %q: %s", dataPath, err)
   254  	}
   255  	defer func() {
   256  		_ = dataFile.Close()
   257  	}()
   258  	zr := snappy.NewReader(dataFile)
   259  	for {
   260  		bucketNum, err := readUint64(zr)
   261  		if err == io.EOF {
   262  			// Reached the end of file.
   263  			return nil
   264  		}
   265  		if bucketNum >= uint64(len(buckets)) {
   266  			return fmt.Errorf("unexpected bucketNum read from %q: %d; must be smaller than %d", dataPath, bucketNum, len(buckets))
   267  		}
   268  		if err := buckets[bucketNum].Load(zr, maxChunks); err != nil {
   269  			return fmt.Errorf("cannot load bucket[%d] from %q: %s", bucketNum, dataPath, err)
   270  		}
   271  	}
   272  }
   273  
   274  func (b *bucket) Save(w io.Writer) error {
   275  	b.Clean()
   276  
   277  	b.mu.RLock()
   278  	defer b.mu.RUnlock()
   279  
   280  	// Store b.idx, b.gen and b.m to w.
   281  
   282  	bIdx := b.idx
   283  	bGen := b.gen
   284  	chunksLen := 0
   285  	for _, chunk := range b.chunks {
   286  		if chunk == nil {
   287  			break
   288  		}
   289  		chunksLen++
   290  	}
   291  	kvs := make([]byte, 0, 2*8*len(b.m))
   292  	var u64Buf [8]byte
   293  	for k, v := range b.m {
   294  		binary.LittleEndian.PutUint64(u64Buf[:], k)
   295  		kvs = append(kvs, u64Buf[:]...)
   296  		binary.LittleEndian.PutUint64(u64Buf[:], v)
   297  		kvs = append(kvs, u64Buf[:]...)
   298  	}
   299  
   300  	if err := writeUint64(w, bIdx); err != nil {
   301  		return fmt.Errorf("cannot write b.idx: %s", err)
   302  	}
   303  	if err := writeUint64(w, bGen); err != nil {
   304  		return fmt.Errorf("cannot write b.gen: %s", err)
   305  	}
   306  	if err := writeUint64(w, uint64(len(kvs))/2/8); err != nil {
   307  		return fmt.Errorf("cannot write len(b.m): %s", err)
   308  	}
   309  	if _, err := w.Write(kvs); err != nil {
   310  		return fmt.Errorf("cannot write b.m: %s", err)
   311  	}
   312  
   313  	// Store b.chunks to w.
   314  	if err := writeUint64(w, uint64(chunksLen)); err != nil {
   315  		return fmt.Errorf("cannot write len(b.chunks): %s", err)
   316  	}
   317  	for chunkIdx := 0; chunkIdx < chunksLen; chunkIdx++ {
   318  		chunk := b.chunks[chunkIdx][:chunkSize]
   319  		if _, err := w.Write(chunk); err != nil {
   320  			return fmt.Errorf("cannot write b.chunks[%d]: %s", chunkIdx, err)
   321  		}
   322  	}
   323  
   324  	return nil
   325  }
   326  
   327  func (b *bucket) Load(r io.Reader, maxChunks uint64) error {
   328  	if maxChunks == 0 {
   329  		return fmt.Errorf("the number of chunks per bucket cannot be zero")
   330  	}
   331  	bIdx, err := readUint64(r)
   332  	if err != nil {
   333  		return fmt.Errorf("cannot read b.idx: %s", err)
   334  	}
   335  	bGen, err := readUint64(r)
   336  	if err != nil {
   337  		return fmt.Errorf("cannot read b.gen: %s", err)
   338  	}
   339  	kvsLen, err := readUint64(r)
   340  	if err != nil {
   341  		return fmt.Errorf("cannot read len(b.m): %s", err)
   342  	}
   343  	kvsLen *= 2 * 8
   344  	kvs := make([]byte, kvsLen)
   345  	if _, err := io.ReadFull(r, kvs); err != nil {
   346  		return fmt.Errorf("cannot read b.m: %s", err)
   347  	}
   348  	m := make(map[uint64]uint64, kvsLen/2/8)
   349  	for len(kvs) > 0 {
   350  		k := binary.LittleEndian.Uint64(kvs)
   351  		kvs = kvs[8:]
   352  		v := binary.LittleEndian.Uint64(kvs)
   353  		kvs = kvs[8:]
   354  		m[k] = v
   355  	}
   356  
   357  	maxBytes := maxChunks * chunkSize
   358  	if maxBytes >= maxBucketSize {
   359  		return fmt.Errorf("too big maxBytes=%d; should be smaller than %d", maxBytes, maxBucketSize)
   360  	}
   361  	chunks := make([][]byte, maxChunks)
   362  	chunksLen, err := readUint64(r)
   363  	if err != nil {
   364  		return fmt.Errorf("cannot read len(b.chunks): %s", err)
   365  	}
   366  	if chunksLen > maxChunks {
   367  		return fmt.Errorf("chunksLen=%d cannot exceed maxChunks=%d", chunksLen, maxChunks)
   368  	}
   369  	currChunkIdx := bIdx / chunkSize
   370  	if currChunkIdx > 0 && currChunkIdx >= chunksLen {
   371  		return fmt.Errorf("too big bIdx=%d; should be smaller than %d", bIdx, chunksLen*chunkSize)
   372  	}
   373  	for chunkIdx := uint64(0); chunkIdx < chunksLen; chunkIdx++ {
   374  		chunk := make([]byte, chunkSize)
   375  		//chunk := getChunk()
   376  		chunks[chunkIdx] = chunk
   377  		if _, err := io.ReadFull(r, chunk); err != nil {
   378  			// Free up allocated chunks before returning the error.
   379  			//for _, chunk := range chunks {
   380  			//	if chunk != nil {
   381  			//		putChunk(chunk)
   382  			//	}
   383  			//}
   384  			return fmt.Errorf("cannot read b.chunks[%d]: %s", chunkIdx, err)
   385  		}
   386  	}
   387  	// Adjust len for the chunk pointed by currChunkIdx.
   388  	if chunksLen > 0 {
   389  		chunkLen := bIdx % chunkSize
   390  		chunks[currChunkIdx] = chunks[currChunkIdx][:chunkLen]
   391  	}
   392  
   393  	b.mu.Lock()
   394  	//for _, chunk := range b.chunks {
   395  	//	putChunk(chunk)
   396  	//}
   397  	b.chunks = chunks
   398  	b.m = m
   399  	b.idx = bIdx
   400  	b.gen = bGen
   401  	b.mu.Unlock()
   402  
   403  	return nil
   404  }
   405  
   406  func writeUint64(w io.Writer, u uint64) error {
   407  	var u64Buf [8]byte
   408  	binary.LittleEndian.PutUint64(u64Buf[:], u)
   409  	_, err := w.Write(u64Buf[:])
   410  	return err
   411  }
   412  
   413  func readUint64(r io.Reader) (uint64, error) {
   414  	var u64Buf [8]byte
   415  	if _, err := io.ReadFull(r, u64Buf[:]); err != nil {
   416  		return 0, err
   417  	}
   418  	u := binary.LittleEndian.Uint64(u64Buf[:])
   419  	return u, nil
   420  }