github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/builder/fscache/fscache.go (about) 1 package fscache // import "github.com/docker/docker/builder/fscache" 2 3 import ( 4 "archive/tar" 5 "crypto/sha256" 6 "encoding/json" 7 "hash" 8 "os" 9 "path/filepath" 10 "sort" 11 "sync" 12 "time" 13 14 "github.com/boltdb/bolt" 15 "github.com/docker/docker/builder" 16 "github.com/docker/docker/builder/remotecontext" 17 "github.com/docker/docker/pkg/archive" 18 "github.com/docker/docker/pkg/directory" 19 "github.com/docker/docker/pkg/stringid" 20 "github.com/docker/docker/pkg/tarsum" 21 "github.com/moby/buildkit/session/filesync" 22 "github.com/pkg/errors" 23 "github.com/sirupsen/logrus" 24 "github.com/tonistiigi/fsutil" 25 "golang.org/x/net/context" 26 "golang.org/x/sync/singleflight" 27 ) 28 29 const dbFile = "fscache.db" 30 const cacheKey = "cache" 31 const metaKey = "meta" 32 33 // Backend is a backing implementation for FSCache 34 type Backend interface { 35 Get(id string) (string, error) 36 Remove(id string) error 37 } 38 39 // FSCache allows syncing remote resources to cached snapshots 40 type FSCache struct { 41 opt Opt 42 transports map[string]Transport 43 mu sync.Mutex 44 g singleflight.Group 45 store *fsCacheStore 46 } 47 48 // Opt defines options for initializing FSCache 49 type Opt struct { 50 Backend Backend 51 Root string // for storing local metadata 52 GCPolicy GCPolicy 53 } 54 55 // GCPolicy defines policy for garbage collection 56 type GCPolicy struct { 57 MaxSize uint64 58 MaxKeepDuration time.Duration 59 } 60 61 // NewFSCache returns new FSCache object 62 func NewFSCache(opt Opt) (*FSCache, error) { 63 store, err := newFSCacheStore(opt) 64 if err != nil { 65 return nil, err 66 } 67 return &FSCache{ 68 store: store, 69 opt: opt, 70 transports: make(map[string]Transport), 71 }, nil 72 } 73 74 // Transport defines a method for syncing remote data to FSCache 75 type Transport interface { 76 Copy(ctx context.Context, id RemoteIdentifier, dest string, cs filesync.CacheUpdater) error 77 } 78 79 // RemoteIdentifier identifies a transfer request 80 type RemoteIdentifier interface { 81 Key() string 82 SharedKey() string 83 Transport() string 84 } 85 86 // RegisterTransport registers a new transport method 87 func (fsc *FSCache) RegisterTransport(id string, transport Transport) error { 88 fsc.mu.Lock() 89 defer fsc.mu.Unlock() 90 if _, ok := fsc.transports[id]; ok { 91 return errors.Errorf("transport %v already exists", id) 92 } 93 fsc.transports[id] = transport 94 return nil 95 } 96 97 // SyncFrom returns a source based on a remote identifier 98 func (fsc *FSCache) SyncFrom(ctx context.Context, id RemoteIdentifier) (builder.Source, error) { // cacheOpt 99 trasportID := id.Transport() 100 fsc.mu.Lock() 101 transport, ok := fsc.transports[id.Transport()] 102 if !ok { 103 fsc.mu.Unlock() 104 return nil, errors.Errorf("invalid transport %s", trasportID) 105 } 106 107 logrus.Debugf("SyncFrom %s %s", id.Key(), id.SharedKey()) 108 fsc.mu.Unlock() 109 sourceRef, err, _ := fsc.g.Do(id.Key(), func() (interface{}, error) { 110 var sourceRef *cachedSourceRef 111 sourceRef, err := fsc.store.Get(id.Key()) 112 if err == nil { 113 return sourceRef, nil 114 } 115 116 // check for unused shared cache 117 sharedKey := id.SharedKey() 118 if sharedKey != "" { 119 r, err := fsc.store.Rebase(sharedKey, id.Key()) 120 if err == nil { 121 sourceRef = r 122 } 123 } 124 125 if sourceRef == nil { 126 var err error 127 sourceRef, err = fsc.store.New(id.Key(), sharedKey) 128 if err != nil { 129 return nil, errors.Wrap(err, "failed to create remote context") 130 } 131 } 132 133 if err := syncFrom(ctx, sourceRef, transport, id); err != nil { 134 sourceRef.Release() 135 return nil, err 136 } 137 if err := sourceRef.resetSize(-1); err != nil { 138 return nil, err 139 } 140 return sourceRef, nil 141 }) 142 if err != nil { 143 return nil, err 144 } 145 ref := sourceRef.(*cachedSourceRef) 146 if ref.src == nil { // failsafe 147 return nil, errors.Errorf("invalid empty pull") 148 } 149 wc := &wrappedContext{Source: ref.src, closer: func() error { 150 ref.Release() 151 return nil 152 }} 153 return wc, nil 154 } 155 156 // DiskUsage reports how much data is allocated by the cache 157 func (fsc *FSCache) DiskUsage() (int64, error) { 158 return fsc.store.DiskUsage() 159 } 160 161 // Prune allows manually cleaning up the cache 162 func (fsc *FSCache) Prune(ctx context.Context) (uint64, error) { 163 return fsc.store.Prune(ctx) 164 } 165 166 // Close stops the gc and closes the persistent db 167 func (fsc *FSCache) Close() error { 168 return fsc.store.Close() 169 } 170 171 func syncFrom(ctx context.Context, cs *cachedSourceRef, transport Transport, id RemoteIdentifier) (retErr error) { 172 src := cs.src 173 if src == nil { 174 src = remotecontext.NewCachableSource(cs.Dir()) 175 } 176 177 if !cs.cached { 178 if err := cs.storage.db.View(func(tx *bolt.Tx) error { 179 b := tx.Bucket([]byte(id.Key())) 180 dt := b.Get([]byte(cacheKey)) 181 if dt != nil { 182 if err := src.UnmarshalBinary(dt); err != nil { 183 return err 184 } 185 } else { 186 return errors.Wrap(src.Scan(), "failed to scan cache records") 187 } 188 return nil 189 }); err != nil { 190 return err 191 } 192 } 193 194 dc := &detectChanges{f: src.HandleChange} 195 196 // todo: probably send a bucket to `Copy` and let it return source 197 // but need to make sure that tx is safe 198 if err := transport.Copy(ctx, id, cs.Dir(), dc); err != nil { 199 return errors.Wrapf(err, "failed to copy to %s", cs.Dir()) 200 } 201 202 if !dc.supported { 203 if err := src.Scan(); err != nil { 204 return errors.Wrap(err, "failed to scan cache records after transfer") 205 } 206 } 207 cs.cached = true 208 cs.src = src 209 return cs.storage.db.Update(func(tx *bolt.Tx) error { 210 dt, err := src.MarshalBinary() 211 if err != nil { 212 return err 213 } 214 b := tx.Bucket([]byte(id.Key())) 215 return b.Put([]byte(cacheKey), dt) 216 }) 217 } 218 219 type fsCacheStore struct { 220 mu sync.Mutex 221 sources map[string]*cachedSource 222 db *bolt.DB 223 fs Backend 224 gcTimer *time.Timer 225 gcPolicy GCPolicy 226 } 227 228 // CachePolicy defines policy for keeping a resource in cache 229 type CachePolicy struct { 230 Priority int 231 LastUsed time.Time 232 } 233 234 func defaultCachePolicy() CachePolicy { 235 return CachePolicy{Priority: 10, LastUsed: time.Now()} 236 } 237 238 func newFSCacheStore(opt Opt) (*fsCacheStore, error) { 239 if err := os.MkdirAll(opt.Root, 0700); err != nil { 240 return nil, err 241 } 242 p := filepath.Join(opt.Root, dbFile) 243 db, err := bolt.Open(p, 0600, nil) 244 if err != nil { 245 return nil, errors.Wrap(err, "failed to open database file %s") 246 } 247 s := &fsCacheStore{db: db, sources: make(map[string]*cachedSource), fs: opt.Backend, gcPolicy: opt.GCPolicy} 248 db.View(func(tx *bolt.Tx) error { 249 return tx.ForEach(func(name []byte, b *bolt.Bucket) error { 250 dt := b.Get([]byte(metaKey)) 251 if dt == nil { 252 return nil 253 } 254 var sm sourceMeta 255 if err := json.Unmarshal(dt, &sm); err != nil { 256 return err 257 } 258 dir, err := s.fs.Get(sm.BackendID) 259 if err != nil { 260 return err // TODO: handle gracefully 261 } 262 source := &cachedSource{ 263 refs: make(map[*cachedSourceRef]struct{}), 264 id: string(name), 265 dir: dir, 266 sourceMeta: sm, 267 storage: s, 268 } 269 s.sources[string(name)] = source 270 return nil 271 }) 272 }) 273 274 s.gcTimer = s.startPeriodicGC(5 * time.Minute) 275 return s, nil 276 } 277 278 func (s *fsCacheStore) startPeriodicGC(interval time.Duration) *time.Timer { 279 var t *time.Timer 280 t = time.AfterFunc(interval, func() { 281 if err := s.GC(); err != nil { 282 logrus.Errorf("build gc error: %v", err) 283 } 284 t.Reset(interval) 285 }) 286 return t 287 } 288 289 func (s *fsCacheStore) Close() error { 290 s.gcTimer.Stop() 291 return s.db.Close() 292 } 293 294 func (s *fsCacheStore) New(id, sharedKey string) (*cachedSourceRef, error) { 295 s.mu.Lock() 296 defer s.mu.Unlock() 297 var ret *cachedSource 298 if err := s.db.Update(func(tx *bolt.Tx) error { 299 b, err := tx.CreateBucket([]byte(id)) 300 if err != nil { 301 return err 302 } 303 backendID := stringid.GenerateRandomID() 304 dir, err := s.fs.Get(backendID) 305 if err != nil { 306 return err 307 } 308 source := &cachedSource{ 309 refs: make(map[*cachedSourceRef]struct{}), 310 id: id, 311 dir: dir, 312 sourceMeta: sourceMeta{ 313 BackendID: backendID, 314 SharedKey: sharedKey, 315 CachePolicy: defaultCachePolicy(), 316 }, 317 storage: s, 318 } 319 dt, err := json.Marshal(source.sourceMeta) 320 if err != nil { 321 return err 322 } 323 if err := b.Put([]byte(metaKey), dt); err != nil { 324 return err 325 } 326 s.sources[id] = source 327 ret = source 328 return nil 329 }); err != nil { 330 return nil, err 331 } 332 return ret.getRef(), nil 333 } 334 335 func (s *fsCacheStore) Rebase(sharedKey, newid string) (*cachedSourceRef, error) { 336 s.mu.Lock() 337 defer s.mu.Unlock() 338 var ret *cachedSource 339 for id, snap := range s.sources { 340 if snap.SharedKey == sharedKey && len(snap.refs) == 0 { 341 if err := s.db.Update(func(tx *bolt.Tx) error { 342 if err := tx.DeleteBucket([]byte(id)); err != nil { 343 return err 344 } 345 b, err := tx.CreateBucket([]byte(newid)) 346 if err != nil { 347 return err 348 } 349 snap.id = newid 350 snap.CachePolicy = defaultCachePolicy() 351 dt, err := json.Marshal(snap.sourceMeta) 352 if err != nil { 353 return err 354 } 355 if err := b.Put([]byte(metaKey), dt); err != nil { 356 return err 357 } 358 delete(s.sources, id) 359 s.sources[newid] = snap 360 return nil 361 }); err != nil { 362 return nil, err 363 } 364 ret = snap 365 break 366 } 367 } 368 if ret == nil { 369 return nil, errors.Errorf("no candidate for rebase") 370 } 371 return ret.getRef(), nil 372 } 373 374 func (s *fsCacheStore) Get(id string) (*cachedSourceRef, error) { 375 s.mu.Lock() 376 defer s.mu.Unlock() 377 src, ok := s.sources[id] 378 if !ok { 379 return nil, errors.Errorf("not found") 380 } 381 return src.getRef(), nil 382 } 383 384 // DiskUsage reports how much data is allocated by the cache 385 func (s *fsCacheStore) DiskUsage() (int64, error) { 386 s.mu.Lock() 387 defer s.mu.Unlock() 388 var size int64 389 390 for _, snap := range s.sources { 391 if len(snap.refs) == 0 { 392 ss, err := snap.getSize() 393 if err != nil { 394 return 0, err 395 } 396 size += ss 397 } 398 } 399 return size, nil 400 } 401 402 // Prune allows manually cleaning up the cache 403 func (s *fsCacheStore) Prune(ctx context.Context) (uint64, error) { 404 s.mu.Lock() 405 defer s.mu.Unlock() 406 var size uint64 407 408 for id, snap := range s.sources { 409 select { 410 case <-ctx.Done(): 411 logrus.Debugf("Cache prune operation cancelled, pruned size: %d", size) 412 // when the context is cancelled, only return current size and nil 413 return size, nil 414 default: 415 } 416 if len(snap.refs) == 0 { 417 ss, err := snap.getSize() 418 if err != nil { 419 return size, err 420 } 421 if err := s.delete(id); err != nil { 422 return size, errors.Wrapf(err, "failed to delete %s", id) 423 } 424 size += uint64(ss) 425 } 426 } 427 return size, nil 428 } 429 430 // GC runs a garbage collector on FSCache 431 func (s *fsCacheStore) GC() error { 432 s.mu.Lock() 433 defer s.mu.Unlock() 434 var size uint64 435 436 cutoff := time.Now().Add(-s.gcPolicy.MaxKeepDuration) 437 var blacklist []*cachedSource 438 439 for id, snap := range s.sources { 440 if len(snap.refs) == 0 { 441 if cutoff.After(snap.CachePolicy.LastUsed) { 442 if err := s.delete(id); err != nil { 443 return errors.Wrapf(err, "failed to delete %s", id) 444 } 445 } else { 446 ss, err := snap.getSize() 447 if err != nil { 448 return err 449 } 450 size += uint64(ss) 451 blacklist = append(blacklist, snap) 452 } 453 } 454 } 455 456 sort.Sort(sortableCacheSources(blacklist)) 457 for _, snap := range blacklist { 458 if size <= s.gcPolicy.MaxSize { 459 break 460 } 461 ss, err := snap.getSize() 462 if err != nil { 463 return err 464 } 465 if err := s.delete(snap.id); err != nil { 466 return errors.Wrapf(err, "failed to delete %s", snap.id) 467 } 468 size -= uint64(ss) 469 } 470 return nil 471 } 472 473 // keep mu while calling this 474 func (s *fsCacheStore) delete(id string) error { 475 src, ok := s.sources[id] 476 if !ok { 477 return nil 478 } 479 if len(src.refs) > 0 { 480 return errors.Errorf("can't delete %s because it has active references", id) 481 } 482 delete(s.sources, id) 483 if err := s.db.Update(func(tx *bolt.Tx) error { 484 return tx.DeleteBucket([]byte(id)) 485 }); err != nil { 486 return err 487 } 488 return s.fs.Remove(src.BackendID) 489 } 490 491 type sourceMeta struct { 492 SharedKey string 493 BackendID string 494 CachePolicy CachePolicy 495 Size int64 496 } 497 498 type cachedSource struct { 499 sourceMeta 500 refs map[*cachedSourceRef]struct{} 501 id string 502 dir string 503 src *remotecontext.CachableSource 504 storage *fsCacheStore 505 cached bool // keep track if cache is up to date 506 } 507 508 type cachedSourceRef struct { 509 *cachedSource 510 } 511 512 func (cs *cachedSource) Dir() string { 513 return cs.dir 514 } 515 516 // hold storage lock before calling 517 func (cs *cachedSource) getRef() *cachedSourceRef { 518 ref := &cachedSourceRef{cachedSource: cs} 519 cs.refs[ref] = struct{}{} 520 return ref 521 } 522 523 // hold storage lock before calling 524 func (cs *cachedSource) getSize() (int64, error) { 525 if cs.sourceMeta.Size < 0 { 526 ss, err := directory.Size(cs.dir) 527 if err != nil { 528 return 0, err 529 } 530 if err := cs.resetSize(ss); err != nil { 531 return 0, err 532 } 533 return ss, nil 534 } 535 return cs.sourceMeta.Size, nil 536 } 537 538 func (cs *cachedSource) resetSize(val int64) error { 539 cs.sourceMeta.Size = val 540 return cs.saveMeta() 541 } 542 func (cs *cachedSource) saveMeta() error { 543 return cs.storage.db.Update(func(tx *bolt.Tx) error { 544 b := tx.Bucket([]byte(cs.id)) 545 dt, err := json.Marshal(cs.sourceMeta) 546 if err != nil { 547 return err 548 } 549 return b.Put([]byte(metaKey), dt) 550 }) 551 } 552 553 func (csr *cachedSourceRef) Release() error { 554 csr.cachedSource.storage.mu.Lock() 555 defer csr.cachedSource.storage.mu.Unlock() 556 delete(csr.cachedSource.refs, csr) 557 if len(csr.cachedSource.refs) == 0 { 558 go csr.cachedSource.storage.GC() 559 } 560 return nil 561 } 562 563 type detectChanges struct { 564 f fsutil.ChangeFunc 565 supported bool 566 } 567 568 func (dc *detectChanges) HandleChange(kind fsutil.ChangeKind, path string, fi os.FileInfo, err error) error { 569 if dc == nil { 570 return nil 571 } 572 return dc.f(kind, path, fi, err) 573 } 574 575 func (dc *detectChanges) MarkSupported(v bool) { 576 if dc == nil { 577 return 578 } 579 dc.supported = v 580 } 581 582 func (dc *detectChanges) ContentHasher() fsutil.ContentHasher { 583 return newTarsumHash 584 } 585 586 type wrappedContext struct { 587 builder.Source 588 closer func() error 589 } 590 591 func (wc *wrappedContext) Close() error { 592 if err := wc.Source.Close(); err != nil { 593 return err 594 } 595 return wc.closer() 596 } 597 598 type sortableCacheSources []*cachedSource 599 600 // Len is the number of elements in the collection. 601 func (s sortableCacheSources) Len() int { 602 return len(s) 603 } 604 605 // Less reports whether the element with 606 // index i should sort before the element with index j. 607 func (s sortableCacheSources) Less(i, j int) bool { 608 return s[i].CachePolicy.LastUsed.Before(s[j].CachePolicy.LastUsed) 609 } 610 611 // Swap swaps the elements with indexes i and j. 612 func (s sortableCacheSources) Swap(i, j int) { 613 s[i], s[j] = s[j], s[i] 614 } 615 616 func newTarsumHash(stat *fsutil.Stat) (hash.Hash, error) { 617 fi := &fsutil.StatInfo{stat} 618 p := stat.Path 619 if fi.IsDir() { 620 p += string(os.PathSeparator) 621 } 622 h, err := archive.FileInfoHeader(p, fi, stat.Linkname) 623 if err != nil { 624 return nil, err 625 } 626 h.Name = p 627 h.Uid = int(stat.Uid) 628 h.Gid = int(stat.Gid) 629 h.Linkname = stat.Linkname 630 if stat.Xattrs != nil { 631 h.Xattrs = make(map[string]string) 632 for k, v := range stat.Xattrs { 633 h.Xattrs[k] = string(v) 634 } 635 } 636 637 tsh := &tarsumHash{h: h, Hash: sha256.New()} 638 tsh.Reset() 639 return tsh, nil 640 } 641 642 // Reset resets the Hash to its initial state. 643 func (tsh *tarsumHash) Reset() { 644 tsh.Hash.Reset() 645 tarsum.WriteV1Header(tsh.h, tsh.Hash) 646 } 647 648 type tarsumHash struct { 649 hash.Hash 650 h *tar.Header 651 }