feat(scheduler): random run-window scheduling and deduped manual triggers
Support a random daily run window (random_window_start/end) where the next auto-run time is derived from a stable per-job hash, avoiding thundering-herd starts while keeping one run per day. Honors the last auto-run on initial scheduling so restarts don't double-fire. Manual triggers can opt into dedupe (dedupe_manual_triggers): an advisory-locked path collapses concurrent requests onto an existing pending/running job instead of queueing duplicates. Scheduler error messages are localized to Chinese. Register the media_supply_cache_sync job (dry-run aware) and add the ops migration that seeds it.
This commit is contained in:
@@ -46,6 +46,46 @@ func TestNextIntervalAutoAtSchedulesAfterCurrentCompletion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextRandomWindowAutoAtIsStableInsideWindow(t *testing.T) {
|
||||
loc := time.FixedZone("CST", 8*60*60)
|
||||
window, ok := parseRandomRunWindowLocal(map[string]any{
|
||||
"random_window_start": "01:00",
|
||||
"random_window_end": "04:00",
|
||||
})
|
||||
if !ok {
|
||||
t.Fatal("expected random window config to parse")
|
||||
}
|
||||
now := time.Date(2026, 6, 2, 0, 30, 0, 0, loc)
|
||||
|
||||
got := nextRandomWindowAutoAt("media_supply_cache_sync", now, loc, window, true, nil)
|
||||
gotAgain := nextRandomWindowAutoAt("media_supply_cache_sync", now.Add(10*time.Minute), loc, window, true, nil)
|
||||
start := time.Date(2026, 6, 2, 1, 0, 0, 0, loc)
|
||||
end := time.Date(2026, 6, 2, 4, 0, 0, 0, loc)
|
||||
if got.Before(start) || !got.Before(end) {
|
||||
t.Fatalf("nextRandomWindowAutoAt() = %s, want inside [%s, %s)", got, start, end)
|
||||
}
|
||||
if !got.Equal(gotAgain) {
|
||||
t.Fatalf("expected stable candidate, got %s and %s", got, gotAgain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextRandomWindowAutoAtSkipsTodayAfterAutoRun(t *testing.T) {
|
||||
loc := time.FixedZone("CST", 8*60*60)
|
||||
window, _ := parseRandomRunWindowLocal(map[string]any{
|
||||
"random_window_start": "01:00",
|
||||
"random_window_end": "04:00",
|
||||
})
|
||||
now := time.Date(2026, 6, 2, 2, 0, 0, 0, loc)
|
||||
last := time.Date(2026, 6, 2, 1, 30, 0, 0, loc)
|
||||
|
||||
got := nextRandomWindowAutoAt("media_supply_cache_sync", now, loc, window, true, &last)
|
||||
wantStart := time.Date(2026, 6, 3, 1, 0, 0, 0, loc)
|
||||
wantEnd := time.Date(2026, 6, 3, 4, 0, 0, 0, loc)
|
||||
if got.Before(wantStart) || !got.Before(wantEnd) {
|
||||
t.Fatalf("nextRandomWindowAutoAt() = %s, want inside tomorrow window [%s, %s)", got, wantStart, wantEnd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmptySuccessRunRequiresConfiguredZeroMetrics(t *testing.T) {
|
||||
stats := map[string]any{
|
||||
"hydrated_count": 0,
|
||||
|
||||
Reference in New Issue
Block a user