Files
monibuca/pkg/task/macro.go
2024-08-24 21:11:32 +08:00

198 lines
4.3 KiB
Go

package task
import (
"context"
"log/slog"
"m7s.live/m7s/v5/pkg/util"
"reflect"
"slices"
"sync"
"sync/atomic"
)
var idG atomic.Uint32
func GetNextTaskID() uint32 {
return idG.Add(1)
}
// MarcoTask include sub tasks
type MarcoTask struct {
Task
addSub chan ITask
children []ITask
lazyRun sync.Once
childrenDisposed chan struct{}
childDisposeListeners []func(ITask)
blocked bool
}
func (*MarcoTask) GetTaskType() TaskType {
return TASK_TYPE_MACRO
}
func (mt *MarcoTask) getMarcoTask() *MarcoTask {
return mt
}
func (mt *MarcoTask) Blocked() bool {
return mt.blocked
}
func (mt *MarcoTask) waitChildrenDispose() {
close(mt.addSub)
<-mt.childrenDisposed
}
func (mt *MarcoTask) OnChildDispose(listener func(ITask)) {
mt.childDisposeListeners = append(mt.childDisposeListeners, listener)
}
func (mt *MarcoTask) onChildDispose(child ITask) {
for _, listener := range mt.childDisposeListeners {
listener(child)
}
if mt.parent != nil {
mt.parent.onChildDispose(child)
}
if child.getParent() == mt {
child.dispose()
}
}
func (mt *MarcoTask) dispose() {
if mt.childrenDisposed != nil {
mt.OnBeforeDispose(mt.waitChildrenDispose)
}
mt.Task.dispose()
}
func (mt *MarcoTask) RangeSubTask(callback func(task ITask) bool) {
for _, task := range mt.children {
callback(task)
}
}
func (mt *MarcoTask) AddTaskLazy(t IMarcoTask) {
t.GetTask().parent = mt
}
func (mt *MarcoTask) AddTask(t ITask, opt ...any) (task *Task) {
mt.lazyRun.Do(func() {
if mt.parent != nil && mt.handler == nil {
mt.parent.AddTask(mt)
}
mt.childrenDisposed = make(chan struct{})
mt.addSub = make(chan ITask, 10)
go mt.run()
})
if task = t.GetTask(); task.handler == nil {
task.parentCtx = mt.Context
for _, o := range opt {
switch v := o.(type) {
case context.Context:
task.parentCtx = v
case Description:
task.Description = v
case RetryConfig:
task.retry = v
case *slog.Logger:
task.Logger = v
}
}
if task.parentCtx == nil {
panic("context is nil")
}
task.parent = mt
task.level = mt.level + 1
if task.ID == 0 {
task.ID = GetNextTaskID()
}
task.Context, task.CancelCauseFunc = context.WithCancelCause(task.parentCtx)
task.startup = util.NewPromise(task.Context)
task.shutdown = util.NewPromise(context.Background())
task.handler = t
if task.Logger == nil {
task.Logger = mt.Logger
}
}
if mt.IsStopped() {
task.startup.Reject(mt.StopReason())
return
}
mt.addSub <- t
return
}
func (mt *MarcoTask) Call(callback func() error) {
mt.Post(callback).WaitStarted()
}
func (mt *MarcoTask) Post(callback func() error) *Task {
task := CreateTaskByCallBack(callback, nil)
return mt.AddTask(task)
}
func (mt *MarcoTask) addChild(task ITask) int {
mt.children = append(mt.children, task)
return len(mt.children) - 1
}
func (mt *MarcoTask) removeChild(index int) {
mt.onChildDispose(mt.children[index])
mt.children = slices.Delete(mt.children, index, index+1)
}
func (mt *MarcoTask) run() {
cases := []reflect.SelectCase{{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(mt.addSub)}}
defer func() {
err := recover()
if err != nil {
mt.Stop(err.(error))
}
stopReason := mt.StopReason()
for _, task := range mt.children {
task.Stop(stopReason)
mt.onChildDispose(task)
}
mt.children = nil
close(mt.childrenDisposed)
}()
for {
mt.blocked = false
if chosen, rev, ok := reflect.Select(cases); chosen == 0 {
mt.blocked = true
if !ok {
return
}
if task := rev.Interface().(ITask); task.getParent() == mt {
index := mt.addChild(task)
if err := task.start(); err == nil {
cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(task.GetSignal())})
} else {
task.Stop(err)
mt.removeChild(index)
}
} else {
mt.addChild(task)
cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(task.GetSignal())})
}
} else {
taskIndex := chosen - 1
task := mt.children[taskIndex]
switch tt := task.(type) {
case IChannelTask:
tt.Tick(rev.Interface())
}
if !ok {
mt.removeChild(taskIndex)
cases = slices.Delete(cases, chosen, chosen+1)
}
}
if !mt.handler.keepalive() && len(mt.children) == 0 {
mt.Stop(ErrAutoStop)
}
}
}