package mysql import ( "bytes" "context" "database/sql" "errors" "time" "git.yuntuo.hk/k2safe/orbit/internal/jsonvalue" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/weave/engine" weave "git.yuntuo.hk/k2safe/orbit/weave/model" ) // driveWorkflowSignal 同时用于等待时钟和当前查询结果;handled=false时继续原查询分类。 // reason为空表示内部已消费Inbox并写入审计,调用方不能再次确认同一来源。 func (r *WorkflowRuntime) driveWorkflowSignal(ctx context.Context, tx *sql.Tx, g *workflowGraph, row *workflowStep, c workflowCandidate, source *workflowResult, allow bool, now time.Time) (handled, changed bool, reason string, err error) { // 来源矛盾单独转核查;任何SQL故障仍回滚整个事务并保留原来源。 if _, err := tx.ExecContext(ctx, `SAVEPOINT workflow_signal_source`); err != nil { return false, false, "", err } before := cloneWorkflowGraph(g) handled, changed, reason, err = r.applyWorkflowSignal(ctx, tx, g, row, c, source, allow, now) var invalid workflowSourceError if !errors.As(err, &invalid) { return } if _, err := tx.ExecContext(ctx, `ROLLBACK TO SAVEPOINT workflow_signal_source`); err != nil { return false, false, "", err } *g = *before row = g.steps[row.view.Key] if g.view.State != weave.InstanceNeedsReview { if err := holdWorkflow(ctx, tx, g, weave.InstanceNeedsReview, string(invalid), now); err != nil { return false, false, "", err } } var current *core.ID if row.view.Kind == "POLL" { if err := tx.QueryRowContext(ctx, `SELECT current_task_id FROM weave_poll_states WHERE namespace_id=? AND workflow_id=? AND activation_id=?`, g.view.NamespaceID, g.view.ID, row.view.ActivationID).Scan(¤t); err != nil { return false, false, "", err } if source != nil || current == nil { if _, err := tx.ExecContext(ctx, `UPDATE weave_poll_states SET current_task_id=NULL,next_poll_at=NULL,resolved_at=?,revision=revision+1,updated_at=? WHERE namespace_id=? AND workflow_id=? AND activation_id=?`, now, now, g.view.NamespaceID, g.view.ID, row.view.ActivationID); err != nil { return false, false, "", err } } } // 已发出的查询仍由Pulse结清,不能因信号损坏而假设外部执行结束。 if row.view.Kind != "POLL" || source != nil || current == nil { if err := saveWorkflowResultStep(ctx, tx, g, row, weave.StepNeedsReview, nil, now); err != nil { return false, false, "", err } } if err := rejectPendingWorkflowSignals(ctx, tx, g, ` AND s.target_step_key=? AND EXISTS(SELECT 1 FROM weave_activations a WHERE a.namespace_id=s.namespace_id AND a.workflow_id=s.workflow_id AND a.sequence=s.target_activation_sequence AND a.id=? AND a.step_id=?)`, []any{row.view.Key, row.view.ActivationID, row.view.ID}, string(invalid), now); err != nil { return false, false, "", err } if err := deleteWorkflowClock(ctx, tx, g, row); err != nil { return false, false, "", err } return true, true, string(invalid), nil } func (r *WorkflowRuntime) applyWorkflowSignal(ctx context.Context, tx *sql.Tx, g *workflowGraph, row *workflowStep, c workflowCandidate, source *workflowResult, allow bool, now time.Time) (handled, changed bool, reason string, err error) { w, err := readWorkflowSignalWait(ctx, tx, g, row) if err != nil { return false, false, "", err } node, _ := g.program.Step(row.view.Key) var poll weave.PollState if node.Poll != nil { var last core.ID poll, last, err = readWorkflowPollState(ctx, tx, g, row) if err != nil { return false, false, "", err } if err := validatePollTaskOwner(ctx, tx, g, row, last); err != nil { return false, false, "", err } if _, err := pollTaskInput(ctx, tx, g, row, node.Poll.Task); err != nil { return false, false, "", err } if poll.CurrentTaskID != nil { if *poll.CurrentTaskID != last || row.view.State != weave.StepActive || poll.NextPollAt != nil { return false, false, "", errors.New("signal poll current round invalid") } if err := validatePollTaskOwner(ctx, tx, g, row, last); err != nil { return false, false, "", err } if source != nil && source.task != last { return false, false, "", nil } } else { if source != nil { return false, false, "", nil } if row.view.State != weave.StepWaiting || poll.NextPollAt == nil { return false, false, "", errors.New("signal poll continuation missing") } if err := verifyWorkflowPollPending(ctx, tx, g, row, poll, last, now); err != nil { return false, false, "", err } } } v, err := selectedWorkflowSignal(ctx, tx, g, row, w, now) if err != nil { return false, false, "", err } if v == nil { if node.Poll != nil { return false, false, "", nil } if w.state != "WAITING" { return false, false, "", errors.New("signal step has settled wait without winner") } if now.Before(w.deadline) { return true, false, "", saveWorkflowClock(ctx, tx, g, row, "SIGNAL", row.view.Revision, w.deadline) } res, err := tx.ExecContext(ctx, `UPDATE weave_waits SET state='EXPIRED',resolved_at=?,revision=revision+1,updated_at=? WHERE namespace_id=? AND id=? AND state='WAITING'`, now, now, g.view.NamespaceID, w.id) if err := exactlyOne(res, err); err != nil { return true, false, "", err } if err := rejectOtherWorkflowSignals(ctx, tx, g, row, w.sequence, "SIGNAL_DEADLINE", now); err != nil { return true, false, "", err } if err := saveWorkflowResultStep(ctx, tx, g, row, weave.StepNeedsReview, nil, now); err != nil { return true, false, "", err } if err := deleteWorkflowClock(ctx, tx, g, row); err != nil { return true, false, "", err } return true, true, "SIGNAL_DEADLINE", holdWorkflow(ctx, tx, g, weave.InstanceNeedsReview, "SIGNAL_DEADLINE", now) } if node.Poll != nil && source == nil && poll.CurrentTaskID != nil { // 已落库查询结果必须按原事件时间参与竞争,不能由扫描先后决定赢家。 task, err := readTask(ctx, tx, g.view.NamespaceID, *poll.CurrentTaskID, false) if err != nil { return false, false, "", err } if task.QueueID != node.Poll.Task.QueueID { return false, false, "", errors.New("signal query queue invalid") } if task.State.Terminal() || task.State == "NEEDS_REVIEW" { pending := workflowCandidate{kind: "INBOX", ns: g.view.NamespaceID, workflow: g.view.ID} var due time.Time if err := tx.QueryRowContext(ctx, `SELECT id,revision,available_at FROM weave_inbox WHERE namespace_id=? AND workflow_id=? AND source_task_id=? AND source_activation_id=? AND state='PENDING' ORDER BY source_revision DESC LIMIT 1`, g.view.NamespaceID, g.view.ID, poll.CurrentTaskID, row.view.ActivationID).Scan(&pending.id, &pending.revision, &due); err != nil { return false, false, "", err } if due.After(now) { return true, false, "", saveWorkflowClock(ctx, tx, g, row, "POLL", row.view.Revision, due) } done, err := r.consume(ctx, tx, g, pending, allow, now) return true, done, "", err } } if node.Poll != nil && source != nil && w.winner == nil && source.snapshot.State == "SUCCEEDED" && !source.snapshot.EffectUncertain && source.occurred.Before(w.deadline) && !source.occurred.After(v.AcceptedAt) { outcome, err := g.program.ClassifyPoll(row.view.Key, source.snapshot.Result) if err == nil && outcome != engine.PollPending { return false, false, "", nil } // 较早的最终查询先胜出;PENDING不争夺终结权。 } newWinner := w.winner == nil if err := claimWorkflowSignal(ctx, tx, g, &w, v, now); err != nil { return true, false, "", err } if err := rejectOtherWorkflowSignals(ctx, tx, g, row, w.sequence, "WAIT_SETTLED", now); err != nil { return true, false, "", err } if node.Poll != nil && poll.CurrentTaskID != nil && source == nil { task, err := readExecutionTask(ctx, tx, g.view.NamespaceID, *poll.CurrentTaskID) if err != nil { return true, false, "", err } if _, err := cancelExecutionTask(ctx, tx, g.view.NamespaceID, task, now, "WORKFLOW_SIGNAL_WON"); err != nil { return true, false, "", err } if !now.Before(poll.DeadlineAt) && g.view.State != weave.InstanceNeedsReview { if err := holdWorkflow(ctx, tx, g, weave.InstanceNeedsReview, "POLL_SIGNAL_INFLIGHT_DEADLINE", now); err != nil { return true, false, "", err } newWinner = true } if err := saveWorkflowClock(ctx, tx, g, row, "POLL", row.view.Revision, now.Add(time.Second)); err != nil { return true, false, "", err } if newWinner { return true, true, "POLL_SIGNAL_SELECTED", nil } return true, false, "", nil } state, output, reason := weave.StepSucceeded, v.Payload, "SIGNAL_CONSUMED" if node.Poll != nil { outcome, err := g.program.ClassifyPoll(row.view.Key, v.Payload) if err != nil || outcome == engine.PollPending { return true, false, "", workflowSourceError("POLL_FINAL_SIGNAL_INVALID") } switch outcome { case engine.PollFailed: state, output, reason = weave.StepFailed, nil, "POLL_SIGNAL_FAILED" case engine.PollNeedsReview: state, output, reason = weave.StepNeedsReview, nil, "POLL_SIGNAL_REVIEW" default: reason = "POLL_SIGNAL_SUCCEEDED" } if source != nil { if source.snapshot.EffectUncertain || source.snapshot.State == "NEEDS_REVIEW" { state, output, reason = weave.StepNeedsReview, nil, "POLL_SIGNAL_QUERY_UNCERTAIN" } else if source.snapshot.State == "SUCCEEDED" { query, err := g.program.ClassifyPoll(row.view.Key, source.snapshot.Result) if err != nil { state, output, reason = weave.StepNeedsReview, nil, "POLL_SIGNAL_QUERY_INVALID" } else if query != engine.PollPending { left, err := jsonvalue.Canonical(source.snapshot.Result) if err != nil { return true, false, "", err } right, err := jsonvalue.Canonical(v.Payload) if err != nil { return true, false, "", err } if !bytes.Equal(left, right) { state, output, reason = weave.StepNeedsReview, nil, "POLL_SIGNAL_QUERY_CONFLICT" } } } } res, err := tx.ExecContext(ctx, `UPDATE weave_poll_states SET current_task_id=NULL,next_poll_at=NULL,resolved_at=?,revision=revision+1,updated_at=? WHERE namespace_id=? AND id=? AND resolved_at IS NULL`, now, now, g.view.NamespaceID, poll.ID) if err := exactlyOne(res, err); err != nil { return true, false, "", err } } if err := saveWorkflowResultStep(ctx, tx, g, row, state, output, now); err != nil { return true, false, "", err } if err := deleteWorkflowClock(ctx, tx, g, row); err != nil { return true, false, "", err } if state == weave.StepNeedsReview || state == weave.StepFailed { next := weave.InstanceNeedsReview if state == weave.StepFailed && (g.view.StopKind != nil || g.version.Specification.FailurePolicy == "FAIL_AFTER_SETTLED" || g.view.CancelRequestedAt != nil) { next = weave.InstanceCancelling } if err := holdWorkflow(ctx, tx, g, next, reason, now); err != nil { return true, false, "", err } } else if err := r.progress(ctx, tx, g, allow, now); err != nil { return true, false, "", err } return true, true, reason, nil } func (r *WorkflowRuntime) advanceSignalClock(ctx context.Context, tx *sql.Tx, g *workflowGraph, row *workflowStep, c workflowCandidate, allow bool, now time.Time) (bool, error) { handled, changed, reason, err := r.driveWorkflowSignal(ctx, tx, g, row, c, nil, allow, now) if err != nil { return false, err } if !handled { return false, errors.New("signal clock did not identify a wait") } if changed && reason != "" { return true, r.audit(ctx, tx, g, c.id, reason, now) } return changed, nil } // expireWorkflowSignal 对早到但未匹配的信号使用总截止索引;已按时匹配的信号仍先完成实际消费。 func (r *WorkflowRuntime) expireWorkflowSignal(ctx context.Context, tx *sql.Tx, g *workflowGraph, c workflowCandidate, allow bool, now time.Time) (bool, error) { var due time.Time if err := tx.QueryRowContext(ctx, `SELECT available_at FROM weave_signals WHERE namespace_id=? AND workflow_id=? AND id=?`, c.ns, c.workflow, c.id).Scan(&due); err != nil { return false, err } if due.After(now) { return false, nil } v, err := readWorkflowSignal(ctx, tx, c.ns, c.workflow, c.id) if err != nil { return false, err } if v.State != "PENDING" || now.Before(v.ExpiresAt) { return false, nil } if g.view.StopKind != nil && !g.view.State.Terminal() { // 停止事务已关闭新等待;由根收集停止前信号,不能让稍后过期扫描把取消改成超时核查。 if err := saveWorkflowAdvance(ctx, tx, g, now); err != nil { return false, err } _, err := tx.ExecContext(ctx, `UPDATE weave_signals SET available_at=DATE_ADD(?,INTERVAL 30 SECOND),revision=revision+1,updated_at=? WHERE namespace_id=? AND id=? AND state='PENDING'`, now, now, c.ns, c.id) return false, err } row := g.steps[v.TargetStepKey] if row != nil && row.view.ActivationID != nil && !g.view.State.Terminal() && (row.view.State == weave.StepWaiting || row.view.State == weave.StepActive) { w, err := readWorkflowSignalWait(ctx, tx, g, row) if err != nil { return false, err } if w.sequence == v.TargetActivationSequence && w.correlation == v.CorrelationKey && w.contract.Name == v.Name && w.contract.PayloadVersion == v.PayloadVersion && v.AcceptedAt.Before(w.deadline) { handled, changed, reason, err := r.driveWorkflowSignal(ctx, tx, g, row, c, nil, allow, now) if err != nil { return false, err } if handled { // 若匹配到另一条更早的信号或正等待结果退避,避免过期索引持续抢占批次。 if _, err := tx.ExecContext(ctx, `UPDATE weave_signals SET available_at=DATE_ADD(?,INTERVAL 30 SECOND),revision=revision+1,updated_at=? WHERE namespace_id=? AND id=? AND state='PENDING'`, now, now, c.ns, c.id); err != nil { return false, err } if changed && reason != "" { return true, r.audit(ctx, tx, g, c.id, reason, now) } return changed, nil } } } res, err := tx.ExecContext(ctx, `UPDATE weave_signals SET state='EXPIRED',disposition='UNMATCHED_DEADLINE',revision=revision+1,updated_at=? WHERE namespace_id=? AND id=? AND state='PENDING' AND revision=?`, now, c.ns, c.id, v.Revision) if err := exactlyOne(res, err); err != nil { return false, err } if err := emitWorkflowSignalTransition(ctx, tx, c.ns, c.workflow, v.ID, v.Revision+1, "EXPIRED", "UNMATCHED_DEADLINE", nil); err != nil { return false, err } return true, r.audit(ctx, tx, g, c.id, "SIGNAL_EXPIRED", now) }