Skip to content

Commit

Permalink
draft: add cache to level calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabriel Musat committed Dec 14, 2022
1 parent 8160042 commit e5d3099
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 28 deletions.
64 changes: 37 additions & 27 deletions internal/graph/node/level.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ package node
import (
"context"
"fmt"

"dep-tree/internal/utils"
)

type key int

const (
cycleKey key = iota
)
type cacheKey string
type cycleKey string

const unknown = -2
const cyclic = -1

func copyMap[K comparable, V any](m map[K]V) map[K]V {
result := make(map[K]V)
for k, v := range m {
result[k] = v
}
return result
}

func hashDep[T any](a *Node[T], b *Node[T]) string {
return a.Id + " -> " + b.Id
}
Expand All @@ -25,56 +28,63 @@ func calculateLevel[T any](
node *Node[T],
rootId string,
level int,
stack []string,
seen map[string]bool,
) (context.Context, int) {
if node.Id == rootId {
var cachedLevelKey = cacheKey("level-" + node.Id)
if cachedLevel, ok := ctx.Value(cachedLevelKey).(int); ok {
// 1. Check first the cache, we do not like to work more than need.
return ctx, cachedLevel + level
} else if node.Id == rootId {
// 2. If it is the root node where are done.
return ctx, level
} else {
for _, seen := range stack {
if seen == node.Id {
return ctx, cyclic
}
}
} else if _, ok := seen[node.Id]; ok {
// 3. Check if we have closed a loop.
return ctx, cyclic
}

// 4. Calculate the maximum level for this node ignore deps that where previously seen as cyclical.
seen = copyMap(seen)
seen[node.Id] = true
maxLevel := unknown
for _, parentId := range node.Parents.Keys() {
parent, _ := node.Parents.Get(parentId)
dep := hashDep(parent, node)
knownCycles, _ := ctx.Value(cycleKey).([]string)
if knownCycles == nil {
knownCycles = []string{}
} else if utils.InArray(dep, knownCycles) {

cachedCycleKey := cycleKey("cycle-" + dep)
if _, ok := ctx.Value(cachedCycleKey).(bool); ok {
continue
}

var newLevel int
ctx, newLevel = calculateLevel(ctx, parent, rootId, level+1, append(stack, node.Id))
ctx, newLevel = calculateLevel(ctx, parent, rootId, level+1, seen)
if newLevel == cyclic {
ctx = context.WithValue(ctx, cycleKey, append(knownCycles, dep))
ctx = context.WithValue(ctx, cachedCycleKey, true)
} else if newLevel > maxLevel {
maxLevel = newLevel
}
}

// 5. If ignoring previously seen cyclical deps we are not able
// to tell the level, then recalculate without ignoring them.
if maxLevel == unknown {
for _, parentId := range node.Parents.Keys() {
parent, _ := node.Parents.Get(parentId)

var newLevel int
ctx, newLevel = calculateLevel(ctx, parent, rootId, level+1, append(stack, node.Id))
if newLevel == cyclic {
continue
} else if newLevel > maxLevel {
ctx, newLevel = calculateLevel(ctx, parent, rootId, level+1, seen)
if newLevel > maxLevel {
maxLevel = newLevel
}
}
}
//if maxLevel >= 0 {
// ctx = context.WithValue(ctx, cachedLevelKey, maxLevel)
//}
return ctx, maxLevel
}

// Level retrieves the longest path until going to "rootId" avoiding cyclical loops.
func (n *Node[T]) Level(ctx context.Context, rootId string) (context.Context, int) {
ctx, lvl := calculateLevel(ctx, n, rootId, 0, []string{})
ctx, lvl := calculateLevel(ctx, n, rootId, 0, map[string]bool{})
if lvl == unknown {
// TODO: there is a bug here, there are cases where this is reached.
msg := "This should not be reachable"
Expand Down
4 changes: 3 additions & 1 deletion internal/graph/node/level_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,13 @@ func TestNode_Level(t *testing.T) {
}
}
ctx := context.Background()
var lvls []int
for i := 0; i < tt.NumNodes; i++ {
var lvl int
ctx, lvl = nodes[i].Level(ctx, nodes[0].Id)
a.Equal(tt.ExpectedLevels[i], lvl)
lvls = append(lvls, lvl)
}
a.Equal(tt.ExpectedLevels, lvls)
})
}
}

0 comments on commit e5d3099

Please sign in to comment.