feat: add reusable MySQL outbox and HTTP lifecycle example
This commit is contained in:
76
adapters/outbox/mysql/runtime.go
Normal file
76
adapters/outbox/mysql/runtime.go
Normal file
@@ -0,0 +1,76 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user