fix: 防止监控任务重复调度
This commit is contained in:
1 parent
78006a7120
commit
217da343a0
2 files changed
+85
-10
No files matched your search
@@ -122,19 +122,21 @@ func (s *MonitorScheduler) addTaskLocked(monitorID string, interval int) error {
|
|||||||
// 构建 cron 表达式: @every Ns
|
// 构建 cron 表达式: @every Ns
|
||||||
spec := fmt.Sprintf("@every %ds", interval)
|
spec := fmt.Sprintf("@every %ds", interval)
|
||||||
|
|
||||||
// 添加到 cron 调度器
|
// 回调绑定本次注册的任务代际。即使异常并发或未来的新路径留下了
|
||||||
|
// 未被 map 追踪的 cron entry,旧代际在执行前也会自行失效。
|
||||||
|
task := &MonitorTask{ID: monitorID}
|
||||||
entryID, err := s.cron.AddFunc(spec, func() {
|
entryID, err := s.cron.AddFunc(spec, func() {
|
||||||
s.executeTask(monitorID)
|
if s.isCurrentTask(task) {
|
||||||
|
s.executeTask(monitorID)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("添加 cron 任务失败: %w", err)
|
return fmt.Errorf("添加 cron 任务失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存任务信息
|
// 保存任务信息
|
||||||
s.tasks[monitorID] = &MonitorTask{
|
task.EntryID = entryID
|
||||||
ID: monitorID,
|
s.tasks[monitorID] = task
|
||||||
EntryID: entryID,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.logger.Info("添加监控任务",
|
s.logger.Info("添加监控任务",
|
||||||
zap.String("taskID", monitorID),
|
zap.String("taskID", monitorID),
|
||||||
@@ -143,11 +145,21 @@ func (s *MonitorScheduler) addTaskLocked(monitorID string, interval int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTask 更新监控任务(先删除再添加)
|
// isCurrentTask 判断 cron 回调是否仍属于该监控任务的当前注册代际。
|
||||||
|
func (s *MonitorScheduler) isCurrentTask(task *MonitorTask) bool {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.tasks[task.ID] == task
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTask 原子更新监控任务。所有 cron entry 与 tasks 映射的变更必须
|
||||||
|
// 共用同一把锁,否则并发更新可能各自添加 entry,最终只有一个被记录,
|
||||||
|
// 其余 entry 会成为无法移除且持续重复执行的孤儿任务。
|
||||||
func (s *MonitorScheduler) UpdateTask(monitorID string, interval int) error {
|
func (s *MonitorScheduler) UpdateTask(monitorID string, interval int) error {
|
||||||
// 移除旧监控任务
|
s.mu.Lock()
|
||||||
s.removeTaskLocked(monitorID)
|
defer s.mu.Unlock()
|
||||||
// 添加新任务
|
|
||||||
|
// addTaskLocked 会在添加前移除已有 entry。
|
||||||
return s.addTaskLocked(monitorID, interval)
|
return s.addTaskLocked(monitorID, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package scheduler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMonitorSchedulerConcurrentUpdateKeepsSingleCronEntry(t *testing.T) {
|
||||||
|
scheduler := NewMonitorScheduler(nil, zap.NewNop())
|
||||||
|
const monitorID = "monitor-1"
|
||||||
|
|
||||||
|
if err := scheduler.AddTask(monitorID, 3600); err != nil {
|
||||||
|
t.Fatalf("AddTask() error = %v", err)
|
||||||
|
}
|
||||||
|
scheduler.mu.RLock()
|
||||||
|
firstGeneration := scheduler.tasks[monitorID]
|
||||||
|
scheduler.mu.RUnlock()
|
||||||
|
|
||||||
|
const updates = 64
|
||||||
|
start := make(chan struct{})
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(updates)
|
||||||
|
for i := 0; i < updates; i++ {
|
||||||
|
interval := 3600 + i
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
<-start
|
||||||
|
if err := scheduler.UpdateTask(monitorID, interval); err != nil {
|
||||||
|
t.Errorf("UpdateTask() error = %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
close(start)
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if got := scheduler.GetTaskCount(); got != 1 {
|
||||||
|
t.Fatalf("GetTaskCount() = %d, want 1", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := scheduler.cron.Entries()
|
||||||
|
if len(entries) != 1 {
|
||||||
|
t.Fatalf("len(cron.Entries()) = %d, want 1", len(entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler.mu.RLock()
|
||||||
|
task := scheduler.tasks[monitorID]
|
||||||
|
scheduler.mu.RUnlock()
|
||||||
|
if task == nil {
|
||||||
|
t.Fatal("task is missing from scheduler map")
|
||||||
|
}
|
||||||
|
if task.EntryID != entries[0].ID {
|
||||||
|
t.Fatalf("tracked entry ID = %d, cron entry ID = %d", task.EntryID, entries[0].ID)
|
||||||
|
}
|
||||||
|
if scheduler.isCurrentTask(firstGeneration) {
|
||||||
|
t.Fatal("replaced task generation must not remain executable")
|
||||||
|
}
|
||||||
|
if !scheduler.isCurrentTask(task) {
|
||||||
|
t.Fatal("tracked task generation must remain executable")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user