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 }