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  }