Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
62 lines
1.9 KiB
Go
62 lines
1.9 KiB
Go
package model
|
|
|
|
import (
|
|
"context"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// DetectionSchedule survives task-history cleanup and is never projected directly
|
|
// to public APIs. Only automatic results advance this channel/kind clock.
|
|
type DetectionSchedule struct {
|
|
ChannelID int `gorm:"primaryKey;autoIncrement:false"`
|
|
Kind string `gorm:"primaryKey;type:varchar(24)"`
|
|
LastValid string `gorm:"type:varchar(16)"`
|
|
IntervalMinutes int
|
|
NextDueAt int64
|
|
LastFinishedAt int64
|
|
LastResultID string `gorm:"type:varchar(64)"`
|
|
}
|
|
|
|
func NextDetectionInterval(lastValid, outcome string, current int) int {
|
|
if outcome != "normal" && outcome != "degraded" {
|
|
return 15
|
|
}
|
|
if lastValid == "" {
|
|
return 30
|
|
}
|
|
if lastValid != outcome {
|
|
return 15
|
|
}
|
|
if current == 15 {
|
|
return 30
|
|
}
|
|
return 60
|
|
}
|
|
|
|
func EnsureDetectionSchedule(ctx context.Context, seed DetectionSchedule) error {
|
|
return DB.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(&seed).Error
|
|
}
|
|
|
|
func ApplyDetectionScheduleResult(ctx context.Context, channelID int, kind, resultID string, finishedAt int64, outcome string) error {
|
|
return DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var state DetectionSchedule
|
|
if err := lockForUpdate(tx).Where("channel_id = ? AND kind = ?", channelID, kind).First(&state).Error; err != nil {
|
|
return err
|
|
}
|
|
// A result is committed before its clock advances. Reconciliation can
|
|
// safely replay it after a crash, without advancing the clock twice.
|
|
if finishedAt <= state.LastFinishedAt || resultID == state.LastResultID {
|
|
return nil
|
|
}
|
|
state.IntervalMinutes = NextDetectionInterval(state.LastValid, outcome, state.IntervalMinutes)
|
|
if outcome == "normal" || outcome == "degraded" {
|
|
state.LastValid = outcome
|
|
}
|
|
state.LastFinishedAt, state.LastResultID = finishedAt, resultID
|
|
state.NextDueAt = finishedAt + int64(state.IntervalMinutes)*60
|
|
return tx.Save(&state).Error
|
|
})
|
|
}
|