为 PostgreSQL 连接增加可配置的事务空闲与锁等待超时,并在请求取消或 Worker 退出后使用独立有界上下文回滚事务。\n\n恢复过期任务时按批次使用 SKIP LOCKED,周期重建缺失或终态 River Job;锁等待超时只在确认尚未提交上游时安全重排,避免重复执行和重复结算。\n\n验证:完整 Go 测试、go vet、生产 Kustomize 渲染、gofmt 与 git diff --check 均通过。
88 lines
2.3 KiB
Go
88 lines
2.3 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type TaskBinaryResultBackfillItem struct {
|
|
ID string
|
|
Result map[string]any
|
|
FinishedAt time.Time
|
|
}
|
|
|
|
func (s *Store) ListTaskBinaryResultBackfillBatch(ctx context.Context, afterID string, batchSize int) ([]TaskBinaryResultBackfillItem, error) {
|
|
if batchSize < 1 || batchSize > 100 {
|
|
batchSize = 100
|
|
}
|
|
var items []TaskBinaryResultBackfillItem
|
|
err := s.beginTransaction(ctx, func(tx pgx.Tx) error {
|
|
if _, err := tx.Exec(ctx, `SET LOCAL lock_timeout = '5s'; SET LOCAL statement_timeout = '5s'`); err != nil {
|
|
return err
|
|
}
|
|
rows, err := tx.Query(ctx, `
|
|
SELECT id::text, result, COALESCE(finished_at, updated_at)
|
|
FROM gateway_tasks
|
|
WHERE status = 'succeeded'
|
|
AND result <> '{}'::jsonb
|
|
AND (NULLIF($1::text, '') IS NULL OR id > NULLIF($1::text, '')::uuid)
|
|
ORDER BY id
|
|
LIMIT $2`, afterID, batchSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
items = make([]TaskBinaryResultBackfillItem, 0, batchSize)
|
|
for rows.Next() {
|
|
var item TaskBinaryResultBackfillItem
|
|
var resultJSON []byte
|
|
if err := rows.Scan(&item.ID, &resultJSON, &item.FinishedAt); err != nil {
|
|
return err
|
|
}
|
|
item.Result = decodeObject(resultJSON)
|
|
items = append(items, item)
|
|
}
|
|
return rows.Err()
|
|
})
|
|
return items, err
|
|
}
|
|
|
|
func (s *Store) UpdateTaskBinaryResultBackfill(ctx context.Context, taskID string, result map[string]any) (bool, error) {
|
|
report := sanitizeJSONForStorageWithReport(minimalTaskResult(result))
|
|
if report.BinaryCount > 0 {
|
|
return false, &taskPayloadBinaryError{
|
|
target: ErrTaskResultBinaryNotMaterialized,
|
|
code: "result_binary_not_materialized",
|
|
count: report.BinaryCount,
|
|
}
|
|
}
|
|
resultJSON, err := json.Marshal(report.Value)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
updated := false
|
|
err = s.beginTransaction(ctx, func(tx pgx.Tx) error {
|
|
if _, err := tx.Exec(ctx, `SET LOCAL lock_timeout = '5s'; SET LOCAL statement_timeout = '5s'`); err != nil {
|
|
return err
|
|
}
|
|
tag, err := tx.Exec(ctx, `
|
|
UPDATE gateway_tasks
|
|
SET result = $2::jsonb,
|
|
updated_at = now()
|
|
WHERE id = $1::uuid
|
|
AND status = 'succeeded'`,
|
|
taskID,
|
|
string(resultJSON),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
updated = tag.RowsAffected() == 1
|
|
return nil
|
|
})
|
|
return updated, err
|
|
}
|