103 lines
3.4 KiB
Go
103 lines
3.4 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"git.pcux.cn/xuans/Scert/backend/internal/certificate"
|
|
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
|
|
"git.pcux.cn/xuans/Scert/backend/internal/repository"
|
|
)
|
|
|
|
// 持久化任务与尝试后才调用此入口;后台执行不得持有请求或响应对象。
|
|
// 进程中断由启动恢复标记失败,不自动重放可能已经签发或部署的外部操作。
|
|
func (s *Server) enqueueCertificateTask(c *gin.Context, task repository.Task, attempt repository.TaskAttempt, action string, operation func(context.Context, string) (certificate.IssueResult, deployment.Report, error), stage, runningMessage, successMessage string) {
|
|
s.taskMu.Lock()
|
|
if s.taskWorkersStopped || len(s.asyncTasks) >= 32 {
|
|
s.taskMu.Unlock()
|
|
s.finishTaskBeforeExecution(task, attempt, "failed", "服务正在停止或任务队列已满,任务未执行")
|
|
s.writeError(c, http.StatusServiceUnavailable, "服务正在停止或任务队列已满,请稍后重试", nil)
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), certificateTaskTimeout)
|
|
if s.taskCancels == nil {
|
|
s.taskCancels = make(map[string]context.CancelFunc)
|
|
}
|
|
if s.asyncTasks == nil {
|
|
s.asyncTasks = make(map[string]bool)
|
|
s.taskSlots = make(chan struct{}, 4)
|
|
}
|
|
s.taskCancels[task.ID] = cancel
|
|
s.asyncTasks[task.ID] = true
|
|
s.taskWorkers.Add(1)
|
|
s.taskMu.Unlock()
|
|
go func() {
|
|
defer s.taskWorkers.Done()
|
|
defer s.unregisterTaskCancel(task.ID)
|
|
defer cancel()
|
|
defer func() {
|
|
if recover() != nil {
|
|
s.finishTaskBeforeExecution(task, attempt, "failed", "任务执行异常中断,请检查部署恢复状态后重试")
|
|
}
|
|
}()
|
|
select {
|
|
case s.taskSlots <- struct{}{}:
|
|
defer func() { <-s.taskSlots }()
|
|
case <-ctx.Done():
|
|
s.finishTaskContext(task, attempt, ctx.Err())
|
|
return
|
|
}
|
|
s.runCertificateTask(ctx, task, attempt, action, operation, stage, runningMessage, successMessage)
|
|
}()
|
|
c.Header("Location", "/api/v1/tasks/"+task.ID)
|
|
c.JSON(http.StatusAccepted, gin.H{"task": task, "accepted": true})
|
|
}
|
|
|
|
func (s *Server) finishTaskContext(task repository.Task, attempt repository.TaskAttempt, err error) {
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
|
s.finishTaskBeforeExecution(task, attempt, "failed", "任务执行超时,未继续执行")
|
|
return
|
|
}
|
|
s.finishTaskBeforeExecution(task, attempt, "cancelled", "任务已取消,未继续执行")
|
|
}
|
|
|
|
func (s *Server) finishTaskBeforeExecution(task repository.Task, attempt repository.TaskAttempt, status, message string) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
finished := time.Now().UTC()
|
|
if err := s.tasks.FinishTaskAttempt(ctx, repository.TaskCompletion{
|
|
TaskID: task.ID, AttemptID: attempt.ID, Status: status, Stage: attempt.Stage,
|
|
Message: message, FinishedAt: finished,
|
|
AuditAction: task.TaskType,
|
|
}); err != nil && s.logger != nil {
|
|
s.logger.Error("保存任务及尝试终态失败,保留中断恢复状态", "task_id", task.ID, "error", err)
|
|
}
|
|
}
|
|
|
|
func (s *Server) stopTaskWorkers() {
|
|
s.taskMu.Lock()
|
|
defer s.taskMu.Unlock()
|
|
s.taskWorkersStopped = true
|
|
for _, cancel := range s.taskCancels {
|
|
cancel()
|
|
}
|
|
}
|
|
|
|
func (s *Server) waitTaskWorkers(ctx context.Context) error {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
s.taskWorkers.Wait()
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-done:
|
|
return nil
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|