Skip to content

Commit 3c4bf98

Browse files
fix(scheduler): preserve cron timezone on task reschedule
This problem arisen from the fact that we rely on the timezone of the time passed to cron trigger for correct calculation of next activation. We might want to improve that by switching to set timezone as a part of cron specification, but this is also confusing when we take into consideration our separate timezone flag (#3818). In this case, it wasn't done for the time of initial activation kept always in UTC+0 timezone. This resulted in switching from correct timezone to UTC+0 timezone on task reschedule. Refs #3818 Fixes #4743
1 parent a46b933 commit 3c4bf98

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

pkg/scheduler/scheduler.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2017 ScyllaDB
1+
// Copyright (C) 2026 ScyllaDB
22

33
package scheduler
44

@@ -140,6 +140,7 @@ func (s *Scheduler[K]) reschedule(ctx *RunContext[K], initialActivation time.Tim
140140
now := s.now()
141141
if d.Location != nil {
142142
now = now.In(d.Location)
143+
initialActivation = initialActivation.In(d.Location)
143144
}
144145
next := d.Trigger.Next(now)
145146

pkg/scheduler/scheduler_test.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2017 ScyllaDB
1+
// Copyright (C) 2026 ScyllaDB
22

33
package scheduler
44

@@ -855,4 +855,58 @@ func TestReschedule(t *testing.T) {
855855
}
856856
}
857857
})
858+
859+
t.Run("preserve cron timezone on task reschedule", func(t *testing.T) {
860+
ctx, cancel := context.WithCancel(t.Context())
861+
defer cancel()
862+
// Use different timezones in time generator and trigger
863+
zone0 := time.FixedZone("UTC", 0)
864+
zone5 := time.FixedZone("UTC+5", 5*60*60)
865+
// Trigger at midnight UTC+5
866+
d := Details{
867+
Trigger: schedules.MustCron("0 0 * * *", time.Time{}),
868+
Location: zone5,
869+
}
870+
// Returns noon in UTC+0
871+
timeGen := func() time.Time {
872+
return time.Date(2024, 1, 1, 12, 0, 0, 0, zone0)
873+
}
874+
// Since trigger timezone takes precedence over time generator timezone,
875+
// expect next activation in 7 hours.
876+
expectedNext := timeGen().Add(7 * time.Hour)
877+
878+
f := newFakeRunner()
879+
s := NewScheduler[testKey](timeGen, f.Run, ll)
880+
k := randomKey()
881+
882+
s.Schedule(ctx, k, d)
883+
// Verify initial activation
884+
a := s.Activations(k)
885+
if len(a) != 1 {
886+
t.Fatalf("expected 1 activation in queue, got %d", len(a))
887+
}
888+
if !expectedNext.Equal(a[0].Time) {
889+
t.Fatalf("expected initial activation to be %v, got %v", expectedNext, a[0].Time)
890+
}
891+
// Start task in the background so that it's rescheduled when it finishes
892+
time.AfterFunc(StartOffset, func() {
893+
s.Trigger(ctx, k)
894+
})
895+
// Verify that task finished before timeout or closing scheduler
896+
select {
897+
case <-startAndWait(ctx, s):
898+
t.Fatal("expected a run, scheduler exit")
899+
case <-time.After(Timeout):
900+
t.Fatal("expected a run, timeout")
901+
case <-f.WaitKeys(k):
902+
}
903+
// Verify rescheduled activation
904+
a = s.Activations(k)
905+
if len(a) != 1 {
906+
t.Fatalf("expected 1 activation in queue, got %d", len(a))
907+
}
908+
if !expectedNext.Equal(a[0].Time) {
909+
t.Fatalf("expected rescheduled activation to be %v, got %v", expectedNext, a[0].Time)
910+
}
911+
})
858912
}

0 commit comments

Comments
 (0)