// worker-protocol 演示一次文本规范化任务的真实执行与原消息重报。 // 这是底层协议示例;持续运行、自动续租与优雅退出见 examples/worker 的运行器示例。 package main import ( "context" "encoding/json" "errors" "fmt" "os" "strings" "time" orbit "git.yuntuo.hk/k2safe/orbit/sdk/go" ) func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func run() error { token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID") if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" { raw, err := os.ReadFile(path) if err != nil { return fmt.Errorf("read private credentials: %w", err) } var credentials struct { Token string `json:"token"` QueueID string `json:"queue_id"` } if json.Unmarshal(raw, &credentials) != nil { return errors.New("invalid credentials file") } if token == "" { token = credentials.Token } if queue == "" { queue = credentials.QueueID } } client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token}) if err != nil { return err } // 每次运行使用独立能力名,避免接走前一次示例遗留的任务。 operation, err := orbit.NewWorkerMessageID() if err != nil { return err } handler := "text.normalize." + operation registration, err := orbit.NewWorkerRegistration(queue, 1, []orbit.WorkerCapability{{Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1"}}) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) defer cancel() accepted, err := client.SubmitTask(ctx, "normalize-"+operation, orbit.SubmitTaskRequest{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"text":" Hello Orbit "}`), RecoverySafety: orbit.RecoveryIdempotent, TaskTimeoutMS: 60000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 20000}}) if err != nil { return err } session := &demoSession{client: client, registration: registration} defer session.close() if err := session.connect(ctx); err != nil { return err } capacityID, err := orbit.NewWorkerMessageID() if err != nil { return err } if _, err := session.conn.Capacity(ctx, capacityID, orbit.WorkerCapacity{Sequence: 1, Capacity: 1}); err != nil { return err } assignment, err := session.conn.NextAssignment(ctx) if err != nil { return err } if assignment.Task.ID != accepted.Task.ID { return errors.New("unexpected task in isolated example") } ref := assignment.Attempt() session.ref = &ref startID, err := orbit.NewWorkerMessageID() if err != nil { return err } started, err := session.confirm(ctx, func(c *orbit.WorkerConnection) (orbit.WorkerCommandResult, error) { return c.Start(ctx, startID, ref) }) if err != nil { return err } deadline, active := started.Lease.ExecutionDeadline() if !active { return errors.New("start receipt does not grant current execution authority") } // 业务代码只拿任务输入;不接收注册秘密或尝试秘密。处理器只调用这一次。 work, stop := context.WithDeadline(ctx, deadline) result, handlerErr := normalize(work, assignment.Task.Payload) stop() finish := orbit.WorkerFinish{Ref: ref, Outcome: orbit.WorkerSucceeded, Result: result} if handlerErr != nil { // 普通异常不能证明外部效果未发生;默认报告 UNKNOWN,保留核查路径。 finish.Outcome = orbit.WorkerUnknown finish.ErrorCode = "HANDLER_UNCONFIRMED" finish.Result = json.RawMessage(`{"reason":"handler did not confirm completion"}`) } finishID, err := orbit.NewWorkerMessageID() if err != nil { return err } confirmed, err := session.confirm(ctx, func(c *orbit.WorkerConnection) (orbit.WorkerCommandResult, error) { return c.Finish(ctx, finishID, finish) }) if err != nil { return err } if confirmed.Receipt.Disposition != "APPLIED" { return errors.New("result saved as evidence only; inspect authoritative task state") } current, err := client.GetTask(ctx, assignment.Task.ID) if err != nil { return err } if current.State != orbit.TaskSucceeded || current.AttemptCount != 1 || string(current.Result) != string(result) { return errors.New("task did not complete as expected") } drainID, err := orbit.NewWorkerMessageID() if err != nil { return err } // 新连接尚未开启容量时,Sequence=2 仍是合法递增值。 if _, err := session.conn.Capacity(ctx, drainID, orbit.WorkerCapacity{Sequence: 2, Capacity: 0, Drain: true}); err != nil { return err } fmt.Printf("task=%s final_state=%s attempts=%d result=%s\n", current.ID, current.State, current.AttemptCount, current.Result) return nil } func normalize(ctx context.Context, payload json.RawMessage) (json.RawMessage, error) { if err := ctx.Err(); err != nil { return nil, err } var in struct { Text string `json:"text"` } if err := json.Unmarshal(payload, &in); err != nil { return nil, err } return json.Marshal(struct { Text string `json:"text"` }{Text: strings.ToUpper(strings.TrimSpace(in.Text))}) } type demoSession struct { client *orbit.Client registration orbit.WorkerRegistration conn *orbit.WorkerConnection ref *orbit.WorkerAttempt } func (s *demoSession) close() { if s.conn != nil { s.conn.Close() } } func (s *demoSession) connect(ctx context.Context) error { s.close() c, err := s.client.DialWorker(ctx, s.registration) if err != nil { return err } s.conn = c if s.ref != nil { id, err := orbit.NewWorkerMessageID() if err != nil { return err } // 即使租约已失效,也可以重报原结果取得原回执或保存迟到证据。 if _, err := c.Resume(ctx, id, *s.ref); err != nil { return err } } return nil } // confirm 只重报控制操作,不再次调用业务函数;同一操作闭包固定消息 ID 与内容。 func (s *demoSession) confirm(ctx context.Context, call func(*orbit.WorkerConnection) (orbit.WorkerCommandResult, error)) (orbit.WorkerCommandResult, error) { var out orbit.WorkerCommandResult var err error for attempt := 0; attempt < 3; attempt++ { if attempt > 0 { select { case <-ctx.Done(): return out, ctx.Err() case <-time.After(time.Duration(attempt) * 100 * time.Millisecond): } if err = s.connect(ctx); err != nil { return out, err } } out, err = call(s.conn) if err == nil { return out, nil } var control *orbit.WorkerError retry := errors.Is(err, orbit.ErrWorkerDisconnected) || errors.Is(err, context.DeadlineExceeded) && ctx.Err() == nil || errors.As(err, &control) && control.Code == "STALE_SESSION" if !retry { return out, err } } return out, fmt.Errorf("control result still unconfirmed after bounded replay: %w", err) }