Files
buildkit/client/llb/diff.go
Tonis Tiigi 30413b5de7 llb: avoid concurrent map write on parallel marshal
Calling marshal changes the internal state of the op, for example
addCap() helper adds capability constraints. These can race with
same map being read by another Marshal call. Locking the Marshal
function itself also makes sure that the cache is not recomputed
in this case.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2024-12-06 17:53:59 -08:00

113 lines
2.6 KiB
Go

package llb
import (
"context"
"github.com/moby/buildkit/solver/pb"
digest "github.com/opencontainers/go-digest"
)
type DiffOp struct {
cache MarshalCache
lower Output
upper Output
output Output
constraints Constraints
}
func NewDiff(lower, upper State, c Constraints) *DiffOp {
addCap(&c, pb.CapDiffOp)
op := &DiffOp{
lower: lower.Output(),
upper: upper.Output(),
constraints: c,
}
op.output = &output{vertex: op}
return op
}
func (m *DiffOp) Validate(ctx context.Context, constraints *Constraints) error {
return nil
}
func (m *DiffOp) Marshal(ctx context.Context, constraints *Constraints) (digest.Digest, []byte, *pb.OpMetadata, []*SourceLocation, error) {
cache := m.cache.Acquire()
defer cache.Release()
if dgst, dt, md, srcs, err := cache.Load(constraints); err == nil {
return dgst, dt, md, srcs, nil
}
if err := m.Validate(ctx, constraints); err != nil {
return "", nil, nil, nil, err
}
proto, md := MarshalConstraints(constraints, &m.constraints)
proto.Platform = nil // diff op is not platform specific
op := &pb.DiffOp{}
op.Lower = &pb.LowerDiffInput{Input: int64(len(proto.Inputs))}
if m.lower == nil {
op.Lower.Input = int64(pb.Empty)
} else {
pbLowerInput, err := m.lower.ToInput(ctx, constraints)
if err != nil {
return "", nil, nil, nil, err
}
proto.Inputs = append(proto.Inputs, pbLowerInput)
}
op.Upper = &pb.UpperDiffInput{Input: int64(len(proto.Inputs))}
if m.upper == nil {
op.Upper.Input = int64(pb.Empty)
} else {
pbUpperInput, err := m.upper.ToInput(ctx, constraints)
if err != nil {
return "", nil, nil, nil, err
}
proto.Inputs = append(proto.Inputs, pbUpperInput)
}
proto.Op = &pb.Op_Diff{Diff: op}
dt, err := deterministicMarshal(proto)
if err != nil {
return "", nil, nil, nil, err
}
return cache.Store(dt, md, m.constraints.SourceLocations, constraints)
}
func (m *DiffOp) Output() Output {
return m.output
}
func (m *DiffOp) Inputs() (out []Output) {
if m.lower != nil {
out = append(out, m.lower)
}
if m.upper != nil {
out = append(out, m.upper)
}
return out
}
// Diff returns a state that represents the diff of the lower and upper states.
// The returned State is useful for use with [Merge] where you can merge the lower state with the diff.
func Diff(lower, upper State, opts ...ConstraintsOpt) State {
if lower.Output() == nil {
if upper.Output() == nil {
// diff of scratch and scratch is scratch
return Scratch()
}
// diff of scratch and upper is just upper
return upper
}
var c Constraints
for _, o := range opts {
o.SetConstraintsOption(&c)
}
return lower.WithOutput(NewDiff(lower, upper, c).Output())
}