From 5994854fe6030f826176622dd66d71a994921d67 Mon Sep 17 00:00:00 2001 From: NoneBack Date: Tue, 15 Oct 2024 15:49:16 +0800 Subject: [PATCH] fix(visualizer): panic when subflow handle is paniced --- graph.go | 18 +++++++++++++----- graph_test.go | 30 +++++++++++++++--------------- taskflow.go | 8 -------- taskflow_test.go | 6 +++--- visualizer.go | 20 +++++++++++++------- 5 files changed, 44 insertions(+), 38 deletions(-) diff --git a/graph.go b/graph.go index 9b1964f..24c2f3a 100644 --- a/graph.go +++ b/graph.go @@ -1,6 +1,7 @@ package gotaskflow import ( + "fmt" "sync" "sync/atomic" @@ -72,11 +73,18 @@ func (g *Graph) instancelize() { } // only for visualizer -func (g *Graph) topologicalSort() ([]*Node, bool) { - g.instancelize() +func (g *Graph) topologicalSort() (sorted []*Node, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("instancelize may failed or paniced") + return + } + }() + + g.instancelize() // may require panic recover indegree := map[*Node]int{} // Node -> indegree zeros := make([]*Node, 0) // zero deps - sorted := make([]*Node, 0, len(g.nodes)) + sorted = make([]*Node, 0, len(g.nodes)) for _, node := range g.nodes { set := map[*Node]struct{}{} @@ -106,9 +114,9 @@ func (g *Graph) topologicalSort() ([]*Node, bool) { for _, node := range g.nodes { if indegree[node] > 0 { - return nil, false + return nil, fmt.Errorf("graph has cycles") } } - return sorted, true + return } diff --git a/graph_test.go b/graph_test.go index 7c2ed40..5541a86 100644 --- a/graph_test.go +++ b/graph_test.go @@ -5,9 +5,9 @@ import "testing" func TestTopologicalSort(t *testing.T) { t.Run("TestEmptyGraph", func(t *testing.T) { graph := newGraph("empty") - sorted, ok := graph.topologicalSort() - if !ok || len(sorted) != 0 { - t.Errorf("expected true and an empty slice, got %v and %v", ok, sorted) + sorted, err := graph.topologicalSort() + if err != nil || len(sorted) != 0 { + t.Errorf("expected true and an empty slice, got %v and %v", err, sorted) } }) @@ -15,9 +15,9 @@ func TestTopologicalSort(t *testing.T) { graph := newGraph("single node") nodeA := newNode("A") graph.push(nodeA) - sorted, ok := graph.topologicalSort() - if !ok || len(sorted) != 1 || sorted[0] != nodeA { - t.Errorf("expected true and the single node, got %v and %v", ok, sorted) + sorted, err := graph.topologicalSort() + if err != nil || len(sorted) != 1 || sorted[0] != nodeA { + t.Errorf("expected true and the single node, got %v and %v", err, sorted) } }) @@ -29,9 +29,9 @@ func TestTopologicalSort(t *testing.T) { nodeA.precede(nodeB) nodeB.precede(nodeC) graph.push(nodeA, nodeB, nodeC) - sorted, ok := graph.topologicalSort() - if !ok || len(sorted) != 3 || sorted[0] != nodeA || sorted[1] != nodeB || sorted[2] != nodeC { - t.Errorf("expected true and a correct sorted order, got %v and %v", ok, sorted) + sorted, err := graph.topologicalSort() + if err != nil || len(sorted) != 3 || sorted[0] != nodeA || sorted[1] != nodeB || sorted[2] != nodeC { + t.Errorf("expected true and a correct sorted order, got %v and %v", err, sorted) } }) @@ -48,9 +48,9 @@ func TestTopologicalSort(t *testing.T) { nodeC.precede(nodeD) nodeD.precede(nodeE) graph.push(nodeA, nodeB, nodeC, nodeD, nodeE) - sorted, ok := graph.topologicalSort() - if !ok || len(sorted) != 5 { - t.Errorf("expected true and a correct sorted order, got %v and %v", ok, sorted) + sorted, err := graph.topologicalSort() + if err != nil || len(sorted) != 5 { + t.Errorf("expected true and a correct sorted order, got %v and %v", err, sorted) } // Further check the ordering nodeIndex := make(map[*Node]int) @@ -71,9 +71,9 @@ func TestTopologicalSort(t *testing.T) { nodeB.precede(nodeC) nodeC.precede(nodeA) // Creates a cycle graph.push(nodeA, nodeB, nodeC) - _, ok := graph.topologicalSort() - if ok { - t.Errorf("expected false due to cycle, got %v", ok) + _, err := graph.topologicalSort() + if err == nil { + t.Errorf("expected false due to cycle, got %v", err) } }) } diff --git a/taskflow.go b/taskflow.go index d9fb765..b9cbedc 100644 --- a/taskflow.go +++ b/taskflow.go @@ -1,13 +1,5 @@ package gotaskflow -import ( - "errors" -) - -var ( - ErrGraphIsCyclic = errors.New("graph is cyclic, not support") -) - type TaskFlow struct { name string graph *Graph diff --git a/taskflow_test.go b/taskflow_test.go index 77d6b66..535d1f6 100644 --- a/taskflow_test.go +++ b/taskflow_test.go @@ -197,8 +197,8 @@ func TestSubflowPanic(t *testing.T) { tf.Push(subflow) exector.Run(tf) exector.Wait() - // if err := gotaskflow.Visualizer.Visualize(tf, os.Stdout); err != nil { - // log.Fatal(err) - // } + if err := gotaskflow.Visualizer.Visualize(tf, os.Stdout); err != nil { + fmt.Errorf("%v", err) + } exector.Profile(os.Stdout) } diff --git a/visualizer.go b/visualizer.go index df08d20..b4b007c 100644 --- a/visualizer.go +++ b/visualizer.go @@ -15,9 +15,9 @@ type visualizer struct { var Visualizer = visualizer{} func (v *visualizer) visualizeG(gv *graphviz.Graphviz, g *Graph, parentG *cgraph.Graph) error { - nodes, ok := g.topologicalSort() - if !ok { - return fmt.Errorf("graph %v topological sort -> %w", g.name, ErrGraphIsCyclic) + nodes, err := g.topologicalSort() + if err != nil { + return fmt.Errorf("graph %v topological sort -> %w", g.name, err) } vGraph := parentG if vGraph == nil { @@ -44,10 +44,16 @@ func (v *visualizer) visualizeG(gv *graphviz.Graphviz, g *Graph, parentG *cgraph vSubGraph := vGraph.SubGraph("cluster_"+node.name, 1) err := v.visualizeG(gv, p.g, vSubGraph) if err != nil { - return fmt.Errorf("graph %v visualize -> %w", g.name, ErrGraphIsCyclic) + fmt.Printf("graph %v visualize -> %s\n", g.name, err) + // return fmt.Errorf() + vNode, err := vSubGraph.CreateNode("unvisualized_" + p.g.name) + if err != nil { + return fmt.Errorf("add node %v -> %w", node.name, err) + } + nodeMap[node.name] = vNode + } else { + nodeMap[node.name] = vSubGraph.FirstNode() } - - nodeMap[node.name] = vSubGraph.FirstNode() } } @@ -68,7 +74,7 @@ func (v *visualizer) Visualize(tf *TaskFlow, writer io.Writer) error { err := v.visualizeG(gv, tf.graph, nil) if err != nil { - return fmt.Errorf("graph %v topological sort -> %w", tf.graph.name, ErrGraphIsCyclic) + return fmt.Errorf("graph %v topological sort -> %w", tf.graph.name, err) } if err := gv.Render(v.root, graphviz.XDOT, writer); err != nil {