Documentation
¶
Overview ¶
Package taskgate 是一个轻量的 Go 任务排队限流库:排队、限流、重试、依赖、取消。 它是库不是服务:不读环境变量、不读配置文件,只吃调用方传进来的 Config。
Index ¶
- Variables
- func CanTransition(from, to Status) bool
- func ParentFailureReason(parentID string, parentStatus Status) string
- func RenewLease(ctx context.Context) error
- type Broker
- type BrokerOptions
- type ChildAction
- type Clock
- type Config
- type Duration
- type ErrSkipRetry
- type ErrThrottled
- type FailKind
- type Filter
- type Gate
- func (g *Gate) Cancel(ctx context.Context, id string) error
- func (g *Gate) Get(ctx context.Context, id string) (*Task, error)
- func (g *Gate) Handle(taskType string, h Handler)
- func (g *Gate) History(ctx context.Context, businessKey string) ([]*Task, error)
- func (g *Gate) List(ctx context.Context, f Filter) ([]*Task, error)
- func (g *Gate) Overview(ctx context.Context) (map[string]map[Status]int64, error)
- func (g *Gate) Replay(ctx context.Context, executionID string, opts ...ReplayOption) (string, error)
- func (g *Gate) ReplayByKey(ctx context.Context, businessKey string, opts ...ReplayOption) (string, error)
- func (g *Gate) Run(ctx context.Context) error
- func (g *Gate) Shutdown(ctx context.Context) error
- func (g *Gate) Stats(ctx context.Context, queue string) (QueueStats, error)
- func (g *Gate) Submit(ctx context.Context, taskType string, payload json.RawMessage, ...) (string, error)
- func (g *Gate) Wait(ctx context.Context, id string) (json.RawMessage, error)
- type Handler
- type LimiterProvider
- type ParentFailurePolicy
- type ParentState
- type QueueConfig
- type QueueLimiter
- type QueueStats
- type QuotaGate
- type QuotaProvider
- type QuotaReservation
- type ReplayOption
- type ReplayRequest
- type Status
- type SubmitDecision
- type SubmitOption
- type Task
- type TaskExistsError
- type TaskFailedError
- type Ticker
Constants ¶
This section is empty.
Variables ¶
var ( // ErrTaskExists 任务已存在:带 BusinessKey 提交撞键(此时错误可 errors.As 出 // *TaskExistsError 拿链尾信息),或预置 ID 撞主键(测试/嵌入入口的存储防御)。 ErrTaskExists = errors.New("taskgate: task already exists") // ErrTaskNotFound 任务不存在(Get/Cancel 找不到,或 Enqueue 时父任务缺失)。 ErrTaskNotFound = errors.New("taskgate: task not found") // ErrLeaseLost 租约令牌不匹配:任务已被回收或被别人重新认领,结果作废。 ErrLeaseLost = errors.New("taskgate: lease lost") // ErrTaskCanceled Heartbeat 发现任务被打了取消标记,scheduler 该 cancel handler 的 ctx 了。 ErrTaskCanceled = errors.New("taskgate: task canceled") // ErrAlreadyFinal 对已经进终态的任务再 Cancel。 ErrAlreadyFinal = errors.New("taskgate: task already in final state") // ErrUnknownType Run 时遇到没注册 handler 的任务类型(Submit 不校验)。 ErrUnknownType = errors.New("taskgate: no handler registered for task type") // ErrShutdown Gate 已经 Shutdown,拒绝新提交。 ErrShutdown = errors.New("taskgate: gate is shut down") // ErrNoTask 在任务 handler 之外的 ctx 上调 RenewLease:ctx 里没有续租闭包。 ErrNoTask = errors.New("taskgate: no task associated with context") // ErrReplayNotFinal Replay 的目标执行还没进终态(只有终态执行可被重放)。 ErrReplayNotFinal = errors.New("taskgate: replay target not in final state") // ErrAlreadyReplayed Replay 的目标已被重放过:历史链不分叉,每个执行至多被重放一次, // 重放只能打在链尾(最新执行)上。 ErrAlreadyReplayed = errors.New("taskgate: execution already replayed (chain must not fork)") // ErrCompletedNotAllowed 重放 completed 的执行必须显式带 AllowCompleted() 选项, // 防止误触发重复计费。 ErrCompletedNotAllowed = errors.New("taskgate: replaying a completed execution requires AllowCompleted") )
哨兵错误:全部导出,调用方用 errors.Is 判断。
Functions ¶
func CanTransition ¶
CanTransition 导出状态机校验,给 memorybroker/sqlitebroker 等后端包在 每次写状态前做统一防御(合同要求:所有写入先过 canTransition 表)。 Phase 1 只定义了包内的 canTransition,后端在别的包里够不着,这里补一个只读出口。
func ParentFailureReason ¶
ParentFailureReason 连锁取消时写进子任务 LastError 的固定文案。 固定下来是为了 brokertest 能逐字断言,三个后端文案一致。
func RenewLease ¶
RenewLease 在 handler 里手动给当前任务续租(lease_until = now + LeaseTTL)。 自动档(默认)也可以调,与自动心跳互不干扰;手动档(QueueConfig.ManualHeartbeat=true) 必须靠它保活。ctx 必须是 handler 收到的那个任务 ctx(或它的子 ctx)。返回值:
- nil:续租成功;
- ErrTaskCanceled:任务已被外部 Cancel(续租照做),此时任务 ctx 已被 cancel, handler 应尽快退出;
- ErrLeaseLost:租约已丢(任务被 reaper 回收),结果注定作废,任务 ctx 已被 cancel,handler 应立即放弃;
- ErrNoTask:ctx 不是任务 ctx(handler 之外调用);
- 其他错误(网络抖动等):续租没成也没丢,handler 可稍后重试。
Types ¶
type Broker ¶
type Broker interface {
Init(opts BrokerOptions) error // New(cfg) 时调用一次,Dequeue 前必须先 Init
Enqueue(ctx context.Context, t *Task) error
Dequeue(ctx context.Context, queues []string) (*Task, error)
Ack(ctx context.Context, id, leaseToken string, result []byte) error
Fail(ctx context.Context, id, leaseToken, errMsg string, kind FailKind, retryAt time.Time) error
Cancel(ctx context.Context, id string) error
FinishCanceled(ctx context.Context, id, leaseToken string) error
Requeue(ctx context.Context, id, leaseToken string) error
Heartbeat(ctx context.Context, id, leaseToken string) error
Get(ctx context.Context, id string) (*Task, error)
Replay(ctx context.Context, req ReplayRequest) (*Task, error)
List(ctx context.Context, f Filter) ([]*Task, error)
QueueLen(ctx context.Context, queue string) (int, error)
Counts(ctx context.Context) (map[string]map[Status]int64, error)
ReapExpired(ctx context.Context) (int, error)
Close() error
}
Broker 存储后端接口。只收 memory/sqlite/redis 三后端都能同语义实现的方法(宪法第 II 条); 各方法的行为合同见 contracts/broker-contract.md,由 brokertest 套件统一验收。
type BrokerOptions ¶
type BrokerOptions struct {
LeaseTTL map[string]time.Duration // 队列→租约 TTL
DefaultLeaseTTL time.Duration // 缺省 60s
LeaseLostMax int // 缺省 3
ThrottledMax int // 缺省 100
Notify func(Task) // 状态流转回调,可 nil;必须在锁/事务外异步调
Clock Clock // 可 nil=真时钟
}
BrokerOptions New(cfg) 装配时传给后端的运行参数,签名照 contracts/broker-contract.md。
type ChildAction ¶
type ChildAction int
ChildAction 父任务到终态之后,对一个直接子任务要执行的动作。
const ( // ChildNone 什么都不做:计数减了但还没减到 0,或者子任务已经在终态。 ChildNone ChildAction = iota // ChildWake 唤醒:子任务 blocked → pending(父全部满足了)。 ChildWake // ChildCancel 连锁取消:子任务应流转到 canceled(FailFast 且父失败/取消)。 // 调用方仍需过 canTransition 校验;若子在 running 等特殊状态,由调用方自行防御处理。 ChildCancel )
func DecideOnParentFinal ¶
func DecideOnParentFinal(parentStatus, childStatus Status, policy ParentFailurePolicy, pendingParents int) (int, ChildAction)
DecideOnParentFinal 判定"父任务进入终态 parentStatus 之后,对一个子任务怎么办"。 pendingParents 传入子任务当前剩余的未终态父计数(调用方在锁/事务内读出), 返回新的计数(递减不为负)与动作。约定:
- 子任务已是终态(比如已被手动取消)→ 不动。
- 父是 completed,或策略是 IgnoreParentFail(此时父任何终态都算满足)→ 计数减一; 减到 0 且子还在 blocked → 唤醒。
- 父是 failed/canceled 且策略是 FailFast → 连锁取消(计数保持原样,反正任务要没了)。
type Clock ¶
type Clock interface {
// Now 当前时刻。
Now() time.Time
// After 到点后往返回的 channel 发一次当前时刻。
After(d time.Duration) <-chan time.Time
// Sleep 睡 d,ctx 先取消就提前返回 ctx.Err()。
Sleep(ctx context.Context, d time.Duration) error
// NewTicker 周期滴答,给 reaper/心跳循环用。
NewTicker(d time.Duration) Ticker
}
Clock 可注入的时钟。租约、退避、限流全部通过它拿时间, 这样测试里用 fakeclock 手动推进,不用真 sleep(宪法第 V 条)。
type Config ¶
type Config struct {
Broker Broker `yaml:"-" json:"-"`
Queues map[string]QueueConfig `yaml:"queues" json:"queues"`
Routes map[string]string `yaml:"routes" json:"routes"` // Type → Queue
DefaultQueue QueueConfig `yaml:"default_queue" json:"default_queue"`
OnStateChange func(Task) `yaml:"-" json:"-"`
LeaseLostMax int `yaml:"lease_lost_max" json:"lease_lost_max"` // 0 补默认 3
ThrottledMax int `yaml:"throttled_max" json:"throttled_max"` // 0 补默认 100
}
Config 全局配置。库不读 env/文件,应用自己 unmarshal 好再传进来; Broker 和 OnStateChange 是运行期对象,序列化时跳过。
type Duration ¶
Duration 包一层 time.Duration,让 yaml/json 配置里能直接写 "10m"、"60s" 这种人话。
func (Duration) MarshalText ¶
MarshalText 序列化回 "10m0s" 这种标准格式。
func (*Duration) UnmarshalText ¶
UnmarshalText 支持 "10m" 这类写法,yaml 和 json 解码都走这里。
type ErrSkipRetry ¶
type ErrSkipRetry struct {
Err error
}
ErrSkipRetry handler 返回它表示"这个错没救,别重试了",任务直接进 failed。 必须按值返回(errors.As 按值匹配),不要返回其指针。
func (ErrSkipRetry) Unwrap ¶
func (e ErrSkipRetry) Unwrap() error
Unwrap 让 errors.Is/As 能穿透到里面包的业务错误。
type ErrThrottled ¶
ErrThrottled handler 返回它表示"被网关限流了,过 RetryAfter 再来": 不占 Attempts,只涨 Throttled 计数,封顶(默认 100)才进 failed。 必须按值返回(errors.As 按值匹配),不要返回其指针。
type Filter ¶
type Filter struct {
Type string
Queue string
Status Status
BusinessKey string // 非空时只返回该业务键下的执行;与其余条件是 AND 关系
Limit int // 0=不限
Offset int // 排序后跳过的条数,0=不跳过
}
Filter List 的过滤条件,零值字段表示不过滤。
排序与分页合同(M3 定型,三后端一致,见 contracts/broker-contract.md):
- 结果一律按 (CreatedAt, ID) 升序:CreatedAt 由 broker 落库时统一写, 同一毫秒内再按 ID 定序,保证全序;
- 执行顺序写死:先按 Type/Queue/Status 过滤 → 排序 → 跳过 Offset 条 → 取 Limit 条;
- Offset ≥ 匹配总数 → 返回空列表(nil error);Offset < 0 按 0 处理;
- 翻页弱一致:翻页期间数据变动不承诺快照一致,只承诺"未变动的任务不丢不重"。
type Gate ¶
type Gate struct {
// contains filtered or unexported fields
}
Gate 是 taskgate 的统一门面:提交、查询、等待、消费全从这里走。 一个 Gate 既可以只当生产者(New 后直接 Submit,不 Handle 不 Run), 也可以注册 handler 后 Run 起来当消费者,两者共用同一个 Broker。
func (*Gate) Cancel ¶
Cancel 取消任务(US6):
- blocked/pending/retrying:后端直接置 canceled 并向下传播(FailFast 子连锁取消);
- running:后端只打取消标记,然后看任务是否正在本进程跑——在的话立即 cancel 它的 handler ctx(不在本进程跑的,由持有它的进程下一次 Heartbeat 发现标记); handler 退出后 scheduler 调 FinishCanceled 落库 canceled;
- 终态:返回 ErrAlreadyFinal;不存在:返回 ErrTaskNotFound。
func (*Gate) History ¶
History 枚举该 BusinessKey 下的执行历史链,链序(旧 → 新),链尾即最新执行; 键不存在返回空切片。它是 List(Filter{BusinessKey: key}) 的便捷封装。
func (*Gate) Replay ¶
func (g *Gate) Replay(ctx context.Context, executionID string, opts ...ReplayOption) (string, error)
Replay 按 ExecutionID 重放一次终态执行:创建**新执行**(新 ID、ReplayOf 指回目标、 三计数清零、默认复制目标 Payload)进入正常调度,目标记录逐字段不变。 目标必须是其链的链尾且已终态;completed 需显式 AllowCompleted()。返回新执行的 ID。
func (*Gate) ReplayByKey ¶
func (g *Gate) ReplayByKey(ctx context.Context, businessKey string, opts ...ReplayOption) (string, error)
ReplayByKey 按 BusinessKey 重放,天然作用于链尾(该键下最新执行)。语义同 Replay。
func (*Gate) Run ¶
Run 启动消费:按注册过 handler 的类型对应的队列起认领循环,阻塞到 ctx 取消或 Shutdown, 然后停止认领、等在跑任务全部收尾后返回(因 Shutdown 退出同样返回 nil)。 生命周期细节在 scheduler.go。
func (*Gate) Shutdown ¶
Shutdown 优雅停止(US7):
- 一进门置停机标记,此后 Submit 一律返回 ErrShutdown,认领循环停止拿新任务;
- 等所有在跑任务善终(Run 也随之退出并返回 nil);
- ctx 先到期:cancel 各在跑任务的 handler ctx,等 handler 退出后把这些任务 Requeue 回 pending(三计数与 RunAt 全不动),返回 ctx 的超时错误;
- 后台 goroutine(认领循环/心跳/reaper)全部同步收尾,返回后零泄漏;
- 重复调用幂等:第二次直接返回 nil。
注意 Shutdown 的打断不是用户取消:被打断的任务回 pending 等下次重跑,不会进 canceled。
type LimiterProvider ¶
type LimiterProvider interface {
// QueueLimiter 按队列配置构造该队列的限流器;出错时 Gate.Run 直接返回该错误。
QueueLimiter(queue string, qc QueueConfig) (QueueLimiter, error)
}
LimiterProvider 后端的**可选能力接口**:能为队列提供跨进程共享的限流器。
限流不是所有后端都能做(memory/sqlite 没有跨进程共享的介质),进不了 Broker 接口的"最小公倍数",所以单独拆成能力接口。scheduler 装配限流器时只做 `broker.(LimiterProvider)` 这一次**能力断言**——断言的是接口不是具体后端类型, 上层依然不 import 任何后端包,不违反宪法 II.2"上层不特判后端": 新后端想提供分布式限流,实现本接口即可;memory/sqlite 不实现, scheduler 自动退回进程内限流(localLimiter),行为与 M1 完全一致。
实现约束:QueueLimiter 的构造必须廉价、不得持有需要显式释放的资源—— 本接口没有 Close,某个队列构造失败时之前已建成的限流器不会被回收, 构造期占了资源就是泄漏(redisbroker 的实现只是复用 Broker 连接拼参数,零资源)。
type ParentFailurePolicy ¶
type ParentFailurePolicy string
ParentFailurePolicy 父任务失败时子任务怎么办。
const ( // FailFast 父任务失败/取消 → 子任务连锁取消(默认)。 FailFast ParentFailurePolicy = "fail_fast" // IgnoreParentFail 父任务只要进了终态(哪怕失败)就照常唤醒子任务。 // 注:同名选项函数是 IgnoreParentFailure(),常量名少个 ure 是为了避开重名。 IgnoreParentFail ParentFailurePolicy = "ignore_parent_failure" )
type ParentState ¶
ParentState 做决策需要的父任务快照:只要 ID(拼错误文案用)和状态。
type QueueConfig ¶
type QueueConfig struct {
Workers int `yaml:"workers" json:"workers"`
RPS float64 `yaml:"rps" json:"rps"` // 0 = 不限速
Burst int `yaml:"burst" json:"burst"` // 0 时取 max(1, int(RPS))
LeaseTTL Duration `yaml:"lease_ttl" json:"lease_ttl"` // 0 补默认 60s
// ManualHeartbeat 手动续租开关。默认 false:scheduler 给每个在跑任务起自动心跳,
// 每 LeaseTTL/3 续租一次。true:不起自动心跳,handler 必须自己定期调
// taskgate.RenewLease 保活,否则租约到期会被 reaper 回收(LeaseLost+1)。
// 注意手动档下跨进程 Cancel 只能靠 handler 下一次 RenewLease 发现
// (返回 ErrTaskCanceled);handler 一直不续租,则由租约过期兜底回收。
// 本地 Cancel(同进程)不受影响,依然即时打断 handler 的 ctx。
ManualHeartbeat bool `yaml:"manual_heartbeat" json:"manual_heartbeat"`
// 周期配额(spec 006,硬配额):每个固定时长窗口最多启动 QuotaLimit 次 handler。
// 窗口对齐 epoch(windowStart = now/period×period),时间取共享介质的服务端钟,
// 不是自然日/自然月;单位是"handler 启动次数",不是任务数(重试的再认领同样计数)。
// QuotaLimit=0 完全不启用(零开销);启用时 QuotaPeriod 必须 >0,且后端必须实现
// QuotaProvider 能力接口,否则 New() 直接报错——配额没有静默降级。
QuotaLimit int `yaml:"quota_limit" json:"quota_limit"`
QuotaPeriod Duration `yaml:"quota_period" json:"quota_period"`
// QuotaKey 配额键,空 = 队列名。多个队列配同一个 key 时共享同一份窗口预算
// (打同一个网关额度的场景),此时各队列的 (QuotaLimit, QuotaPeriod) 必须一致。
QuotaKey string `yaml:"quota_key" json:"quota_key"`
}
QueueConfig 单个队列的限流参数。
type QueueLimiter ¶
type QueueLimiter interface {
// AcquireSlot 占一个并发槽,占不到就阻塞;ctx 取消返回 ctx.Err()。
AcquireSlot(ctx context.Context) error
// ReleaseSlot 归还并发槽。必须和 AcquireSlot 一一配对。
ReleaseSlot()
// WaitToken 等一个 RPS 令牌;不限速时立即放行;ctx 取消返回其错误。
WaitToken(ctx context.Context) error
}
QueueLimiter 单个队列的限流器抽象,两层独立生效:
- 并发槽(AcquireSlot/ReleaseSlot):限"同时在跑多少个";
- RPS 令牌(WaitToken):限"每秒新启动多少个"。
scheduler 只依赖这个接口,不关心限流器是进程内的还是跨进程共享的: 后端实现了 LimiterProvider 就用后端给的,否则用进程内的 localLimiter。 两层怎么配合用见 scheduler.claimLoop:先占槽、再等令牌。
type QueueStats ¶
type QueueStats struct {
Workers int `json:"workers"` // 配置的并发上限
Running int `json:"running"` // 本进程正在执行的任务数(纯生产者恒为 0)
QueueLen int `json:"queue_len"` // 积压:pending + retrying
RPS float64 `json:"rps"` // 配置的限速,0 = 不限
// 周期配额状态位(spec 006):"队列不动了"必须能靠这两个位区分原因。
QuotaExhausted bool `json:"quota_exhausted"` // 本窗口额度已尽,认领暂停等下窗
QuotaStalled bool `json:"quota_stalled"` // 配额介质不可达,fail-closed 暂停中
}
QueueStats 单个队列的水位:配置的并发/限速 + 当前在跑数 + 积压长度 + 配额状态位。
type QuotaGate ¶
type QuotaGate interface {
// Reserve 原子预留一份额度:在共享介质内一个原子单位完成
// "取服务端时间 → 算窗口 → 检查余额 → 扣减",检查与扣减之间没有窗口。
// 返回三态:
// - res≠nil:预留成功,res.Window 是本次预留落在的窗口起点;
// - res==nil 且 err==nil:本窗口额度耗尽(**不是错误**,等下个窗口);
// - err≠nil:介质故障,调用方必须 fail-closed(零放行,退避重试)。
Reserve(ctx context.Context) (*QuotaReservation, error)
// Release 尽力退还一份预留(认领扑空/出错的补偿),只作用于 r 的窗口;
// 窗口已切走则落空无害。失败时调用方不重试——该份额度当 leaked(视同消耗),
// 方向永远保守:任何故障只少放行、不多放行。
Release(ctx context.Context, r *QuotaReservation) error
}
QuotaGate 单个队列(quota key)的配额闸。行为合同见 specs/006-periodic-quota/contracts/quota-capability-contract.md。
type QuotaProvider ¶
type QuotaProvider interface {
// QueueQuota 按队列配置构造配额闸;只在 qc.QuotaLimit > 0 时被调用。
QueueQuota(queue string, qc QueueConfig) (QuotaGate, error)
}
QuotaProvider 后端的**可选能力接口**(spec 006):能为队列提供跨进程共享的周期配额。 与 LimiterProvider 平行,但合同相反——**没有静默降级**:配置了 QuotaLimit>0 而后端 未实现本接口,taskgate.New() 直接报错。硬配额的全部意义是"绝不超发",退回进程内 计数等于假保护,宁可不启动(模型裁决 #3)。
实现约束:构造必须廉价、不持有需显式释放的资源(同 LimiterProvider); quota key 相同的多个 QuotaGate 共享介质计数,实例之间不得有本地共享状态。
type QuotaReservation ¶
type QuotaReservation struct {
Window int64
}
QuotaReservation 一次额度预留。Window 是预留落在的窗口起点 (unix 秒,共享介质的服务端钟),Release 靠它定位退还目标。
type ReplayOption ¶
type ReplayOption func(*replayOptions)
ReplayOption 重放时的函数式选项。
func AllowCompleted ¶
func AllowCompleted() ReplayOption
AllowCompleted 显式允许重放 completed 的执行("重新生成报告"这类主动重跑)。 不带它重放 completed 会拿到 ErrCompletedNotAllowed——防止误触发重复计费。
func WithPayload ¶
func WithPayload(p json.RawMessage) ReplayOption
WithPayload 用新 Payload 重放(参数修正后重跑)。不带它默认复制目标执行的 Payload; 要显式清空就传 json.RawMessage("null") 或 "{}"——nil 表示"没传",非 nil 即覆盖。
type ReplayRequest ¶
type ReplayRequest struct {
ExecutionID string // 目标执行,必须是其链的链尾
BusinessKey string // 与 ExecutionID 二选一
AllowCompleted bool // 重放 completed 必须显式打开
Payload json.RawMessage // nil = 复制目标执行的 Payload;非 nil 即覆盖
}
ReplayRequest Replay 的入参:目标用 ExecutionID 或 BusinessKey 指定,恰好一个非空。 按键指定时天然作用于链尾(该键下最新执行)。整个校验+创建必须在后端一个原子单位 (同事务/同 Lua/同临界区)内完成,行为合同见 specs/005-identity-replay/contracts/。
type Status ¶
type Status string
Status 任务状态,共七态。用字符串是为了落库和日志里直接可读。
const ( StatusBlocked Status = "blocked" // 有父任务还没跑完,等唤醒 StatusPending Status = "pending" // 排队中,可被认领 StatusRunning Status = "running" // 已被 worker 认领,持有租约 StatusRetrying Status = "retrying" // 失败后等退避时间到点重跑 StatusCompleted Status = "completed" // 终态:成功 StatusFailed Status = "failed" // 终态:失败(重试耗尽/跳过重试/计数封顶) StatusCanceled Status = "canceled" // 终态:被取消(主动取消或父失败传播) )
type SubmitDecision ¶
type SubmitDecision struct {
// Status 只会是三者之一:pending(可直接排队)、blocked(等父)、canceled(父已失败且 FailFast)。
Status Status
// PendingParents 仅在 Status==blocked 时有意义:还没到终态的父任务数(同 ID 去重后)。
PendingParents int
// LastError 仅在 Status==canceled 时有意义:取消原因,如 "parent <id> failed"。
LastError string
}
SubmitDecision 提交(Enqueue)时的初始状态判定结果。
func DecideOnSubmit ¶
func DecideOnSubmit(parents []ParentState, policy ParentFailurePolicy) SubmitDecision
DecideOnSubmit 判定一个带依赖的任务在提交那一刻应该落成什么初始状态。 规则(照 broker-contract.md 的 Enqueue 合同):
- FailFast 策略下,只要有任何一个父已经 failed/canceled → 直接 canceled。 哪怕其它父还没跑完也立即取消:这个子任务已经注定跑不成,等下去没有意义。
- IgnoreParentFail 策略下,父只要进了终态(哪怕失败)就算"满足"。
- 还有父没到终态 → blocked,并记下未完成父的数量(pending_parents)。
- 父全部满足 → pending,可以直接排队。
同一个父 ID 写了多遍只算一个:否则 pending_parents 会多计,父完成一次只减一次, 子任务就永远唤不醒了。调用方(后端)拿到 PendingParents 后按这个数落库。
type SubmitOption ¶
type SubmitOption func(*submitOptions)
SubmitOption 提交任务时的函数式选项。
func DependsOn ¶
func DependsOn(ids ...string) SubmitOption
DependsOn 声明父任务,父全部完成才会被唤醒;父 ID 必须已存在,否则拒收。
func IgnoreParentFailure ¶
func IgnoreParentFailure() SubmitOption
IgnoreParentFailure 父任务失败也照常执行(默认是 FailFast 连锁取消)。
func WithBusinessKey ¶
func WithBusinessKey(key string) SubmitOption
WithBusinessKey 业务幂等键:同键下已存在任何执行(不论状态)时 Submit 拒绝, 错误满足 errors.Is(err, ErrTaskExists),且可 errors.As 出 *TaskExistsError 拿到 链尾执行的 ID 与状态。失败后想再跑同一件事,走 Replay,不走再次 Submit。
func WithID
deprecated
func WithID(id string) SubmitOption
WithID 旧的"自定义任务 ID"选项。
Deprecated: 任务 ID(ExecutionID)已收紧为系统生成,用户不可指定;本选项现在 等同于 WithBusinessKey——传入的值成为业务幂等键,不再是任务 ID,**不能**拿去 Get/DependsOn(那两处只认 Submit 返回的 ID)。新代码请直接用 WithBusinessKey。
type Task ¶
type Task struct {
ID string `json:"id"` // ExecutionID:一次执行的永久身份,broker 生成 ulid,永不复用;公开 API 不提供写入口
BusinessKey string `json:"business_key,omitempty"` // 业务幂等键:同键下存在任何执行则 Enqueue 拒绝;创建后不可变
ReplayOf string `json:"replay_of,omitempty"` // 本执行重放自哪个 ExecutionID;由 Replay 写入,创建后不可变
Type string `json:"type"` // 决定 handler 和默认队列
Queue string `json:"queue"` // 限流单元,入队那一刻定死
Payload json.RawMessage `json:"payload,omitempty"` // 入参
Status Status `json:"status"`
Result json.RawMessage `json:"result,omitempty"` // Ack 时写入
LastError string `json:"last_error,omitempty"`
Attempts int `json:"attempts"` // 业务失败次数,> MaxRetry → failed
MaxRetry int `json:"max_retry"` // 0 = 不重试
LeaseLost int `json:"lease_lost"`
Throttled int `json:"throttled"`
RunAt time.Time `json:"run_at"` // 延迟执行和重试退避都靠它
DependsOn []string `json:"depends_on,omitempty"`
OnParentFailure ParentFailurePolicy `json:"on_parent_failure"`
LeaseToken string `json:"lease_token,omitempty"` // Dequeue 时携带,对外只读
CreatedAt time.Time `json:"created_at"`
StartedAt time.Time `json:"started_at,omitzero"`
FinishedAt time.Time `json:"finished_at,omitzero"`
}
Task 任务实体。字段语义照 data-model.md 第 1 节,Payload/Result 一律 json.RawMessage。
type TaskExistsError ¶
type TaskExistsError struct {
BusinessKey string // 撞的键
ExecutionID string // 键下链尾执行的 ID
Status Status // 链尾执行的状态
}
TaskExistsError 带 BusinessKey 提交撞键时的错误:errors.Is(err, ErrTaskExists) 照常成立,同时携带键下链尾(最新执行)的身份与状态,调用方据此直接决定要不要 Replay,不必再按键查询绕一圈。errors.As 按 *TaskExistsError 匹配。
func (*TaskExistsError) Unwrap ¶
func (e *TaskExistsError) Unwrap() error
Unwrap 让 errors.Is(err, ErrTaskExists) 保持成立,存量判错代码零改动。
type TaskFailedError ¶
type TaskFailedError struct {
ID string // 任务 ID
Status Status // failed 或 canceled
LastError string // 最后一次失败/取消原因
}
TaskFailedError Wait 等到 failed/canceled 终态时返回的错误,带上任务现场方便定位。
func (*TaskFailedError) Error ¶
func (e *TaskFailedError) Error() string
Error 实现 error 接口,带上任务 ID、终态与原因。
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package brokertest 是 Broker 行为契约的统一验收套件。
|
Package brokertest 是 Broker 行为契约的统一验收套件。 |
|
e2e
|
|
|
mockgw
Package mockgw 是一个可注入故障的 mock LLM/OCR 网关,只给 e2e 测试用,不属于库的公开 API。
|
Package mockgw 是一个可注入故障的 mock LLM/OCR 网关,只给 e2e 测试用,不属于库的公开 API。 |
|
examples
|
|
|
llm
command
examples/llm 三级 LLM 流水线示例:检索(retrieve)→ 生成(generate)→ 打分(score)。
|
examples/llm 三级 LLM 流水线示例:检索(retrieve)→ 生成(generate)→ 打分(score)。 |
|
internal
|
|
|
fakeclock
Package fakeclock 是测试专用的假时钟:时间只在调 Advance 时前进, 测试不真 sleep,时序完全确定(宪法第 V 条)。
|
Package fakeclock 是测试专用的假时钟:时间只在调 Advance 时前进, 测试不真 sleep,时序完全确定(宪法第 V 条)。 |
|
sqlbroker
Package sqlbroker 是 PostgreSQL / MySQL 两个服务器型后端的共享核心:基于标准库 database/sql,把两库"真正不同"的点收进 Dialect(见 dialect.go),其余标准 SQL 一份。
|
Package sqlbroker 是 PostgreSQL / MySQL 两个服务器型后端的共享核心:基于标准库 database/sql,把两库"真正不同"的点收进 Dialect(见 dialect.go),其余标准 SQL 一份。 |
|
Package memorybroker 是 Broker 的内存参考实现:单进程、单 sync.Mutex + sync.Cond。
|
Package memorybroker 是 Broker 的内存参考实现:单进程、单 sync.Mutex + sync.Cond。 |
|
Package mysqlbroker 是 taskgate 的 MySQL 后端:database/sql + go-sql-driver/mysql(纯 Go 免 cgo)。
|
Package mysqlbroker 是 taskgate 的 MySQL 后端:database/sql + go-sql-driver/mysql(纯 Go 免 cgo)。 |
|
Package pgbroker 是 taskgate 的 PostgreSQL 后端:database/sql + pgx(stdlib 模式,纯 Go 免 cgo)。
|
Package pgbroker 是 taskgate 的 PostgreSQL 后端:database/sql + pgx(stdlib 模式,纯 Go 免 cgo)。 |
|
prototype
|
|
|
identity
Package identity 是 Identity 领域模型的原型验证层(见 docs/plans/2026-07-16-Identity领域模型.md),不进正式代码。
|
Package identity 是 Identity 领域模型的原型验证层(见 docs/plans/2026-07-16-Identity领域模型.md),不进正式代码。 |
|
quota
Package quota 是 Quota 领域模型的原型验证(见 docs/plans/2026-07-16-Quota领域模型.md),不进正式代码。
|
Package quota 是 Quota 领域模型的原型验证(见 docs/plans/2026-07-16-Quota领域模型.md),不进正式代码。 |
|
Package redisbroker 是 Broker 的 Redis 后端:所有"多步读写必须原子"的操作 都收进单段 Lua 脚本执行(宪法 III:终态更新与子任务唤醒同一段脚本收敛), 语义以 memorybroker 为基准,由 brokertest 的 18 条契约统一验收。
|
Package redisbroker 是 Broker 的 Redis 后端:所有"多步读写必须原子"的操作 都收进单段 Lua 脚本执行(宪法 III:终态更新与子任务唤醒同一段脚本收敛), 语义以 memorybroker 为基准,由 brokertest 的 18 条契约统一验收。 |
|
Package sqlitebroker 是 Broker 的 sqlite 文件后端:纯 Go 驱动(modernc.org/sqlite,免 cgo), WAL 模式单文件落盘。
|
Package sqlitebroker 是 Broker 的 sqlite 文件后端:纯 Go 驱动(modernc.org/sqlite,免 cgo), WAL 模式单文件落盘。 |