发布 0.2.0:模型池自愈、探测修正、任务异步触发与删除加固
CI / test (push) Successful in 32s
Release / release (push) Successful in 52s

This commit is contained in:
2026-07-10 20:25:37 +08:00
parent dbba1f4905
commit 7706f59549
19 changed files with 978 additions and 107 deletions
+59 -4
View File
@@ -3,6 +3,7 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"strings"
@@ -46,6 +47,12 @@ type TaskService struct {
entries map[uint]cron.EntryID
// runMu 让租户删除等待在途执行结束,并阻止新执行读取删除前的任务快照。
runMu sync.RWMutex
// runningMu 保护 running:同一任务不允许并发重复执行
// (cron 重叠触发静默跳过,手动触发返回 ErrTaskRunning)
runningMu sync.Mutex
running map[uint]bool
// runWG 追踪手动触发的后台执行,Stop 时等待收尾
runWG sync.WaitGroup
// snatchVarsMu 保护 snatchVars:runSnatch 抢满时写入成功通知变量,
// execute 组装执行后快照时取走(一次性)
@@ -63,6 +70,7 @@ func NewTaskService(db *gorm.DB, configs *OciConfigService, notifier *Notifier,
settings: settings,
cron: cron.New(),
entries: map[uint]cron.EntryID{},
running: map[uint]bool{},
snatchIPWait: defaultSnatchIPWait,
snatchVars: map[uint]map[string]string{},
}
@@ -86,10 +94,11 @@ func (s *TaskService) Start() error {
return nil
}
// Stop 停止调度并等待执行中的任务收尾
// Stop 停止调度并等待执行中的任务收尾(含手动触发的后台执行),
// 保证任务产生的异步通知都已进入 Notifier 的等待队列。
func (s *TaskService) Stop() {
<-s.cron.Stop().Done()
s.runWG.Wait()
}
// healthCheckPayload 是测活任务参数;ociConfigIds 为空表示全部配置。
@@ -356,7 +365,7 @@ func (s *TaskService) TaskLogs(ctx context.Context, id uint, limit int) ([]model
return logs, nil
}
// RunTaskNow 立即执行一次任务并返回本次日志。
// RunTaskNow 同步执行一次任务并返回本次日志(内部与测试使用;API 走 TriggerTask)
func (s *TaskService) RunTaskNow(ctx context.Context, id uint) (*model.TaskLog, error) {
if _, err := s.GetTask(ctx, id); err != nil {
return nil, err
@@ -364,6 +373,43 @@ func (s *TaskService) RunTaskNow(ctx context.Context, id uint) (*model.TaskLog,
return s.execute(id), nil
}
// ErrTaskRunning 表示任务已有一次执行在途,拒绝重复触发。
var ErrTaskRunning = errors.New("任务正在执行中,请稍候")
// TriggerTask 异步触发一次任务执行并立即返回;执行结果照常落任务日志与通知,
// 由前端轮询呈现。同一任务在途时返回 ErrTaskRunning。
func (s *TaskService) TriggerTask(ctx context.Context, id uint) error {
if _, err := s.GetTask(ctx, id); err != nil {
return err
}
if !s.beginRun(id) {
return ErrTaskRunning
}
s.runWG.Add(1)
go func() {
defer s.runWG.Done()
s.runHeld(id)
}()
return nil
}
// beginRun 抢占任务的执行权;在途时返回 false。
func (s *TaskService) beginRun(id uint) bool {
s.runningMu.Lock()
defer s.runningMu.Unlock()
if s.running[id] {
return false
}
s.running[id] = true
return true
}
func (s *TaskService) endRun(id uint) {
s.runningMu.Lock()
delete(s.running, id)
s.runningMu.Unlock()
}
// schedule 把任务注册进 cron 调度。
func (s *TaskService) schedule(task *model.Task) error {
s.mu.Lock()
@@ -412,9 +458,18 @@ func (s *TaskService) lockTenantCleanup() func() {
return s.runMu.Unlock
}
// execute 执行一次任务:加载 → 分派 → 更新任务状态并写日志,
// 前后状态交给通知判定,只在状态变化时推送。
// execute 执行一次任务:同一任务已有执行在途时静默跳过(cron 重叠触发防抖)。
func (s *TaskService) execute(taskID uint) *model.TaskLog {
if !s.beginRun(taskID) {
return nil
}
return s.runHeld(taskID)
}
// runHeld 在已持有执行权的前提下完成一次执行:加载 → 分派 → 更新任务状态并写日志,
// 前后状态交给通知判定,只在状态变化时推送。
func (s *TaskService) runHeld(taskID uint) *model.TaskLog {
defer s.endRun(taskID)
s.runMu.RLock()
defer s.runMu.RUnlock()
return s.executeLocked(taskID)