Skip to content

Commit

Permalink
Merge pull request #1965 from tonistiigi/fileop-cache-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
AkihiroSuda authored Feb 4, 2021
2 parents 91e19ef + 8d70777 commit d557934
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 0 deletions.
30 changes: 30 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func TestIntegration(t *testing.T) {
testSourceMapFromRef,
testLazyImagePush,
testStargzLazyPull,
testFileOpInputSwap,
}, mirrors)

integration.Run(t, []integration.Test{
Expand Down Expand Up @@ -1128,7 +1129,36 @@ func testFileOpCopyRm(t *testing.T, sb integration.Sandbox) {
dt, err = ioutil.ReadFile(filepath.Join(destDir, "file2"))
require.NoError(t, err)
require.Equal(t, []byte("file2"), dt)
}

// testFileOpInputSwap is a regression test that cache is invalidated when subset of fileop is built
func testFileOpInputSwap(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)
c, err := New(context.TODO(), sb.Address())
require.NoError(t, err)
defer c.Close()

base := llb.Scratch().File(llb.Mkfile("/foo", 0600, []byte("foo")))

src := llb.Scratch().File(llb.Mkfile("/bar", 0600, []byte("bar")))

st := base.File(llb.Copy(src, "/bar", "/baz"))

def, err := st.Marshal(context.TODO())
require.NoError(t, err)

_, err = c.Solve(context.TODO(), def, SolveOpt{}, nil)
require.NoError(t, err)

// bar does not exist in base but index of all inputs remains the same
st = base.File(llb.Copy(base, "/bar", "/baz"))

def, err = st.Marshal(context.TODO())
require.NoError(t, err)

_, err = c.Solve(context.TODO(), def, SolveOpt{}, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "bar: no such file")
}

func testFileOpRmWildcard(t *testing.T, sb integration.Sandbox) {
Expand Down
45 changes: 45 additions & 0 deletions solver/llbsolver/ops/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func (f *fileOp) CacheMap(ctx context.Context, g session.Group, index int) (*sol
}
}

indexes := make([][]int, 0, len(f.op.Actions))

for _, action := range f.op.Actions {
var dt []byte
var err error
Expand Down Expand Up @@ -103,14 +105,21 @@ func (f *fileOp) CacheMap(ctx context.Context, g session.Group, index int) (*sol
}

actions = append(actions, dt)
indexes = append(indexes, []int{int(action.Input), int(action.SecondaryInput), int(action.Output)})
}

if isDefaultIndexes(indexes) {
indexes = nil
}

dt, err := json.Marshal(struct {
Type string
Actions [][]byte
Indexes [][]int `json:"indexes,omitempty"`
}{
Type: fileCacheType,
Actions: actions,
Indexes: indexes,
})
if err != nil {
return nil, false, err
Expand Down Expand Up @@ -611,3 +620,39 @@ func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptyp
}
return inp.(input), err
}

func isDefaultIndexes(idxs [][]int) bool {
// Older version of checksum did not contain indexes for actions resulting in possibility for a wrong cache match.
// We detect the most common pattern for indexes and maintain old checksum for that case to minimize cache misses on upgrade.
// If a future change causes braking changes in instruction cache consider removing this exception.
if len(idxs) == 0 {
return false
}

for i, idx := range idxs {
if len(idx) != 3 {
return false
}
// input for first action is first input
if i == 0 && idx[0] != 0 {
return false
}
// input for other actions is previous action
if i != 0 && idx[0] != len(idxs)+(i-1) {
return false
}
// secondary input is second input or -1
if idx[1] != -1 && idx[1] != 1 {
return false
}
// last action creates output
if i == len(idxs)-1 && idx[2] != 0 {
return false
}
// other actions do not create an output
if i != len(idxs)-1 && idx[2] != -1 {
return false
}
}
return true
}

0 comments on commit d557934

Please sign in to comment.