// keyed-concurrency 演示跨队列资源互斥及资源端单调序号校验。 package main import ( "context" "encoding/json" "errors" "fmt" "os" "sync" "time" orbit "git.yuntuo.hk/k2safe/orbit/sdk/go" ) var errStale = errors.New("stale resource token") // resource 仅用于本地示范。生产必须在资源存储事务内持久化最高序号和操作身份, // 并与实际写入原子提交;Go mutex 无法保护其他进程或外部服务。 type resource struct { mu sync.Mutex namespace, key string highest uint64 operations map[string]bool } func (r *resource) apply(namespace, key string, token uint64, operation string) error { r.mu.Lock() defer r.mu.Unlock() if namespace != r.namespace || key != r.key || token == 0 || token < r.highest { return errStale } r.highest = token // 同一操作的重报不产生第二次模拟写入。 r.operations[operation] = true return nil } func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func run() error { token := os.Getenv("ORBIT_TOKEN") if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" && token == "" { raw, err := os.ReadFile(path) if err != nil { return err } var c struct { Token string `json:"token"` } if err = json.Unmarshal(raw, &c); err != nil { return err } token = c.Token } api, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token}) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) defer cancel() op, err := orbit.NewWorkerMessageID() if err != nil { return err } name := "keyed." + op var queues []string var tasks []orbit.Task for i := 0; i < 2; i++ { q, err := api.CreateQueue(ctx, fmt.Sprintf("queue-%s-%d", op, i), orbit.CreateQueueRequest{Name: fmt.Sprintf("%s.%d", name, i), MaxConcurrency: 4, RatePerSecond: 100}) if err != nil { return err } queues = append(queues, q.Queue.ID) in := orbit.SubmitTaskRequest{QueueID: q.Queue.ID, Handler: name, HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"object_ref":"objects/shared"}`), ConcurrencyKey: "object/" + op, TaskTimeoutMS: 20000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 10000}} out, err := api.SubmitTask(ctx, fmt.Sprintf("operation-%s-%d", op, i), in) if err != nil { return err } tasks = append(tasks, out.Task) } r := &resource{namespace: tasks[0].NamespaceID, key: tasks[0].ConcurrencyKey, operations: map[string]bool{}} handler := orbit.WorkerHandler{Capability: orbit.WorkerCapability{Handler: name, HandlerVersion: "v1", PayloadVersion: "v1"}, Handle: func(ctx context.Context, e orbit.WorkerExecution) (json.RawMessage, error) { if err := ctx.Err(); err != nil { return nil, err } // 本示例每条任务表示一个独立操作,Task.ID 在安全重试期间保持不变。 if err := r.apply(e.Task.NamespaceID, e.Task.ConcurrencyKey, e.ConcurrencyToken, e.Task.ID); err != nil { return nil, &orbit.WorkerFailure{Code: "STALE_RESOURCE_TOKEN"} } return json.RawMessage(`{"updated":true}`), nil }} run, stop := context.WithCancel(ctx) done := make(chan error, 2) started := 0 defer func() { stop() for i := 0; i < started; i++ { <-done } }() for _, queue := range queues { w, err := orbit.NewWorker(api, orbit.WorkerOptions{QueueID: queue, Capacity: 4, ShutdownTimeout: 5 * time.Second, Handlers: []orbit.WorkerHandler{handler}}) if err != nil { return err } started++ go func() { done <- w.Run(run) }() } var low, high uint64 for _, task := range tasks { for { current, err := api.GetTask(ctx, task.ID) if err != nil { return err } if current.State == orbit.TaskSucceeded { break } if current.State == orbit.TaskFailed || current.State == orbit.TaskCancelled || current.State == orbit.TaskNeedsReview { return fmt.Errorf("unexpected task state: %s", current.State) } select { case <-ctx.Done(): return ctx.Err() case <-time.After(80 * time.Millisecond): } } history, err := api.ListTaskAttempts(ctx, task.ID, orbit.HistoryPageRequest{}) if err != nil { return err } if len(history.Items) != 1 || history.Items[0].ConcurrencyToken == 0 { return errors.New("missing resource token") } v := history.Items[0].ConcurrencyToken if low == 0 || v < low { low = v } if v > high { high = v } } if low == high { return errors.New("resource token reused") } if !errors.Is(r.apply(r.namespace, r.key, low, "late-write"), errStale) { return errors.New("old resource token accepted") } r.mu.Lock() count := len(r.operations) r.mu.Unlock() if count != 2 { return errors.New("unexpected resource writes") } stop() for started > 0 { err := <-done started-- if err != nil { return err } } fmt.Printf("queues=2 completed=2 tokens_increasing=true\nresource_writes=%d stale_write_rejected=true worker_drained=true\n", count) return nil }