fix(cron): improve wake channel handling and enhance concurrency in tests

This commit is contained in:
tong3jie 2026-03-12 11:45:44 +08:00
parent 991c7845d5
commit 5513de0b98
2 changed files with 22 additions and 10 deletions

View file

@ -74,6 +74,7 @@ func NewCronService(storePath string, onJob JobHandler) *CronService {
storePath: storePath, storePath: storePath,
onJob: onJob, onJob: onJob,
gronx: gronx.New(), gronx: gronx.New(),
wakeChan: make(chan struct{}),
} }
// Initialize and load store on creation // Initialize and load store on creation
cs.loadStore() cs.loadStore()
@ -98,7 +99,9 @@ func (cs *CronService) Start() error {
} }
cs.stopChan = make(chan struct{}) cs.stopChan = make(chan struct{})
if cs.wakeChan == nil {
cs.wakeChan = make(chan struct{}) cs.wakeChan = make(chan struct{})
}
cs.running = true cs.running = true
go cs.runLoop(cs.stopChan) go cs.runLoop(cs.stopChan)
@ -332,6 +335,15 @@ func (cs *CronService) computeNextRun(schedule *CronSchedule, nowMS int64) *int6
} }
} }
// wake up the loop to re-evaluate next wake time immediately (e.g. after add/update/remove jobs)
func (cs *CronService) notify() {
select {
case cs.wakeChan <- struct{}{}:
default:
// if the channel is full, it means the loop will wake up soon anyway, so we can skip sending
}
}
func (cs *CronService) recomputeNextRuns() { func (cs *CronService) recomputeNextRuns() {
now := time.Now().UnixMilli() now := time.Now().UnixMilli()
for i := range cs.store.Jobs { for i := range cs.store.Jobs {
@ -433,10 +445,7 @@ func (cs *CronService) AddJob(
return nil, err return nil, err
} }
select { cs.notify()
case cs.wakeChan <- struct{}{}:
default:
}
return &job, nil return &job, nil
} }
@ -450,10 +459,7 @@ func (cs *CronService) UpdateJob(job *CronJob) error {
cs.store.Jobs[i] = *job cs.store.Jobs[i] = *job
cs.store.Jobs[i].UpdatedAtMS = time.Now().UnixMilli() cs.store.Jobs[i].UpdatedAtMS = time.Now().UnixMilli()
select { cs.notify()
case cs.wakeChan <- struct{}{}:
default:
}
return cs.saveStoreUnsafe() return cs.saveStoreUnsafe()
} }
@ -485,6 +491,8 @@ func (cs *CronService) removeJobUnsafe(jobID string) bool {
} }
} }
cs.notify()
return removed return removed
} }
@ -507,6 +515,9 @@ func (cs *CronService) EnableJob(jobID string, enabled bool) *CronJob {
if err := cs.saveStoreUnsafe(); err != nil { if err := cs.saveStoreUnsafe(); err != nil {
log.Printf("[cron] failed to save store after enable: %v", err) log.Printf("[cron] failed to save store after enable: %v", err)
} }
cs.notify()
return job return job
} }
} }

View file

@ -214,6 +214,7 @@ func TestCronService_ConcurrentAccess(t *testing.T) {
for j := range iterations { for j := range iterations {
at := time.Now().Add(time.Hour).UnixMilli() at := time.Now().Add(time.Hour).UnixMilli()
cs.AddJob(fmt.Sprintf("Job-%d-%d", id, j), CronSchedule{Kind: "at", AtMS: &at}, "", false, "", "") cs.AddJob(fmt.Sprintf("Job-%d-%d", id, j), CronSchedule{Kind: "at", AtMS: &at}, "", false, "", "")
time.Sleep(100 * time.Microsecond)
} }
}(i) }(i)
} }
@ -227,7 +228,7 @@ func TestCronService_ConcurrentAccess(t *testing.T) {
if len(jobs) > 0 { if len(jobs) > 0 {
cs.EnableJob(jobs[0].ID, j%2 == 0) cs.EnableJob(jobs[0].ID, j%2 == 0)
} }
time.Sleep(1 * time.Millisecond) time.Sleep(100 * time.Microsecond)
} }
}() }()
} }