71 lines
2.3 KiB
Go
71 lines
2.3 KiB
Go
package flow
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
// 运行中执行跟踪:优雅关停时取消全部执行(含脱离连接的恢复执行)并等待落完终态再退出,
|
||
// 避免"程序停止 → exec 仍卡在 status=1"。
|
||
//
|
||
// 背景:WS 执行(handleExecute)的 execCtx 派生自连接 closeCtx,Close() 取消连接即随之中止落终态;
|
||
// 但恢复执行(recoverExecution)的 ctx 经 context.WithoutCancel 脱离连接,程序关停时若不显式取消,
|
||
// 恢复中的 exec 不会落终态(仍 status=1),重启要等心跳陈旧 60s 才能再捞。这里登记所有运行中执行,
|
||
// SetShuttingDown 统一取消、main 等待全部落库后再退出。
|
||
var (
|
||
execRunMu sync.Mutex
|
||
execRuns = make(map[string]context.CancelFunc)
|
||
)
|
||
|
||
// trackExecRun 登记一次运行中执行,返回 finish 在落完终态(recordWorkflow 后)调用。
|
||
// 每次运行唯一 key(重试/断点续跑复用同一 execId 也不冲突)。
|
||
func trackExecRun(cancel context.CancelFunc) (finish func()) {
|
||
key := uuid.NewString()
|
||
execRunMu.Lock()
|
||
execRuns[key] = cancel
|
||
execRunMu.Unlock()
|
||
return func() {
|
||
execRunMu.Lock()
|
||
delete(execRuns, key)
|
||
execRunMu.Unlock()
|
||
}
|
||
}
|
||
|
||
// cancelAllExecRuns 优雅关停:取消所有运行中执行。恢复执行 ctx 经 WithoutCancel 脱离连接,
|
||
// 必须显式取消才能随 WS 执行一起落终态(status=3/retryable=1)。
|
||
func cancelAllExecRuns() {
|
||
execRunMu.Lock()
|
||
cancels := make([]context.CancelFunc, 0, len(execRuns))
|
||
for _, c := range execRuns {
|
||
cancels = append(cancels, c)
|
||
}
|
||
execRunMu.Unlock()
|
||
for _, c := range cancels {
|
||
c()
|
||
}
|
||
}
|
||
|
||
// WaitExecRunsDrain 等待所有运行中执行落完终态(限时),供 main 优雅关停收尾后退出进程。
|
||
// 关停后不再启动新执行(execute/reExecute/recoverExecution 顶部有 IsShuttingDown 守卫),
|
||
// 因此运行中集合只减不增,轮询安全。
|
||
func WaitExecRunsDrain(timeout time.Duration) {
|
||
deadline := time.Now().Add(timeout)
|
||
for {
|
||
execRunMu.Lock()
|
||
n := len(execRuns)
|
||
execRunMu.Unlock()
|
||
if n == 0 {
|
||
return
|
||
}
|
||
if time.Now().After(deadline) {
|
||
g.Log().Warningf(context.Background(), "优雅关停等待执行落库超时,剩余 %d 个执行", n)
|
||
return
|
||
}
|
||
time.Sleep(100 * time.Millisecond)
|
||
}
|
||
}
|