Golang Dag

package dag import ( "context" "ina-fei/api/internal/config" "ina-fei/api/internal/types" "log" "sync" ) type Graph struct { // nodes to run Nodes []*Node // where to fetch all node result ResultGather chan *NodeResult // adjoint table for this graph cache map[string][]*Node } func NewGraph() *Graph { return &Graph{ResultGather: make(chan *NodeResult), cache: make(map[string][]*Node)} } func (g *Graph) Add(node *Node) { // add to adjoint table // check if this node already be exists // panic if exists if _, ok := g.cache[node.Meta.Name]; ok == true { panic("node already exists in this graph") } // add to adjoint table g.cache[node.Meta.Name] = make([]*Node, 0) // connect node to graph node.ResultGather = g.ResultGather //add to list prepare for run g.Nodes = append(g.Nodes, node) } func (g *Graph) Connect(from, to *Node) { // check both node exists in this graph if _, ok := g.cache[from.Meta.Name]; ok != true { panic("node not exists in this graph; call Add before connect") } if _, ok := g.cache[to.Meta.Name]; ok != true { panic("node not exists in this graph; call Add before connect") } // check if already connected for _, n := range g.cache[from.Meta.Name] { if n.Meta.Name == to.Meta.Name { log.Println("already connect, will do nothing") return } } // do connect // modify adjoint table g.cache[from.Meta.Name] = append(g.cache[from.Meta.Name], to) // modify channel of both side node c := make(chan *NodeResult) from.OutputChannels = append(from.OutputChannels, c) to.InputChannels = append(to.InputChannels, c) log.Printf("connect from node %s to node %s", from.Meta.Name, to.Meta.Name) } func (g *Graph) findCycle() { // inDegrees stores in degree count for every node inDegrees := make(map[string]int) // set up for _, n := range g.Nodes { // set current node if _, ok := inDegrees[n.Meta.Name]; ok != true { inDegrees[n.Meta.Name] = 0 } // set successor of current node successors := g.cache[n.Meta.Name] for _, successor := range successors { inDegrees[successor.Meta.Name] += 1 } } // for len(inDegrees) != 0 { readyNodes := make([]string, 0) for k, v := range inDegrees { if v == 0 { readyNodes = append(readyNodes, k) } } if len(readyNodes) == 0 { panic("find cycle") } for _, readyNode := range readyNodes { successorOfReadyNode := g.cache[readyNode] for _, s := range successorOfReadyNode { inDegrees[s.Meta.Name] -= 1 } // delete ready node delete(inDegrees, readyNode) } } } // total async // check g.ResultGather func (g *Graph) Run(ctx context.Context, config config.Config, req types.Request) { // find cycle first g.findCycle() var wg sync.WaitGroup for _, n := range g.Nodes { wg.Add(1) go func(n *Node) { defer wg.Done() n.Run(ctx, config, req) }(n) } go func() { wg.Wait() close(g.ResultGather) }() }

package dag import ( "context" "fmt" "api/internal/config" "api/internal/types" "log" "sync" "github.com/google/uuid" ) type NodeType int const ( DataSourceType NodeType = iota VarLogicType ) type ( NodeMetaData struct { Name string Type NodeType } NodeResult struct { Meta NodeMetaData // error occurs or not Err error // result // map if NodeType is VarLogicType // pointer to AyoTransitResult otherwise Result interface{} } // Node logic func generate node result // ctx -> not currently be used // config -> configure from yaml file and etc. // req -> request info contains basic user info and etc. // gather -> channel where proceed result is stored NodeLogicFunc func(ctx context.Context, config config.Config, req types.Request, cache []*NodeResult) *NodeResult Node struct { Meta NodeMetaData InputChannels []chan *NodeResult OutputChannels []chan *NodeResult ResultGather chan *NodeResult LogicFunc NodeLogicFunc } ) func NewNode(nodeType NodeType, name string) *Node { return &Node{Meta: NodeMetaData{Name: name, Type: nodeType}} } func NewNodeWithName(nodeType NodeType) *Node { return &Node{Meta: NodeMetaData{Name: uuid.New().String(), Type: nodeType}} } func (node *Node) SetFunc(logicFunc NodeLogicFunc) { node.LogicFunc = logicFunc } func (node *Node) MakeNodeResult(result interface{}, err error) *NodeResult { return &NodeResult{ Meta: node.Meta, Err: err, Result: result, } } func (node *Node) Run(ctx context.Context, config config.Config, req types.Request) { // check func is set if node.LogicFunc == nil { panic("call setFunc before Run") } var wg sync.WaitGroup // wait for input // gather all input gather := make(chan *NodeResult) log.Printf("node %s begin to wait", node.Meta.Name) for _, i := range node.InputChannels { wg.Add(1) go func(i chan *NodeResult) { defer wg.Done() nodeResult := <-i gather <- nodeResult }(i) } go func() { wg.Wait() close(gather) }() // run logic cache := make([]*NodeResult, 0) for i := range gather { cache = append(cache, i) } log.Printf("node %s finish wait", node.Meta.Name) log.Printf("node %s prepare to run", node.Meta.Name) currentNodeResult := node.LogicFunc(ctx, config, req, cache) // send result to output channels for _, c := range node.OutputChannels { wg.Add(1) go func(c chan *NodeResult) { defer wg.Done() defer close(c) c <- currentNodeResult }(c) } // for send result wg.Add(1) go func() { defer wg.Done() if node.Meta.Type == VarLogicType { fmt.Printf("node %s send result to ResultGather\n", node.Meta.Name) node.ResultGather <- currentNodeResult } }() wg.Wait() log.Printf("node %s finish run", node.Meta.Name) }