Files
mq/examples/broker.go
2024-09-27 22:02:22 +05:45

253 lines
6.4 KiB
Go

package main
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"strings"
"sync"
"time"
)
type DataItem map[string]interface{}
type NodeInfo struct {
Name string
Conn net.Conn
}
type Broker struct {
nodes map[string]NodeInfo
edges map[string]string
loops map[string][]string
conditions map[string]ConditionConfig
results map[string][]DataItem // Track task results by task ID
mu sync.Mutex
}
type ConditionConfig struct {
TrueNode string
FalseNode string
}
func NewBroker() *Broker {
return &Broker{
nodes: make(map[string]NodeInfo),
edges: make(map[string]string),
loops: make(map[string][]string),
conditions: make(map[string]ConditionConfig),
results: make(map[string][]DataItem),
}
}
func (b *Broker) RegisterNode(name string, conn net.Conn) {
b.mu.Lock()
defer b.mu.Unlock()
fmt.Printf("Registering node: %s\n", name)
b.nodes[name] = NodeInfo{Name: name, Conn: conn}
}
func (b *Broker) AddEdge(fromNode string, toNode string) {
b.mu.Lock()
defer b.mu.Unlock()
fmt.Printf("Adding edge from %s to %s\n", fromNode, toNode)
b.edges[fromNode] = toNode
}
func (b *Broker) AddLoop(loopNode string, targetNodes []string) {
b.mu.Lock()
defer b.mu.Unlock()
fmt.Printf("Adding loop at %s with targets: %v\n", loopNode, targetNodes)
b.loops[loopNode] = targetNodes
}
func (b *Broker) AddCondition(condNode string, trueNode string, falseNode string) {
b.mu.Lock()
defer b.mu.Unlock()
fmt.Printf("Adding condition at %s, True: %s, False: %s\n", condNode, trueNode, falseNode)
b.conditions[condNode] = ConditionConfig{
TrueNode: trueNode,
FalseNode: falseNode,
}
}
func (b *Broker) SendDataToNode(nodeName string, taskID string, data []DataItem, resultChannel chan []DataItem) {
b.mu.Lock()
node, exists := b.nodes[nodeName]
b.mu.Unlock()
if !exists {
fmt.Printf("Node %s not found!\n", nodeName)
return
}
fmt.Printf("Sending data to %s for task %s...\n", nodeName, taskID)
encoder := json.NewEncoder(node.Conn)
err := encoder.Encode(data)
if err != nil {
fmt.Printf("Error sending data to %s: %v\n", nodeName, err)
return
}
// Receive the processed data back from the node asynchronously
go func() {
decoder := json.NewDecoder(node.Conn)
var result []DataItem
err = decoder.Decode(&result)
if err != nil {
fmt.Printf("Error receiving data from %s for task %s: %v\n", nodeName, taskID, err)
return
}
fmt.Printf("Received processed data from %s for task %s\n", nodeName, taskID)
// Send the result to the result aggregation channel
resultChannel <- result
}()
}
func (b *Broker) DispatchData(startNode string, data []DataItem, taskID string) []DataItem {
finalResult := []DataItem{}
currentNode := startNode
resultChannel := make(chan []DataItem, len(data)) // Create a channel to handle async results
for {
b.mu.Lock()
nextNode, hasEdge := b.edges[currentNode]
loopTargets, hasLoop := b.loops[currentNode]
conditionConfig, hasCondition := b.conditions[currentNode]
b.mu.Unlock()
// Handle Loops (async dispatch)
if hasLoop {
var wg sync.WaitGroup
fmt.Printf("Dispatching to loop nodes from %s for task %s...\n", currentNode, taskID)
for _, targetNode := range loopTargets {
wg.Add(1)
go func(node string) {
defer wg.Done()
b.SendDataToNode(node, taskID, data, resultChannel)
}(targetNode)
}
// Wait for loop processing to complete
go func() {
wg.Wait()
close(resultChannel)
}()
// Collect async results
for res := range resultChannel {
finalResult = append(finalResult, res...)
}
b.AggregateResults(taskID, finalResult)
return finalResult // Exit after loop processing
}
// Handle Conditions
if hasCondition {
for _, item := range data {
resultChannel := make(chan []DataItem, 1)
go b.SendDataToNode(currentNode, taskID, []DataItem{item}, resultChannel)
select {
case result := <-resultChannel:
nextNode = conditionConfig.TrueNode
finalResult = append(finalResult, b.DispatchData(nextNode, result, taskID)...)
case <-time.After(5 * time.Second): // Timeout if no response
fmt.Printf("Condition check timed out at node: %s\n", currentNode)
nextNode = conditionConfig.FalseNode
}
}
b.AggregateResults(taskID, finalResult)
return finalResult // Exit after condition processing
}
// Handle simple edges (sequential flow)
if hasEdge {
b.SendDataToNode(currentNode, taskID, data, resultChannel)
select {
case result := <-resultChannel:
currentNode = nextNode
data = result
case <-time.After(5 * time.Second): // Timeout if no response
fmt.Printf("Processing timed out at node: %s\n", currentNode)
return finalResult
}
} else {
fmt.Printf("No edge found for node: %s, stopping...\n", currentNode)
break
}
}
b.AggregateResults(taskID, finalResult)
return finalResult
}
func (b *Broker) AggregateResults(taskID string, result []DataItem) {
b.mu.Lock()
defer b.mu.Unlock()
b.results[taskID] = append(b.results[taskID], result...)
fmt.Printf("Aggregated result for task %s: %v\n", taskID, b.results[taskID])
}
func (b *Broker) HandleConnections() {
listener, err := net.Listen("tcp", ":8081")
if err != nil {
fmt.Println("Error setting up TCP server:", err)
os.Exit(1)
}
defer listener.Close()
fmt.Println("Broker is listening on port 8081...")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting connection:", err)
continue
}
go func(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
nodeName, err := reader.ReadString('\n')
if err != nil {
fmt.Println("Error reading node name:", err)
return
}
nodeName = strings.TrimSpace(nodeName)
b.RegisterNode(nodeName, conn)
}(conn)
}
}
func main() {
broker := NewBroker()
// Set up the flow
broker.AddEdge("Node1", "Node2")
broker.AddLoop("Node2", []string{"Node3"})
broker.AddCondition("Node3", "Node4", "")
// Start the broker to listen for node connections
go broker.HandleConnections()
fmt.Println("Press ENTER to start the flow after nodes are connected...")
bufio.NewReader(os.Stdin).ReadString('\n')
// Example Data Items
dataItems := []DataItem{
{"id": 1, "value": "item1"},
{"id": 2, "value": "item2"},
{"id": 3, "value": "item3"},
}
taskID := "task-001" // Unique ID to track this task
finalResult := broker.DispatchData("Node1", dataItems, taskID)
fmt.Println("Final result after processing:", finalResult)
}