Documentation
¶
Overview ¶
Example (ConditionalBranch) ¶
Example_conditionalBranch 演示条件分支与分支汇聚。 分支与汇聚都挂在依赖边上——边条件是 xdag 唯一的分支判断入口。
┌─ notify-ok (code == 200) ─┐
check ───┤ ├──> report (任一分支成功即可)
└─ rollback (code != 200) ─┘
package main
import (
"context"
"fmt"
"sort"
"github.com/xmapst/xdag"
"github.com/xmapst/xdag/xexpr"
)
// branchTask 是一个带条件的最小任务实现。
type branchTask struct {
name string
deps []string
edgeConds map[string]string
output map[string]any
err error
}
func (t *branchTask) Name() string { return t.name }
func (t *branchTask) Dependencies() []string { return t.deps }
func (t *branchTask) RetryPolicy() *xdag.RetryPolicy { return nil }
func (t *branchTask) PreExecution(context.Context, int64, map[string]any) {}
func (t *branchTask) PostExecution(context.Context, int64, map[string]any, error) {}
func (t *branchTask) Condition(dep string) string { return t.edgeConds[dep] }
func (t *branchTask) Execute(context.Context, int64, map[string]any) (map[string]any, error) {
return t.output, t.err
}
func main() {
tasks := map[string]xdag.Task{
"check": &branchTask{
name: "check",
output: map[string]any{"code": 500},
},
"notify-ok": &branchTask{
name: "notify-ok", deps: []string{"check"},
edgeConds: map[string]string{"check": `Output("check").code == 200`},
},
"rollback": &branchTask{
name: "rollback", deps: []string{"check"},
edgeConds: map[string]string{"check": `Output("check").code != 200`},
},
"report": &branchTask{
name: "report", deps: []string{"notify-ok", "rollback"},
// 分支汇聚:默认要求所有依赖成功,这里改为任一成功即可
edgeConds: map[string]string{
"notify-ok": `Succeeded(Dep)`,
"rollback": `Succeeded(Dep)`,
},
},
}
dag, err := xdag.New(tasks, xdag.WithEvaluator(xexpr.New()))
if err != nil {
panic(err)
}
if _, err = dag.Execute(context.Background()); err != nil {
panic(err)
}
states := dag.States()
names := make([]string, 0, len(states))
for name := range states {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Printf("%-10s %s\n", name, states[name])
}
}
Output: check success notify-ok skipped report success rollback success
Example (EdgeCondition) ¶
Example_edgeCondition 演示用边条件表达「可选依赖」: notify 这条边由全局变量决定是否参与门禁,失活时 notify 失败也不会拦住 deploy。
package main
import (
"context"
"errors"
"fmt"
"github.com/xmapst/xdag"
"github.com/xmapst/xdag/xexpr"
)
// branchTask 是一个带条件的最小任务实现。
type branchTask struct {
name string
deps []string
edgeConds map[string]string
output map[string]any
err error
}
func (t *branchTask) Name() string { return t.name }
func (t *branchTask) Dependencies() []string { return t.deps }
func (t *branchTask) RetryPolicy() *xdag.RetryPolicy { return nil }
func (t *branchTask) PreExecution(context.Context, int64, map[string]any) {}
func (t *branchTask) PostExecution(context.Context, int64, map[string]any, error) {}
func (t *branchTask) Condition(dep string) string { return t.edgeConds[dep] }
func (t *branchTask) Execute(context.Context, int64, map[string]any) (map[string]any, error) {
return t.output, t.err
}
func main() {
tasks := map[string]xdag.Task{
"build": &branchTask{name: "build", output: map[string]any{"artifact": "app.tar"}},
"notify": &branchTask{name: "notify", err: errors.New("notify service down")},
"deploy": &branchTask{
name: "deploy", deps: []string{"build", "notify"},
// build 边无条件生效;notify 边只在严格模式下参与门禁
edgeConds: map[string]string{"notify": `Vars["strictNotify"] == true`},
output: map[string]any{"ok": true},
},
}
for _, strict := range []bool{false, true} {
dag, err := xdag.New(tasks,
xdag.WithEvaluator(xexpr.New()),
xdag.WithVars(map[string]any{"strictNotify": strict}),
)
if err != nil {
panic(err)
}
_, _ = dag.Execute(context.Background()) // notify 必然失败
fmt.Printf("strictNotify=%-5v deploy=%s\n", strict, dag.State("deploy"))
}
}
Output: strictNotify=false deploy=success strictNotify=true deploy=upstream_skipped
Index ¶
- Constants
- Variables
- func HasCycle(tasks map[string]Task) booldeprecated
- func IsAncestorFunc(name string) bool
- func Validate(tasks map[string]Task) error
- type ConditionErrorPolicy
- type Conditional
- type Dagcuter
- type Env
- type Evaluator
- type Option
- type Program
- type RefKind
- type Reference
- type Referencer
- type RetryExecutor
- type RetryPolicy
- type State
- type Task
Examples ¶
Constants ¶
const InputsField = "Inputs"
InputsField 是 Env 中按依赖名索引的字段名,供 Evaluator 做静态引用分析。
Variables ¶
var ( // ErrCircularDependency 任务图中存在环。 ErrCircularDependency = errors.New("circular dependency detected") // ErrUnknownDependency 任务声明了一个不存在的依赖。 ErrUnknownDependency = errors.New("unknown dependency") )
var ErrAlreadyExecuted = errors.New("dag already executed")
ErrAlreadyExecuted Dagcuter 的入度表在执行过程中被消费,不能重复执行。
var ErrNoEvaluator = errors.New("task declares a condition but no evaluator is configured")
ErrNoEvaluator 任务声明了条件表达式,但没有通过 WithEvaluator 配置表达式引擎。
var MaxTasks = 150
MaxTasks 是默认的任务数量上限。可用 WithMaxTasks 按实例覆盖。
Functions ¶
func IsAncestorFunc ¶ added in v0.0.3
IsAncestorFunc 报告 name 是否为以任务名为参数的 Env 函数,供 Evaluator 做静态引用分析。
Types ¶
type ConditionErrorPolicy ¶ added in v0.0.3
type ConditionErrorPolicy uint8
ConditionErrorPolicy 决定条件表达式求值出错时任务的归宿。
const ( // FailOnConditionError 求值出错视为任务失败(默认)。 // 表达式写错与条件不成立是两回事,静默跳过会让问题难以定位。 FailOnConditionError ConditionErrorPolicy = iota // SkipOnConditionError 求值出错视为条件不成立,任务被跳过。 SkipOnConditionError )
type Conditional ¶ added in v0.0.3
type Conditional interface {
// Condition 返回依赖边 dep -> 当前任务 的条件表达式。
// 返回空串表示该边无条件生效。
Condition(dep string) string
}
Conditional 是 Task 的可选扩展接口,也是 xdag 中唯一的分支判断入口。
边条件回答的是「这条依赖要不要参与门禁」:求值为 false 时该边失活, 即本任务不再要求这个依赖成功;求值为 true 或未声明时该边生效,要求依赖必须成功。
由此派生出两种终态:
- 所有入边都失活 → StateSkipped,没有任何依赖构成执行本任务的理由
- 存在生效的边但其上游未成功 → StateUpstreamSkipped
边条件求值时 Env.Dep 为该边的上游任务名,因此一条 Succeeded(Dep) 可以复用到所有边上。
注意:分支判断挂在边上,因此没有依赖的根任务无法附加条件——它没有入边。 需要按开关控制入口时,请在任务自身的 Execute 中处理,或引入一个显式的前置任务。
type Dagcuter ¶
func (*Dagcuter) ExecutionOrder ¶
ExecutionOrder 返回成功执行的任务的完成顺序。 被跳过与失败的任务不在其中,需要完整视图请使用 States。
type Env ¶ added in v0.0.3
type Env struct {
// Task 是当前任务名。
Task string
// Attempt 是当前尝试次数,从 1 开始。
// 分支条件在重试之前求值一次,因此恒为 0;只有 RetryPolicy.RetryIf 会用到它。
Attempt int64
// Dep 是当前正在求值的依赖边的上游任务名。边条件中恒为非空,RetryIf 中为空。
Dep string
// Error 是最近一次失败的错误信息,仅在 RetryPolicy.RetryIf 中非空。
Error string
// Vars 是通过 WithVars 注入的全局变量。
Vars map[string]any
// Inputs 是直接依赖的输出,key 为依赖任务名。
// 被跳过或失败的依赖不会出现在其中。
Inputs map[string]map[string]any
// contains filtered or unexported fields
}
Env 是条件表达式的求值环境。
导出字段与导出方法可以直接在表达式中书写,例如:
Output("check").code == 200
Inputs["fetch-user"]["vip"] == true and Vars["env"] == "prod"
Succeeded(Dep)
可见范围被限制在当前任务的祖先集合内:访问非祖先任务会返回错误。 这是刻意的约束——非祖先任务在本任务被调度时不保证已经完成,读取它的输出 会让求值结果随调度时序变化。
type Evaluator ¶ added in v0.0.3
Evaluator 把表达式文本编译成可复用的 Program。 实现由 New 在构建阶段调用,因此表达式的语法与类型错误会在 New 中一次性暴露。
type Option ¶ added in v0.0.3
type Option func(*options)
Option 用于配置 Dagcuter。
func WithConditionErrorPolicy ¶ added in v0.0.3
func WithConditionErrorPolicy(p ConditionErrorPolicy) Option
WithConditionErrorPolicy 设置条件求值出错时的处理策略,默认 FailOnConditionError。
func WithEvaluator ¶ added in v0.0.3
WithEvaluator 注入表达式引擎。只有配置了引擎,实现 Conditional 的任务 以及 RetryPolicy.RetryIf 才能使用表达式。 官方实现见子包 github.com/xmapst/xdag/xexpr。
func WithMaxTasks ¶ added in v0.0.3
WithMaxTasks 覆盖本次构建的任务数量上限,默认取包级变量 MaxTasks。
type Referencer ¶ added in v0.0.3
type Referencer interface {
References() []Reference
}
Referencer 是 Program 的可选扩展接口。实现了它的 Program 会在 New 阶段接受 引用范围校验:越界引用从运行期错误提前为构建期错误。
只有字面量参数能被静态分析,Output(Dep) 这类动态引用仍由运行期检查兜底。
type RetryExecutor ¶
type RetryExecutor struct {
// contains filtered or unexported fields
}
func (*RetryExecutor) ExecuteWithRetry ¶
func (r *RetryExecutor) ExecuteWithRetry(ctx context.Context, taskName string, fn func(attempt int64) error) error
ExecuteWithRetry 带重试的执行函数
type RetryPolicy ¶
type RetryPolicy struct {
Interval time.Duration `json:"interval" yaml:"interval"`
MaxInterval time.Duration `json:"maxInterval" yaml:"maxInterval"`
MaxAttempts int64 `json:"maxAttempts" yaml:"maxAttempts"`
Multiplier float64 `json:"multiplier" yaml:"multiplier"`
// RetryIf 是可选的重试条件表达式,在每次失败后、真正发起下一次尝试之前求值。
// 求值为 false 时立即放弃剩余尝试;空串表示无条件重试,与旧行为一致。
//
// 表达式中可以使用 Env.Error(最近一次失败的错误信息)与 Env.Attempt,例如:
//
// Error matches "timeout|connection reset"
// not (Error contains "invalid argument")
//
// 该表达式在 New 阶段编译,运行期修改 RetryPolicy 不会生效。
RetryIf string `json:"retryIf" yaml:"retryIf"`
}
type Task ¶
type Task interface {
Name() string
Dependencies() []string
// RetryPolicy returns the retry policy for the task
RetryPolicy() *RetryPolicy
// PreExecution is called before Execute
PreExecution(ctx context.Context, attempt int64, input map[string]any)
// Execute is called after PreExecution
Execute(ctx context.Context, attempt int64, input map[string]any) (map[string]any, error)
// PostExecution is called after Execute
PostExecution(ctx context.Context, attempt int64, output map[string]any, err error)
}