package outbox import ( "context" "errors" "time" orbit "git.yuntuo.hk/k2safe/orbit/sdk/go" ) var ErrShutdownTimeout = errors.New("outbox sender shutdown deadline exceeded; reconcile persisted records") // RunOptions 控制进程轮询与关闭,不改变每条记录持久化的 24 小时/100 次预算。 type RunOptions struct { PollInterval time.Duration // PollInterval 空闲/退避轮询间隔,默认500毫秒,范围10毫秒至10秒。 ShutdownTimeout time.Duration // ShutdownTimeout 取消后等待当前发送及本地确认,默认20秒,最多5分钟。 } // Run 持续发送;取消后停止领取,限时完成当前交换及确认,调用方必须等待返回。 // 存储/确认错误返回给宿主处理;HTTP暂时失败持久化退避,REVIEW留在数据库供核查。 // 可以多进程并行运行;数据库租约协调领取,同一记录的HTTP仍可能重复,始终复用原键。 func (s *Store) Run(parent context.Context, api *orbit.Client, options RunOptions) error { if api == nil { return ErrInvalid } if options.PollInterval == 0 { options.PollInterval = 500 * time.Millisecond } if options.ShutdownTimeout == 0 { options.ShutdownTimeout = 20 * time.Second } if options.PollInterval < 10*time.Millisecond || options.PollInterval > 10*time.Second || options.ShutdownTimeout < time.Millisecond || options.ShutdownTimeout > 5*time.Minute { return ErrInvalid } work, cancel := context.WithCancel(context.WithoutCancel(parent)) done := make(chan struct{}) expired := make(chan struct{}) go func() { defer close(done) select { case <-work.Done(): return case <-parent.Done(): } timer := time.NewTimer(options.ShutdownTimeout) defer timer.Stop() select { case <-work.Done(): case <-timer.C: close(expired) cancel() } }() defer func() { cancel(); <-done }() for parent.Err() == nil { _, err := s.SendOnce(work, api) select { case <-expired: return ErrShutdownTimeout default: } if err != nil && !errors.Is(err, ErrIdle) && !errors.Is(err, ErrLeaseLost) { return err } if err == nil { continue } timer := time.NewTimer(options.PollInterval) select { case <-parent.Done(): timer.Stop() case <-timer.C: } } return nil }