update: dependencies

This commit is contained in:
Oarkflow
2024-11-16 16:02:59 +05:45
parent 70f3cd4b2b
commit 3bfd26c8ed
6 changed files with 152 additions and 30 deletions

View File

@@ -83,7 +83,8 @@ var (
ContentType = "Content-Type" ContentType = "Content-Type"
AwaitResponseKey = "Await-Response" AwaitResponseKey = "Await-Response"
QueueKey = "Topic" QueueKey = "Topic"
TypeJson = "application/json" TypeJson = "application/json; charset=utf-8"
TypeHtml = "text/html; charset=utf-8"
HeaderKey = "headers" HeaderKey = "headers"
TriggerNode = "triggerNode" TriggerNode = "triggerNode"
) )

View File

@@ -1,14 +1,18 @@
package dag package dag
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/oarkflow/mq/sio"
"io" "io"
"net/http" "net/http"
"net/url"
"os" "os"
"time" "time"
"github.com/oarkflow/mq/jsonparser"
"github.com/oarkflow/mq/sio"
"github.com/oarkflow/mq" "github.com/oarkflow/mq"
"github.com/oarkflow/mq/consts" "github.com/oarkflow/mq/consts"
"github.com/oarkflow/mq/metrics" "github.com/oarkflow/mq/metrics"
@@ -36,6 +40,7 @@ func (tm *DAG) Handlers() {
metrics.HandleHTTP() metrics.HandleHTTP()
http.Handle("/", http.FileServer(http.Dir("webroot"))) http.Handle("/", http.FileServer(http.Dir("webroot")))
http.Handle("/notify", tm.SetupWS()) http.Handle("/notify", tm.SetupWS())
http.HandleFunc("GET /render", tm.Render)
http.HandleFunc("POST /request", tm.Request) http.HandleFunc("POST /request", tm.Request)
http.HandleFunc("POST /publish", tm.Publish) http.HandleFunc("POST /publish", tm.Publish)
http.HandleFunc("POST /schedule", tm.Schedule) http.HandleFunc("POST /schedule", tm.Schedule)
@@ -124,7 +129,7 @@ func (tm *DAG) request(w http.ResponseWriter, r *http.Request, async bool) {
} }
err = json.Unmarshal(payload, &request) err = json.Unmarshal(payload, &request)
if err != nil { if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest) http.Error(w, "Failed to unmarshal body", http.StatusBadRequest)
return return
} }
} else { } else {
@@ -145,6 +150,7 @@ func (tm *DAG) request(w http.ResponseWriter, r *http.Request, async bool) {
if request.Recurring { if request.Recurring {
opts = append(opts, mq.WithRecurring()) opts = append(opts, mq.WithRecurring())
} }
ctx = context.WithValue(ctx, "query_params", r.URL.Query())
var rs mq.Result var rs mq.Result
if request.Schedule { if request.Schedule {
rs = tm.ScheduleTask(ctx, request.Payload, opts...) rs = tm.ScheduleTask(ctx, request.Payload, opts...)
@@ -155,6 +161,19 @@ func (tm *DAG) request(w http.ResponseWriter, r *http.Request, async bool) {
json.NewEncoder(w).Encode(rs) json.NewEncoder(w).Encode(rs)
} }
func (tm *DAG) Render(w http.ResponseWriter, r *http.Request) {
ctx := mq.SetHeaders(r.Context(), map[string]string{consts.AwaitResponseKey: "true", "request_type": "render"})
ctx = context.WithValue(ctx, "query_params", r.URL.Query())
rs := tm.Process(ctx, nil)
content, err := jsonparser.GetString(rs.Payload, "content")
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", consts.TypeHtml)
w.Write([]byte(content))
}
func (tm *DAG) Request(w http.ResponseWriter, r *http.Request) { func (tm *DAG) Request(w http.ResponseWriter, r *http.Request) {
tm.request(w, r, true) tm.request(w, r, true)
} }
@@ -166,3 +185,25 @@ func (tm *DAG) Publish(w http.ResponseWriter, r *http.Request) {
func (tm *DAG) Schedule(w http.ResponseWriter, r *http.Request) { func (tm *DAG) Schedule(w http.ResponseWriter, r *http.Request) {
tm.request(w, r, false) tm.request(w, r, false)
} }
func GetTaskID(ctx context.Context) string {
if queryParams := ctx.Value("query_params"); queryParams != nil {
if params, ok := queryParams.(url.Values); ok {
if id := params.Get("taskID"); id != "" {
return id
}
}
}
return ""
}
func CanNextNode(ctx context.Context) string {
if queryParams := ctx.Value("query_params"); queryParams != nil {
if params, ok := queryParams.(url.Values); ok {
if id := params.Get("next"); id != "" {
return id
}
}
}
return ""
}

View File

@@ -26,9 +26,19 @@ const (
Iterator Iterator
) )
type NodeType int
func (c NodeType) IsValid() bool { return c >= Process && c <= Page }
const (
Process NodeType = iota
Page
)
type Node struct { type Node struct {
processor mq.Processor processor mq.Processor
Name string Name string
Type NodeType
Key string Key string
Edges []Edge Edges []Edge
isReady bool isReady bool
@@ -60,6 +70,7 @@ type DAG struct {
server *mq.Broker server *mq.Broker
consumer *mq.Consumer consumer *mq.Consumer
taskContext map[string]*TaskManager taskContext map[string]*TaskManager
iteratorNodes map[string]struct{}
conditions map[FromNode]map[When]Then conditions map[FromNode]map[When]Then
pool *mq.Pool pool *mq.Pool
taskCleanupCh chan string taskCleanupCh chan string
@@ -136,6 +147,7 @@ func NewDAG(name, key string, opts ...mq.Option) *DAG {
name: name, name: name,
key: key, key: key,
nodes: make(map[string]*Node), nodes: make(map[string]*Node),
iteratorNodes: make(map[string]struct{}),
taskContext: make(map[string]*TaskManager), taskContext: make(map[string]*TaskManager),
conditions: make(map[FromNode]map[When]Then), conditions: make(map[FromNode]map[When]Then),
taskCleanupCh: make(chan string), taskCleanupCh: make(chan string),
@@ -259,6 +271,9 @@ func (tm *DAG) AddNode(name, key string, handler mq.Processor, firstNode ...bool
Key: key, Key: key,
processor: con, processor: con,
} }
if handler.GetType() == "page" {
n.Type = Page
}
if tm.server.SyncMode() { if tm.server.SyncMode() {
n.isReady = true n.isReady = true
} }
@@ -305,6 +320,9 @@ func (tm *DAG) AddCondition(fromNode FromNode, conditions map[When]Then) *DAG {
func (tm *DAG) AddIterator(label, from string, targets ...string) *DAG { func (tm *DAG) AddIterator(label, from string, targets ...string) *DAG {
tm.Error = tm.addEdge(Iterator, label, from, targets...) tm.Error = tm.addEdge(Iterator, label, from, targets...)
tm.mu.Lock()
tm.iteratorNodes[from] = struct{}{}
tm.mu.Unlock()
return tm return tm
} }
@@ -349,10 +367,15 @@ func (tm *DAG) GetReport() string {
func (tm *DAG) ProcessTask(ctx context.Context, task *mq.Task) mq.Result { func (tm *DAG) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
tm.mu.Lock() tm.mu.Lock()
taskID := mq.NewID() if task.ID == "" {
manager := NewTaskManager(tm, taskID) task.ID = mq.NewID()
manager.createdAt = task.CreatedAt }
tm.taskContext[taskID] = manager manager, exists := tm.taskContext[task.ID]
if !exists {
manager = NewTaskManager(tm, task.ID, tm.iteratorNodes)
manager.createdAt = task.CreatedAt
tm.taskContext[task.ID] = manager
}
tm.mu.Unlock() tm.mu.Unlock()
if tm.consumer != nil { if tm.consumer != nil {
@@ -363,6 +386,18 @@ func (tm *DAG) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
} }
task.Topic = initialNode task.Topic = initialNode
} }
if manager.topic != "" {
task.Topic = manager.topic
canNext := CanNextNode(ctx)
if canNext != "" {
if n, ok := tm.nodes[task.Topic]; ok {
if len(n.Edges) > 0 {
task.Topic = n.Edges[0].To[0].Key
}
}
} else {
}
}
result := manager.processTask(ctx, task.Topic, task.Payload) result := manager.processTask(ctx, task.Topic, task.Payload)
if result.Error != nil { if result.Error != nil {
@@ -375,12 +410,9 @@ func (tm *DAG) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
} }
func (tm *DAG) check(ctx context.Context, payload []byte) (context.Context, *mq.Task, error) { func (tm *DAG) check(ctx context.Context, payload []byte) (context.Context, *mq.Task, error) {
tm.mu.RLock()
if tm.paused { if tm.paused {
tm.mu.RUnlock()
return ctx, nil, fmt.Errorf("unable to process task, error: DAG is not accepting any task") return ctx, nil, fmt.Errorf("unable to process task, error: DAG is not accepting any task")
} }
tm.mu.RUnlock()
if !tm.IsReady() { if !tm.IsReady() {
return ctx, nil, fmt.Errorf("unable to process task, error: DAG is not ready yet") return ctx, nil, fmt.Errorf("unable to process task, error: DAG is not ready yet")
} }
@@ -391,7 +423,18 @@ func (tm *DAG) check(ctx context.Context, payload []byte) (context.Context, *mq.
if tm.server.SyncMode() { if tm.server.SyncMode() {
ctx = mq.SetHeaders(ctx, map[string]string{consts.AwaitResponseKey: "true"}) ctx = mq.SetHeaders(ctx, map[string]string{consts.AwaitResponseKey: "true"})
} }
return ctx, mq.NewTask(mq.NewID(), payload, initialNode), nil taskID := GetTaskID(ctx)
tm.mu.RLock()
defer tm.mu.RUnlock()
if taskID != "" {
if _, exists := tm.taskContext[taskID]; !exists {
return ctx, nil, fmt.Errorf("provided task ID doesn't exist")
}
}
if taskID == "" {
taskID = mq.NewID()
}
return ctx, mq.NewTask(taskID, payload, initialNode), nil
} }
func (tm *DAG) Process(ctx context.Context, payload []byte) mq.Result { func (tm *DAG) Process(ctx context.Context, payload []byte) mq.Result {

View File

@@ -11,24 +11,30 @@ import (
) )
type TaskManager struct { type TaskManager struct {
createdAt time.Time createdAt time.Time
processedAt time.Time processedAt time.Time
status string status string
dag *DAG dag *DAG
nodeResults map[string]mq.Result nodeResults map[string]mq.Result
wg *WaitGroup wg *WaitGroup
taskID string taskID string
results []mq.Result results []mq.Result
mutex sync.Mutex iteratorNodes map[string]struct{}
mutex sync.Mutex
topic string
} }
func NewTaskManager(d *DAG, taskID string) *TaskManager { func NewTaskManager(d *DAG, taskID string, iteratorNodes map[string]struct{}) *TaskManager {
if iteratorNodes == nil {
iteratorNodes = make(map[string]struct{})
}
return &TaskManager{ return &TaskManager{
dag: d, dag: d,
nodeResults: make(map[string]mq.Result), nodeResults: make(map[string]mq.Result),
results: make([]mq.Result, 0), results: make([]mq.Result, 0),
taskID: taskID, taskID: taskID,
wg: NewWaitGroup(), iteratorNodes: iteratorNodes,
wg: NewWaitGroup(),
} }
} }
@@ -51,6 +57,7 @@ func (tm *TaskManager) dispatchFinalResult(ctx context.Context) mq.Result {
_ = tm.dag.server.NotifyHandler()(ctx, rs) _ = tm.dag.server.NotifyHandler()(ctx, rs)
} }
tm.dag.taskCleanupCh <- tm.taskID tm.dag.taskCleanupCh <- tm.taskID
tm.topic = rs.Topic
return rs return rs
} }

View File

@@ -9,6 +9,23 @@ import (
"time" "time"
) )
func (tm *TaskManager) renderResult(ctx context.Context) mq.Result {
if rs, ok := tm.nodeResults[tm.topic]; ok {
tm.updateTS(&rs)
return rs
}
var rs mq.Result
if len(tm.results) == 1 {
rs = tm.handleResult(ctx, tm.results[0])
} else {
rs = tm.handleResult(ctx, tm.results)
}
tm.updateTS(&rs)
tm.dag.callbackToConsumer(ctx, rs)
tm.topic = rs.Topic
return rs
}
func (tm *TaskManager) processTask(ctx context.Context, nodeID string, payload json.RawMessage) mq.Result { func (tm *TaskManager) processTask(ctx context.Context, nodeID string, payload json.RawMessage) mq.Result {
defer mq.RecoverPanic(mq.RecoverTitle) defer mq.RecoverPanic(mq.RecoverTitle)
node, ok := tm.dag.nodes[nodeID] node, ok := tm.dag.nodes[nodeID]
@@ -23,6 +40,10 @@ func (tm *TaskManager) processTask(ctx context.Context, nodeID string, payload j
go tm.processNode(ctx, node, payload) go tm.processNode(ctx, node, payload)
}() }()
tm.wg.Wait() tm.wg.Wait()
requestType, ok := mq.GetHeader(ctx, "request_type")
if ok && requestType == "render" {
return tm.renderResult(ctx)
}
return tm.dispatchFinalResult(ctx) return tm.dispatchFinalResult(ctx)
} }
@@ -46,15 +67,16 @@ func (tm *TaskManager) getConditionalEdges(node *Node, result mq.Result) []Edge
} }
func (tm *TaskManager) handleNextTask(ctx context.Context, result mq.Result) mq.Result { func (tm *TaskManager) handleNextTask(ctx context.Context, result mq.Result) mq.Result {
tm.topic = result.Topic
defer func() {
tm.wg.Done()
mq.RecoverPanic(mq.RecoverTitle)
}()
if result.Ctx != nil { if result.Ctx != nil {
if headers, ok := mq.GetHeaders(ctx); ok { if headers, ok := mq.GetHeaders(ctx); ok {
ctx = mq.SetHeaders(result.Ctx, headers.AsMap()) ctx = mq.SetHeaders(result.Ctx, headers.AsMap())
} }
} }
defer func() {
tm.wg.Done()
mq.RecoverPanic(mq.RecoverTitle)
}()
node, ok := tm.dag.nodes[result.Topic] node, ok := tm.dag.nodes[result.Topic]
if !ok { if !ok {
return result return result
@@ -70,6 +92,9 @@ func (tm *TaskManager) handleNextTask(ctx context.Context, result mq.Result) mq.
} else { } else {
tm.appendResult(result, false) tm.appendResult(result, false)
} }
if node.Type == Page {
return result
}
for _, edge := range edges { for _, edge := range edges {
switch edge.Type { switch edge.Type {
case Iterator: case Iterator:
@@ -88,6 +113,10 @@ func (tm *TaskManager) handleNextTask(ctx context.Context, result mq.Result) mq.
}(ctx, target, item) }(ctx, target, item)
} }
} }
}
}
for _, edge := range edges {
switch edge.Type {
case Simple: case Simple:
for _, target := range edge.To { for _, target := range edge.To {
ctx = mq.SetHeaders(ctx, map[string]string{consts.QueueKey: target.Key}) ctx = mq.SetHeaders(ctx, map[string]string{consts.QueueKey: target.Key})

1
go.mod
View File

@@ -8,6 +8,7 @@ require (
github.com/oarkflow/dipper v0.0.6 github.com/oarkflow/dipper v0.0.6
github.com/oarkflow/errors v0.0.6 github.com/oarkflow/errors v0.0.6
github.com/oarkflow/expr v0.0.11 github.com/oarkflow/expr v0.0.11
github.com/oarkflow/jet v0.0.4
github.com/oarkflow/json v0.0.13 github.com/oarkflow/json v0.0.13
github.com/oarkflow/xid v1.2.5 github.com/oarkflow/xid v1.2.5
github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_golang v1.20.5