77 lines
2.2 KiB
Go
77 lines
2.2 KiB
Go
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
|
||
}
|