mirror of
https://github.com/moby/buildkit.git
synced 2026-08-04 14:50:21 +00:00
exec: refactor content-based cache detection
This refactors the content-based cache to be that little bit tidier. In
addition to adding comments that explain *why* we're even bothering,
this restructures the code to avoid being unclear.
To explain the changes in a little more detail (since it's not
abundantly clear why this translation is valid), the initial condition
looks like:
if (!m.Readonly || m.Dest == pb.RootMount) && m.Output != -1 {
deps[m.Input].NoContentBasedHash = true
We can apply De Morgan's law recursively to invert the condition and the
result:
deps[m.Input].NoContentBasedHash = true
if (m.Readonly && m.Dest != pb.RootMount) || m.Output == -1 {
deps[m.Input].NoContentBasedHash = false
With all the juggling of NoContentBasedCache, we invert the variable
name to be ContentBasedCache (and invert everywhere it's used as well):
if (m.Readonly && m.Dest != pb.RootMount) || m.Output == -1 {
deps[m.Input].ContentBasedHash = true
This reads a bit easier, but now we split this into two separate
branches for readability (and so we can comment each one in more detail
separately):
if m.Readonly && m.Dest != pb.RootMount {
deps[m.Input].ContentBasedHash = true
}
if m.Output == -1 {
deps[m.Input].ContentBasedHash = true
}
While this has been the behavior for ages, I think it makes sense to
deliberately this behavior slightly. It doesn't make sense to me that we
should only disallow read-only root mounts, but no-output root mounts
are allowed - there's no reason these shouldn't behave identically, by
splitting these out:
if m.Readonly {
deps[m.Input].ContentBasedHash = true
}
if m.Output == -1 {
deps[m.Input].ContentBasedHash = true
}
if m.Dest == pb.RootMount {
deps[m.Input].ContentBasedHash = false
}
There is a small chance that this is a breaking change for some users,
however, 1. SkipOutput (-1) is very rare and not often used in the wild
(except for dockerfiles, where it's only used for non-root mounts), and 2.
will only cause a cache miss.
Signed-off-by: Justin Chadwell <me@jedevc.com>
This commit is contained in:
@@ -212,7 +212,7 @@ func (e *ExecOp) CacheMap(ctx context.Context, g session.Group, index int) (*sol
|
||||
}
|
||||
cm.Deps[i].Selector = digest.FromBytes(bytes.Join(dgsts, []byte{0}))
|
||||
}
|
||||
if !dep.NoContentBasedHash {
|
||||
if dep.ContentBasedHash {
|
||||
cm.Deps[i].ComputeDigestFunc = opsutils.NewContentHashFunc(toSelectors(dedupePaths(dep.Selectors)))
|
||||
}
|
||||
cm.Deps[i].PreprocessFunc = unlazyResultFunc
|
||||
@@ -275,8 +275,11 @@ func toSelectors(p []string) []opsutils.Selector {
|
||||
}
|
||||
|
||||
type dep struct {
|
||||
Selectors []string
|
||||
NoContentBasedHash bool
|
||||
Selectors []string
|
||||
|
||||
// ContentBasedHash enables content-based caching. This is used to ensure
|
||||
// that all caching is done safely and efficiently.
|
||||
ContentBasedHash bool
|
||||
}
|
||||
|
||||
func (e *ExecOp) getMountDeps() ([]dep, error) {
|
||||
@@ -292,9 +295,38 @@ func (e *ExecOp) getMountDeps() ([]dep, error) {
|
||||
sel := path.Join("/", m.Selector)
|
||||
deps[m.Input].Selectors = append(deps[m.Input].Selectors, sel)
|
||||
|
||||
if (!m.Readonly || m.Dest == pb.RootMount) && m.Output != -1 { // exclude read-only rootfs && read-write mounts
|
||||
deps[m.Input].NoContentBasedHash = true
|
||||
// Assume that we *cannot* perform content-based caching, and then
|
||||
// enable it selectively only for cases where we want to
|
||||
contentBasedCache := false
|
||||
|
||||
// Allow content-based cached where safe - these are enforced to avoid
|
||||
// the following case:
|
||||
// - A "snapshot" contains "foo/a.txt" and "bar/b.txt"
|
||||
// - "RUN --mount from=snapshot,src=bar touch bar/c.txt" creates a new
|
||||
// file in bar
|
||||
// - If we run again, but this time "snapshot" contains a new
|
||||
// "foo/sneaky.txt", the content-based cache matches the previous
|
||||
// run, since we only select "bar"
|
||||
// - But this cached result is incorrect - "foo/sneaky.txt" isn't in
|
||||
// our cached result, but it is in our input.
|
||||
if m.Output == pb.SkipOutput {
|
||||
// if the mount has no outputs, it's safe to enable content-based
|
||||
// caching, since it's guaranteed to not be used as an input for
|
||||
// any future steps
|
||||
contentBasedCache = true
|
||||
} else if m.Readonly {
|
||||
// if the mount is read-only, then it's also safe, since it can't
|
||||
// be modified by the operation
|
||||
contentBasedCache = true
|
||||
}
|
||||
|
||||
if m.Dest == pb.RootMount {
|
||||
// we explicitly choose to not implement it on the root mount,
|
||||
// since this is likely very expensive (and not incredibly useful)
|
||||
contentBasedCache = false
|
||||
}
|
||||
|
||||
deps[m.Input].ContentBasedHash = contentBasedCache
|
||||
}
|
||||
return deps, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user