Files
MyGoNavi/internal/syncjob/continuous_backoff.go
Syngnat bc02a4c614 feat(syncjob): 新增可恢复的数据同步任务运行时
- 持久化任务、运行、事件、检查点与错误行状态
- 支持调度、暂停取消、恢复重试、并发限制和故障接管
- 增加执行 fencing、连续失败退避与错误行重放租约
2026-08-08 19:06:57 +08:00

79 lines
2.2 KiB
Go

package syncjob
import (
"context"
"hash/fnv"
"time"
)
const (
continuousFailureBackoffInitial = 5 * time.Second
continuousFailureBackoffMaximum = 5 * time.Minute
continuousFailureHistoryLimit = 32
)
func (m *Manager) continuousFailureNotBefore(ctx context.Context, definition JobDefinition) (int64, int, error) {
if definition.Schedule.Kind != ScheduleContinuous {
return 0, 0, nil
}
runs, err := m.store.ListRuns(ctx, definition.ID, continuousFailureHistoryLimit)
if err != nil {
return 0, 0, err
}
consecutiveFailures := 0
var latestFailure RunRecord
for _, run := range runs {
switch run.Status {
case RunStatusQueued, RunStatusRunning, RunStatusCancelling:
continue
case RunStatusFailed, RunStatusPartial, RunStatusInterrupted:
if consecutiveFailures == 0 {
latestFailure = run
}
consecutiveFailures++
default:
// A success or an operator-controlled terminal state ends the failure
// streak. The next continuous launch returns to the normal poll cadence.
goto counted
}
}
counted:
if consecutiveFailures == 0 || latestFailure.FinishedAt <= 0 {
return 0, consecutiveFailures, nil
}
backoff := continuousFailureBackoff(definition.ID, latestFailure.ID, consecutiveFailures)
return time.UnixMilli(latestFailure.FinishedAt).Add(backoff).UnixMilli(), consecutiveFailures, nil
}
func continuousFailureBackoff(jobID, latestRunID string, consecutiveFailures int) time.Duration {
if consecutiveFailures < 1 {
return 0
}
base := continuousFailureBackoffInitial
for attempt := 1; attempt < consecutiveFailures && base < continuousFailureBackoffMaximum; attempt++ {
if base > continuousFailureBackoffMaximum/2 {
base = continuousFailureBackoffMaximum
break
}
base *= 2
}
if base >= continuousFailureBackoffMaximum {
return continuousFailureBackoffMaximum
}
jitterRoom := base / 5
if remaining := continuousFailureBackoffMaximum - base; jitterRoom > remaining {
jitterRoom = remaining
}
if jitterRoom <= 0 {
return base
}
hasher := fnv.New64a()
_, _ = hasher.Write([]byte(jobID))
_, _ = hasher.Write([]byte{0})
_, _ = hasher.Write([]byte(latestRunID))
_, _ = hasher.Write([]byte{0, byte(consecutiveFailures)})
jitter := time.Duration(hasher.Sum64() % (uint64(jitterRoom) + 1))
return base + jitter
}