diff --git a/core/itg/graph/update.go b/core/itg/graph/update.go index 457f4c15..6122b00f 100644 --- a/core/itg/graph/update.go +++ b/core/itg/graph/update.go @@ -186,6 +186,23 @@ func computeAvailableHashes( // computeHashes computes hashes recursively for the given target ID. func (g *OptimizedGraph) computeHashes(ctx context.Context, id int) ([]byte, error) { + computed := make(map[int][]byte) + hash, err := g.computeHashesRecursively(ctx, id, NewIntSet(), computed) + if err != nil { + return nil, err + } + for targetID, targetHash := range computed { + g.OptimizedTargets[targetID].Hash = targetHash + } + return hash, nil +} + +func (g *OptimizedGraph) computeHashesRecursively( + ctx context.Context, + id int, + visiting IntSet, + computed map[int][]byte, +) ([]byte, error) { if ctx.Err() != nil { return nil, context.Cause(ctx) } @@ -201,9 +218,16 @@ func (g *OptimizedGraph) computeHashes(ctx context.Context, id int) ([]byte, err if target.Hash != nil { return target.Hash, nil } + if hash, ok := computed[id]; ok { + return hash, nil + } + if visiting.Contains(id) { + return []byte{}, nil + } + + visiting.Insert(id) + defer visiting.Delete(id) - // mark as visiting to handle cycles - target.Hash = []byte{} var hash []byte switch g.RuleTypeIDToString[target.RuleType] { case targethasher.SourceFileType, targethasher.PackageGroup: @@ -214,7 +238,7 @@ func (g *OptimizedGraph) computeHashes(ctx context.Context, id int) ([]byte, err singleDep = dep break } - dephash, err := g.computeHashes(ctx, singleDep) + dephash, err := g.computeHashesRecursively(ctx, singleDep, visiting, computed) if err != nil { return nil, err } @@ -230,7 +254,7 @@ func (g *OptimizedGraph) computeHashes(ctx context.Context, id int) ([]byte, err return strings.Compare(g.TargetIDToString[i], g.TargetIDToString[j]) }) for _, dep := range depIDs { - dephash, err := g.computeHashes(ctx, dep) + dephash, err := g.computeHashesRecursively(ctx, dep, visiting, computed) if err != nil { return nil, err } @@ -239,7 +263,7 @@ func (g *OptimizedGraph) computeHashes(ctx context.Context, id int) ([]byte, err hash = h.Sum(nil) } if hash != nil { - target.Hash = hash + computed[id] = hash } return hash, nil } diff --git a/core/itg/graph/update_test.go b/core/itg/graph/update_test.go index 71b847c3..033d30c2 100644 --- a/core/itg/graph/update_test.go +++ b/core/itg/graph/update_test.go @@ -15,8 +15,10 @@ package graph import ( + "bytes" "context" "crypto/sha1" + "encoding/gob" "testing" buildpb "github.com/bazelbuild/buildtools/build_proto" @@ -231,4 +233,66 @@ func TestComputeHashes(t *testing.T) { h.Write(depHash) assert.Equal(t, h.Sum(nil), got) }) + + t.Run("failed traversal restores hashes before retry", func(t *testing.T) { + t.Parallel() + goodName := "//pkg:a_good" + badName := "//pkg:z_bad.go" + rootName := "//pkg:root" + goodHashWithoutDeps := []byte{0x01, 0x02} + rootHashWithoutDeps := []byte{0x03, 0x04} + g := OptimizeGraph(map[string]*targethasher.Target{ + goodName: { + Name: goodName, + RuleType: "go_library", + HashWithoutDeps: goodHashWithoutDeps, + }, + badName: { + Name: badName, + RuleType: targethasher.SourceFileType, + }, + rootName: { + Name: rootName, + RuleType: "go_library", + HashWithoutDeps: rootHashWithoutDeps, + Deps: []string{badName, goodName}, + }, + }) + goodID := g.TargetNameToID[goodName] + badID := g.TargetNameToID[badName] + rootID := g.TargetNameToID[rootName] + + _, err := g.computeHashes(context.Background(), rootID) + require.Error(t, err) + assert.Nil(t, g.OptimizedTargets[rootID].Hash) + assert.Nil(t, g.OptimizedTargets[goodID].Hash) + assert.Nil(t, g.OptimizedTargets[badID].Hash) + + var persisted bytes.Buffer + require.NoError(t, gob.NewEncoder(&persisted).Encode(g)) + var restored OptimizedGraph + require.NoError(t, gob.NewDecoder(&persisted).Decode(&restored)) + assert.Nil(t, restored.OptimizedTargets[rootID].Hash) + assert.Nil(t, restored.OptimizedTargets[goodID].Hash) + + badHash := []byte{0x05, 0x06} + g.OptimizedTargets[badID].Hash = badHash + + got, err := g.computeHashes(context.Background(), rootID) + require.NoError(t, err) + + goodHasher := sha1.New() + goodHasher.Write(goodHashWithoutDeps) + goodHash := goodHasher.Sum(nil) + rootHasher := sha1.New() + rootHasher.Write(rootHashWithoutDeps) + rootHasher.Write(goodHash) + rootHasher.Write(badHash) + expected := rootHasher.Sum(nil) + + assert.Equal(t, goodHash, g.OptimizedTargets[goodID].Hash) + assert.Equal(t, expected, got) + assert.Equal(t, expected, g.OptimizedTargets[rootID].Hash) + assert.NotEmpty(t, got) + }) }