package exec import ( "context" "errors" "fmt" "sync" "github.com/hashicorp/go-multierror" "gitlink.org.cn/cloudream/common/pkgs/future" "gitlink.org.cn/cloudream/common/utils/lo2" ) type binding struct { ID VarID Callback *future.SetValueFuture[VarValue] } type freeVar struct { ID VarID Value VarValue } type Executor struct { plan Plan vars map[VarID]freeVar bindings []*binding lock sync.Mutex store map[string]VarValue } func NewExecutor(plan Plan) *Executor { planning := Executor{ plan: plan, vars: make(map[VarID]freeVar), store: make(map[string]VarValue), } return &planning } func (s *Executor) Plan() *Plan { return &s.plan } func (s *Executor) Run(ctx *ExecContext) (map[string]VarValue, error) { c, cancel := context.WithCancel(ctx.Context) ctx = &ExecContext{ Context: c, Values: ctx.Values, } defer cancel() err := s.runOps(s.plan.Ops, ctx, cancel) if err != nil { return nil, err } return s.store, nil } func (s *Executor) runOps(ops []Op, ctx *ExecContext, cancel context.CancelFunc) error { lock := sync.Mutex{} var err error var wg sync.WaitGroup wg.Add(len(ops)) for i, arg := range ops { go func(op Op, index int) { defer wg.Done() if e := op.Execute(ctx, s); e != nil { lock.Lock() // 尽量不记录 Canceled 错误,除非没有其他错误 if errors.Is(e, context.Canceled) { if err == nil { err = context.Canceled } } else { err = multierror.Append(err, fmt.Errorf("%T: %w", op, e)) } lock.Unlock() cancel() } }(arg, i) } wg.Wait() return err } func (s *Executor) BindVar(ctx context.Context, id VarID) (VarValue, error) { s.lock.Lock() gv, ok := s.vars[id] if ok { delete(s.vars, id) s.lock.Unlock() return gv.Value, nil } callback := future.NewSetValue[VarValue]() s.bindings = append(s.bindings, &binding{ ID: id, Callback: callback, }) s.lock.Unlock() return callback.Wait(ctx) } func (s *Executor) PutVar(id VarID, value VarValue) *Executor { s.lock.Lock() defer s.lock.Unlock() for ib, b := range s.bindings { if b.ID != id { continue } b.Callback.SetValue(value) s.bindings = lo2.RemoveAt(s.bindings, ib) return s } // 如果没有绑定,则直接放入变量表中 s.vars[id] = freeVar{ID: id, Value: value} return s } func (s *Executor) Store(key string, val VarValue) { s.lock.Lock() defer s.lock.Unlock() s.store[key] = val } func BindVar[T VarValue](e *Executor, ctx context.Context, id VarID) (T, error) { v, err := e.BindVar(ctx, id) if err != nil { var def T return def, err } ret, ok := v.(T) if !ok { var def T return def, fmt.Errorf("binded var %v is %T, not %T", id, v, def) } return ret, nil } func BindArray[T VarValue](e *Executor, ctx context.Context, ids []VarID) ([]T, error) { ret := make([]T, len(ids)) for i := range ids { v, err := e.BindVar(ctx, ids[i]) if err != nil { return nil, err } v2, ok := v.(T) if !ok { var def T return nil, fmt.Errorf("binded var %v is %T, not %T", ids[i], v, def) } ret[i] = v2 } return ret, nil } func PutArray[T VarValue](e *Executor, ids []VarID, values []T) { for i := range ids { e.PutVar(ids[i], values[i]) } }