Skip to content

Commit b6c5803

Browse files
authored
Merge pull request #4 from Azure/haitao/new-dag-lib
drop terraform dag lib
2 parents 2ed229d + 7f3c57c commit b6c5803

File tree

7 files changed

+120
-806
lines changed

7 files changed

+120
-806
lines changed

README.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,8 @@ AsyncJob aiming to help you organize code in dependencyGraph(DAG), instead of a
4848
```
4949

5050
### visualize of a job
51-
this visualize depend on [terraform/dag](github.com/hashicorp/terraform/dag), with some limitation, may need some upstream tweaks:
52-
- able to customize node name
53-
- able to distinguash type of node (param, executionBlock)
54-
- able to show state of node (pending, running, completed, failed)
51+
tried https://github.com/hashicorp/terraform/tree/main/internal/dag, which doesn't have own go module, but terraform go module have too much dependencies.
52+
baking a inhouse one.
5553

5654
```
5755
digraph {

go.mod

+1-15
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,4 @@ module github.com/Azure/go-asyncjob
22

33
go 1.18
44

5-
require (
6-
github.com/Azure/go-asynctask v1.2.1-after-all
7-
github.com/hashicorp/terraform v0.15.3
8-
)
9-
10-
require (
11-
github.com/agext/levenshtein v1.2.2 // indirect
12-
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
13-
github.com/hashicorp/errwrap v1.1.0 // indirect
14-
github.com/hashicorp/go-multierror v1.1.1 // indirect
15-
github.com/hashicorp/hcl/v2 v2.10.0 // indirect
16-
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
17-
github.com/zclconf/go-cty v1.8.3 // indirect
18-
golang.org/x/text v0.3.5 // indirect
19-
)
5+
require github.com/Azure/go-asynctask v1.3.0

go.sum

+3-763
Large diffs are not rendered by default.

graph/graph.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package graph
2+
3+
import (
4+
"bytes"
5+
)
6+
7+
type GraphErrorCode string
8+
9+
const (
10+
ErrDuplicateNode GraphErrorCode = "node with same key already exists in this graph"
11+
ErrConnectNotExistingNode GraphErrorCode = "node to connect does not exist in this graph"
12+
)
13+
14+
func (ge GraphErrorCode) Error() string {
15+
return string(ge)
16+
}
17+
18+
type NodeConstrain interface {
19+
// Name of the node, used as key in the graph, so should be unique.
20+
GetName() string
21+
}
22+
23+
type Edge[NT NodeConstrain] struct {
24+
From NT
25+
To NT
26+
}
27+
28+
type Graph[NT NodeConstrain] struct {
29+
nodes map[string]NT
30+
nodeEdges map[string][]*Edge[NT]
31+
}
32+
33+
func NewGraph[NT NodeConstrain]() *Graph[NT] {
34+
return &Graph[NT]{
35+
nodes: make(map[string]NT),
36+
nodeEdges: make(map[string][]*Edge[NT]),
37+
}
38+
}
39+
40+
func (g *Graph[NT]) AddNode(n NT) error {
41+
nodeKey := n.GetName()
42+
if _, ok := g.nodes[nodeKey]; ok {
43+
return ErrDuplicateNode
44+
}
45+
g.nodes[nodeKey] = n
46+
47+
return nil
48+
}
49+
50+
func (g *Graph[NT]) Connect(from, to string) error {
51+
var nodeFrom, nodeTo NT
52+
var ok bool
53+
if nodeFrom, ok = g.nodes[from]; !ok {
54+
return ErrConnectNotExistingNode
55+
}
56+
57+
if nodeTo, ok = g.nodes[to]; !ok {
58+
return ErrConnectNotExistingNode
59+
}
60+
61+
g.nodeEdges[from] = append(g.nodeEdges[from], &Edge[NT]{From: nodeFrom, To: nodeTo})
62+
return nil
63+
}
64+
65+
// https://en.wikipedia.org/wiki/DOT_(graph_description_language)
66+
func (g *Graph[NT]) ToDotGraph() (string, error) {
67+
edges := make(map[string][]string)
68+
for _, nodeEdges := range g.nodeEdges {
69+
for _, edge := range nodeEdges {
70+
edges[edge.From.GetName()] = append(edges[edge.From.GetName()], edge.To.GetName())
71+
}
72+
}
73+
74+
buf := new(bytes.Buffer)
75+
err := digraphTemplate.Execute(buf, edges)
76+
if err != nil {
77+
return "", err
78+
}
79+
return buf.String(), nil
80+
}

graph/template.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package graph
2+
3+
import (
4+
"text/template"
5+
)
6+
7+
var digraphTemplate = template.Must(template.New("digraph").Parse(digraphTemplateText))
8+
9+
const digraphTemplateText = `digraph {
10+
compound = "true"
11+
newrank = "true"
12+
subgraph "root" {
13+
{{ range $from, $toList := $}}{{ range $_, $to := $toList}} "{{$from}}" -> "{{$to}}"
14+
{{ end }}{{ end }} }
15+
}`

job.go

+13-21
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"fmt"
66
"sync"
77

8+
"github.com/Azure/go-asyncjob/graph"
89
"github.com/Azure/go-asynctask"
9-
"github.com/hashicorp/terraform/dag"
1010
)
1111

