feat: persist workflow signals and arbitrate poll completion

This commit is contained in:
2026-09-18 03:52:50 +08:00
parent e2baffbe37
commit 7d5ebfcb09
34 changed files with 1958 additions and 113 deletions

View File

@@ -29,8 +29,8 @@ func NewWorkflowRuntime(db *sql.DB, node core.ID) (*WorkflowRuntime, error) {
type WorkflowRuntimeStats struct {
Examined int // Examined 本批取出的持久化候选数,包含被其他节点先处理或跳过的记录。
Consumed int // Consumed 已提交确认或隔离的内部结果数,事务回滚不计入。
Advanced int // Advanced 已提交推进的根或等待检查点数,不等于业务任务执行次数。
Consumed int // Consumed 以Inbox候选为入口提交确认或隔离的数量;时钟内消费计入Advanced,回滚不计入。
Advanced int // Advanced 已提交推进的根、等待检查点或过期信号数,不等于业务任务执行次数。
}
type workflowCandidate struct {
@@ -39,7 +39,7 @@ type workflowCandidate struct {
revision uint64
}
// Tick 同时读取两种到期索引,各取最多limit项再全局选前limit项;不先锁Inbox再反锁父资源。
// Tick 同时读取三种到期索引,各取最多limit项再全局选前limit项;不先锁Inbox或信号再反锁父资源。
func (r *WorkflowRuntime) Tick(ctx context.Context, limit int) (WorkflowRuntimeStats, error) {
var stats WorkflowRuntimeStats
if limit < 1 || limit > 100 {
@@ -47,7 +47,8 @@ func (r *WorkflowRuntime) Tick(ctx context.Context, limit int) (WorkflowRuntimeS
}
rows, err := r.db.QueryContext(ctx, `(SELECT 'INBOX' kind,namespace_id,id,workflow_id,revision,available_at FROM weave_inbox WHERE state='PENDING' AND available_at<=UTC_TIMESTAMP(6) ORDER BY available_at,namespace_id,id LIMIT ?)
UNION ALL (SELECT 'ROOT' kind,namespace_id,id,workflow_id,revision,available_at FROM weave_pending_advances WHERE available_at<=UTC_TIMESTAMP(6) ORDER BY available_at,namespace_id,id LIMIT ?)
ORDER BY available_at,namespace_id,id LIMIT ?`, limit, limit, limit)
UNION ALL (SELECT 'SIGNAL' kind,namespace_id,id,workflow_id,revision,available_at FROM weave_signals WHERE state='PENDING' AND available_at<=UTC_TIMESTAMP(6) ORDER BY available_at,namespace_id,id LIMIT ?)
ORDER BY available_at,namespace_id,id LIMIT ?`, limit, limit, limit, limit)
if err != nil {
return stats, err
}
@@ -95,6 +96,8 @@ func (r *WorkflowRuntime) deferCandidate(ctx context.Context, c workflowCandidat
q := `UPDATE weave_pending_advances SET available_at=DATE_ADD(UTC_TIMESTAMP(6),INTERVAL 30 SECOND),revision=revision+1,updated_at=UTC_TIMESTAMP(6) WHERE namespace_id=? AND id=? AND revision=?`
if c.kind == "INBOX" {
q = `UPDATE weave_inbox SET available_at=DATE_ADD(UTC_TIMESTAMP(6),INTERVAL 30 SECOND),error_code='RUNTIME_UNAVAILABLE',revision=revision+1,updated_at=UTC_TIMESTAMP(6) WHERE namespace_id=? AND id=? AND revision=? AND state='PENDING'`
} else if c.kind == "SIGNAL" {
q = `UPDATE weave_signals SET available_at=DATE_ADD(UTC_TIMESTAMP(6),INTERVAL 30 SECOND),revision=revision+1,updated_at=UTC_TIMESTAMP(6) WHERE namespace_id=? AND id=? AND revision=? AND state='PENDING'`
}
_, err := r.db.ExecContext(ctx, q, c.ns, c.id, c.revision)
return err
@@ -115,6 +118,10 @@ func (r *WorkflowRuntime) process(ctx context.Context, c workflowCandidate, skip
done, err = r.consume(ctx, tx, g, c, allow, now)
return err
}
if c.kind == "SIGNAL" {
done, err = r.expireWorkflowSignal(ctx, tx, g, c, allow, now)
return err
}
var step core.ID
var expected uint64
var raw []byte
@@ -133,7 +140,7 @@ func (r *WorkflowRuntime) process(ctx context.Context, c workflowCandidate, skip
if json.Unmarshal(raw, &checkpoint) != nil || checkpoint.SchemaVersion != 1 {
return errors.New("workflow checkpoint invalid")
}
if checkpoint.Kind == "TIMER" || checkpoint.Kind == "POLL" {
if checkpoint.Kind == "TIMER" || checkpoint.Kind == "POLL" || checkpoint.Kind == "SIGNAL" {
done, err = r.advanceClock(ctx, tx, g, c, step, expected, checkpoint, allow, now)
return err
}