feat: add standalone Worker protocol SDK and ACK-loss regression

This commit is contained in:
2026-09-17 13:59:33 +08:00
parent cd0dc7011b
commit 1753fcbccb
19 changed files with 1995 additions and 16 deletions

View File

@@ -0,0 +1,214 @@
// worker-protocol 演示一次文本规范化任务的真实执行与原消息重报。
// 这是底层协议示例;持续运行、自动续租与优雅退出由后续 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)
}