package mysql import ( "context" "database/sql" "errors" "math" "git.yuntuo.hk/k2safe/orbit/internal/jsonwire" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/platform/events" ) // appendResultEvent 必须在来源任务更新和事件写入之后、原事务提交之前调用。 // 流行一直锁到提交;后来的事务无法提交更大序号并越过尚未提交的较小序号。 // 不访问订阅或交付行:慢消费者不能反向阻塞任务的结果持久化。 func appendResultEvent(ctx context.Context, tx *sql.Tx, ns, eventID, taskID core.ID, revision uint64, eventType string) error { var snapshot events.TaskResult var raw []byte var link workflowTaskLink snapshot.SchemaVersion = 1 err := tx.QueryRowContext(ctx, `SELECT t.id,t.queue_id,t.state,t.revision,t.result,t.effect_uncertain,t.current_attempt_id,t.owner_kind,t.owner_id,t.gate_id, EXISTS(SELECT 1 FROM weave_activations a WHERE a.namespace_id=t.namespace_id AND a.task_id=t.id) OR EXISTS(SELECT 1 FROM weave_poll_rounds r WHERE r.namespace_id=t.namespace_id AND r.task_id=t.id) OR EXISTS(SELECT 1 FROM weave_instances i WHERE i.namespace_id=t.namespace_id AND i.gate_id=t.gate_id) FROM pulse_tasks t WHERE t.namespace_id=? AND t.id=?`, ns, taskID).Scan(&snapshot.TaskID, &snapshot.QueueID, &snapshot.State, &snapshot.Revision, &raw, &snapshot.EffectUncertain, &snapshot.CurrentAttemptID, &link.Owner, &link.Activation, &link.Gate, &link.Linked) if err != nil { return err } if snapshot.Revision != revision || eventType != "task."+map[string]string{"SUCCEEDED": "succeeded", "FAILED": "failed", "CANCELLED": "cancelled", "NEEDS_REVIEW": "needs_review"}[snapshot.State] { return errors.New("task result event does not match persisted task state") } snapshot.Result = raw body, err := jsonwire.Marshal(snapshot) if err != nil { return err } // 内部待处理记录和结果同事务,外部结果流锁仍最后取得。 if err := appendWorkflowResult(ctx, tx, ns, eventID, taskID, revision, body, link); err != nil { return err } head, _, err := lockEventStream(ctx, tx, ns) if err != nil { return err } if head == math.MaxUint64 { return errors.New("event sequence exhausted") } if _, err = tx.ExecContext(ctx, `INSERT INTO orbit_result_events(namespace_id,id,sequence,queue_id,snapshot) VALUES (?,?,?,?,?)`, ns, eventID, head+1, snapshot.QueueID, string(body)); err != nil { return err } _, err = tx.ExecContext(ctx, `UPDATE orbit_event_streams SET last_sequence=?,updated_at=UTC_TIMESTAMP(6) WHERE namespace_id=?`, head+1, ns) return err } func lockEventStream(ctx context.Context, tx *sql.Tx, ns core.ID) (head, retained uint64, err error) { if _, err = tx.ExecContext(ctx, `INSERT INTO orbit_event_streams(namespace_id) VALUES (?) ON DUPLICATE KEY UPDATE namespace_id=orbit_event_streams.namespace_id`, ns); err != nil { return } err = tx.QueryRowContext(ctx, `SELECT last_sequence,retained_from_sequence FROM orbit_event_streams WHERE namespace_id=? FOR UPDATE`, ns).Scan(&head, &retained) return }