1212
type JobState string
@@ -22,7 +22,7 @@ type Job struct {
2222
state JobState
2323
rootJob *StepInfo[interface{}]
2424
jobStart *sync.WaitGroup
25-
stepsDag *dag.AcyclicGraph
25+
stepsDag *graph.Graph[StepMeta]
2626

2727
// runtimeCtx is captured to separate build context and runtime context.
2828
// golang not recommending to store context in the struct. I don't have better idea.
@@ -38,7 +38,7 @@ func NewJob(name string) *Job {
3838

3939
jobStart: &jobStart,
4040
state: JobStatePending,
41-
stepsDag: &dag.AcyclicGraph{},
41+
stepsDag: graph.NewGraph[StepMeta](),
4242
}
4343

4444
j.rootJob = &StepInfo[interface{}]{
@@ -53,7 +53,7 @@ func NewJob(name string) *Job {
5353
}
5454

5555
j.Steps[j.rootJob.GetName()] = j.rootJob
56-
j.stepsDag.Add(j.rootJob.GetName())
56+
j.stepsDag.AddNode(j.rootJob)
5757

5858
return j
5959
}
@@ -68,7 +68,7 @@ func InputParam[T any](bCtx context.Context, j *Job, stepName string, value *T)
6868
step.task = asynctask.Start(bCtx, instrumentedFunc)
6969

7070
j.Steps[stepName] = step
71-
j.registerStepInGraph(stepName, j.rootJob.GetName())
71+
j.registerStepInGraph(step, j.rootJob.GetName())
7272

7373
return step
7474
}
@@ -116,7 +116,7 @@ func AddStep[T any](bCtx context.Context, j *Job, stepName string, stepFunc asyn
116116
step.task = asynctask.Start(bCtx, instrumentedFunc)
117117

118118
j.Steps[stepName] = step
119-
j.registerStepInGraph(stepName, precedingStepNames...)
119+
j.registerStepInGraph(step, precedingStepNames...)
120120

121121
return step, nil
122122
}
@@ -168,10 +168,7 @@ func StepAfter[T, S any](bCtx context.Context, j *Job, stepName string, parentSt
168168
step.task = asynctask.ContinueWith(bCtx, parentStep.task, instrumentedFunc)
169169

170170
j.Steps[stepName] = step
171-
j.registerStepInGraph(stepName, precedingStepNames...)
172-
if err := j.stepsDag.Validate(); err != nil {
173-
return nil, fmt.Errorf("cycle dependency detected: %s", err)
174-
}
171+
j.registerStepInGraph(step, precedingStepNames...)
175172
return step, nil
176173
}
177174

@@ -221,7 +218,7 @@ func StepAfterBoth[T, S, R any](bCtx context.Context, j *Job, stepName string, p
221218
step.task = asynctask.AfterBoth(bCtx, parentStepT.task, parentStepS.task, instrumentedFunc)
222219

223220
j.Steps[stepName] = step
224-
j.registerStepInGraph(stepName, precedingStepNames...)
221+
j.registerStepInGraph(step, precedingStepNames...)
225222

226223
return step, nil
227224
}
@@ -246,21 +243,16 @@ func (j *Job) Wait(ctx context.Context) error {
246243
return asynctask.WaitAll(ctx, &asynctask.WaitAllOptions{}, tasks...)
247244
}
248245

249-
func (j *Job) registerStepInGraph(stepName string, precedingStep ...string) error {
250-
j.stepsDag.Add(stepName)
246+
func (j *Job) registerStepInGraph(step StepMeta, precedingStep ...string) error {
247+
j.stepsDag.AddNode(step)
251248
for _, precedingStepName := range precedingStep {
252-
j.stepsDag.Connect(dag.BasicEdge(precedingStepName, stepName))
253-
if err := j.stepsDag.Validate(); err != nil {
254-
return fmt.Errorf("failed to add step %q depend on %q, likely a cycle dependency. %w", stepName, precedingStepName, err)
255-
}
249+
j.stepsDag.Connect(precedingStepName, step.GetName())
256250
}
257251

258252
return nil
259253
}
260254

261255
// Visualize return a DAG of the job execution graph
262-
func (j *Job) Visualize() string {
263-
opts := &dag.DotOpts{MaxDepth: 42}
264-
actual := j.stepsDag.Dot(opts)
265-
return string(actual)
256+
func (j *Job) Visualize() (string, error) {
257+
return j.stepsDag.ToDotGraph()
266258
}

job_test.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ func TestSimpleJob(t *testing.T) {
1515
}
1616
jb := sb.BuildJob(context.Background())
1717

18-
dotGraph := jb.Visualize()
19-
fmt.Println(dotGraph)
20-
2118
jb.Start(context.Background())
2219
jb.Wait(context.Background())
20+
21+
dotGraph, err := jb.Visualize()
22+
if err != nil {
23+
t.FailNow()
24+
}
25+
fmt.Println(dotGraph)
2326
}
2427

2528
var _ JobBuilder = &SqlJobBuilder{}

0 commit comments

Comments
 (0)