buildkit/cache/refs.go

213 lines
4.2 KiB
Go
Raw Normal View History

package cache
import (
"crypto/rand"
"encoding/hex"
"sync"
"github.com/containerd/containerd/mount"
"github.com/pkg/errors"
"github.com/tonistiigi/buildkit_poc/util/flightcontrol"
"golang.org/x/net/context"
)
const sizeUnknown int64 = -1
type ImmutableRef interface {
Mountable
ID() string
Release() error
Size(ctx context.Context) (int64, error)
// Prepare() / ChainID() / Meta()
}
type MutableRef interface {
Mountable
ID() string
Freeze() (ImmutableRef, error)
ReleaseAndCommit(ctx context.Context) (ImmutableRef, error)
Size(ctx context.Context) (int64, error)
}
type Mountable interface {
Mount() ([]mount.Mount, error)
}
type cacheRecord struct {
mu sync.Mutex
mutable bool
frozen bool
// meta SnapMeta
refs map[*cacheRef]struct{}
id string
cm *cacheManager
parent ImmutableRef
view string
viewMount []mount.Mount
sizeG flightcontrol.Group
size int64
}
// hold manager lock before calling
func (cr *cacheRecord) ref() *cacheRef {
ref := &cacheRef{cacheRecord: cr}
cr.refs[ref] = struct{}{}
return ref
}
func (cr *cacheRecord) Size(ctx context.Context) (int64, error) {
// this expects that usage() is implemented lazily
s, err, _ := cr.sizeG.Do(ctx, cr.id, func(ctx context.Context) (interface{}, error) {
cr.mu.Lock()
s := cr.size
cr.mu.Unlock()
if s != sizeUnknown {
return s, nil
}
usage, err := cr.cm.ManagerOpt.Snapshotter.Usage(ctx, cr.id)
if err != nil {
return s, errors.Wrapf(err, "failed to get usage for %s", cr.id)
}
cr.mu.Lock()
cr.size = s
cr.mu.Unlock()
return usage.Size, nil
})
return s.(int64), err
}
type cacheRef struct {
*cacheRecord
}
func (sr *cacheRef) Mount() ([]mount.Mount, error) {
sr.mu.Lock()
defer sr.mu.Unlock()
if sr.mutable {
m, err := sr.cm.Snapshotter.Mounts(context.TODO(), sr.id)
if err != nil {
return nil, errors.Wrapf(err, "failed to mount %s", sr.id)
}
return m, nil
} else {
if sr.viewMount == nil { // TODO: handle this better
sr.view = generateID()
m, err := sr.cm.Snapshotter.View(context.TODO(), sr.view, sr.id)
if err != nil {
sr.view = ""
return nil, errors.Wrapf(err, "failed to mount %s", sr.id)
}
sr.viewMount = m
}
return sr.viewMount, nil
}
return nil, errors.New("snapshot mount not implemented")
}
func (sr *cacheRef) Release() error {
sr.cm.mu.Lock()
defer sr.cm.mu.Unlock()
sr.mu.Lock()
defer sr.mu.Unlock()
return sr.release()
}
func (sr *cacheRef) release() error {
if sr.parent != nil {
if err := sr.parent.(*cacheRef).release(); err != nil {
return err
}
}
if sr.viewMount != nil {
if err := sr.cm.Snapshotter.Remove(context.TODO(), sr.view); err != nil {
return err
}
sr.view = ""
sr.viewMount = nil
}
delete(sr.refs, sr)
sr.frozen = false
if len(sr.refs) == 0 {
//go sr.cm.GC()
}
return nil
}
func (sr *cacheRef) Freeze() (ImmutableRef, error) {
sr.cm.mu.Lock()
defer sr.cm.mu.Unlock()
sr.mu.Lock()
defer sr.mu.Unlock()
if !sr.mutable || sr.frozen || len(sr.refs) != 1 {
return nil, errors.Wrapf(errInvalid, "invalid mutable")
}
if _, ok := sr.refs[sr]; !ok {
return nil, errors.Wrapf(errInvalid, "invalid mutable")
}
sr.frozen = true
sr.size = sizeUnknown
return sr, nil
}
func (sr *cacheRef) ReleaseAndCommit(ctx context.Context) (ImmutableRef, error) {
sr.cm.mu.Lock()
defer sr.cm.mu.Unlock()
sr.mu.Lock()
if !sr.mutable || sr.frozen {
sr.mu.Unlock()
return nil, errors.Wrapf(errInvalid, "invalid mutable")
}
if len(sr.refs) != 1 {
sr.mu.Unlock()
return nil, errors.Wrapf(errInvalid, "multiple mutable references")
}
sr.mu.Unlock()
id := generateID() // TODO: no need to actually switch the key here
err := sr.cm.Snapshotter.Commit(ctx, id, sr.id)
if err != nil {
return nil, errors.Wrapf(err, "failed to commit %s", sr.id)
}
delete(sr.cm.records, sr.id)
rec := &cacheRecord{
id: id,
cm: sr.cm,
refs: make(map[*cacheRef]struct{}),
size: sizeUnknown,
}
sr.cm.records[id] = rec // TODO: save to db
return rec.ref(), nil
}
func (sr *cacheRef) ID() string {
return sr.id
}
func generateID() string {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return hex.EncodeToString(b)
}