Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
493 lines
18 KiB
Go
493 lines
18 KiB
Go
package model
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/glebarez/sqlite"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/schema"
|
|
)
|
|
|
|
func TestSystemTaskHistoryDatabaseMatrix(t *testing.T) {
|
|
for _, dialect := range []string{"sqlite", "mysql", "postgres"} {
|
|
t.Run(dialect, func(t *testing.T) {
|
|
var dialector gorm.Dialector
|
|
switch dialect {
|
|
case "sqlite":
|
|
dialector = sqlite.Open(":memory:")
|
|
case "mysql":
|
|
dsn := os.Getenv("TEST_MYSQL_DSN")
|
|
if dsn == "" {
|
|
t.Skip("TEST_MYSQL_DSN is not configured")
|
|
}
|
|
dialector = mysql.Open(dsn)
|
|
case "postgres":
|
|
dsn := os.Getenv("TEST_POSTGRES_DSN")
|
|
if dsn == "" {
|
|
t.Skip("TEST_POSTGRES_DSN is not configured")
|
|
}
|
|
dialector = postgres.Open(dsn)
|
|
}
|
|
db, err := gorm.Open(dialector, &gorm.Config{NamingStrategy: schema.NamingStrategy{TablePrefix: "history_test_"}})
|
|
require.NoError(t, err)
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
sqlDB.SetMaxOpenConns(1)
|
|
previousDB := DB
|
|
DB = db
|
|
t.Cleanup(func() {
|
|
DB = previousDB
|
|
require.NoError(t, db.Migrator().DropTable(&SystemTask{}))
|
|
require.NoError(t, sqlDB.Close())
|
|
})
|
|
require.NoError(t, db.AutoMigrate(&SystemTask{}))
|
|
var version string
|
|
versionQuery := "SELECT version()"
|
|
if dialect == "sqlite" {
|
|
versionQuery = "SELECT sqlite_version()"
|
|
}
|
|
require.NoError(t, db.Raw(versionQuery).Scan(&version).Error)
|
|
t.Logf("database version: %s", version)
|
|
|
|
tasks := []SystemTask{
|
|
{TaskID: "model-failed-old", Type: SystemTaskTypeModelUpdate, Status: SystemTaskStatusFailed},
|
|
{TaskID: "model-success", Type: SystemTaskTypeModelUpdate, Status: SystemTaskStatusSucceeded},
|
|
{TaskID: "channel-failed", Type: SystemTaskTypeChannelTest, Status: SystemTaskStatusFailed},
|
|
{TaskID: "model-failed-recent", Type: SystemTaskTypeModelUpdate, Status: SystemTaskStatusFailed},
|
|
{TaskID: "model-latest", Type: SystemTaskTypeModelUpdate, Status: SystemTaskStatusSucceeded},
|
|
{TaskID: "channel-pending", Type: SystemTaskTypeChannelTest, Status: SystemTaskStatusPending},
|
|
{TaskID: "channel-running", Type: SystemTaskTypeChannelTest, Status: SystemTaskStatusRunning},
|
|
{TaskID: "log-only-history", Type: SystemTaskTypeLogCleanup, Status: SystemTaskStatusSucceeded},
|
|
}
|
|
require.NoError(t, db.Create(&tasks).Error)
|
|
filter := SystemTaskFilter{Scope: "history", Type: SystemTaskTypeModelUpdate, Status: SystemTaskStatusFailed}
|
|
page, total, err := ListSystemTasks(filter, 1, 1)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 2, total)
|
|
require.Len(t, page, 1)
|
|
assert.Equal(t, "model-failed-old", page[0].TaskID)
|
|
page, total, err = ListSystemTasks(filter, 2, 1)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 2, total)
|
|
assert.Empty(t, page)
|
|
|
|
active, total, err := ListSystemTasks(SystemTaskFilter{Scope: "active"}, 0, 20)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 2, total)
|
|
require.Len(t, active, 2)
|
|
assert.Equal(t, "channel-running", active[0].TaskID)
|
|
assert.Equal(t, "channel-pending", active[1].TaskID)
|
|
|
|
deleted, err := DeleteSystemTaskHistory(filter)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 2, deleted)
|
|
_, total, err = ListSystemTasks(SystemTaskFilter{Scope: "history"}, 0, 20)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 4, total, "other types and statuses are untouched")
|
|
|
|
deleted, err = DeleteSystemTaskHistory(SystemTaskFilter{Scope: "active", Status: SystemTaskStatusRunning})
|
|
require.NoError(t, err)
|
|
assert.Zero(t, deleted, "caller cannot override the terminal-status restriction")
|
|
deleted, err = DeleteSystemTaskHistory(SystemTaskFilter{})
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 2, deleted)
|
|
remaining, total, err := ListSystemTasks(SystemTaskFilter{}, 0, 100)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 4, total)
|
|
var remainingIDs []string
|
|
for _, task := range remaining {
|
|
remainingIDs = append(remainingIDs, task.TaskID)
|
|
}
|
|
assert.ElementsMatch(t, []string{"model-latest", "channel-pending", "channel-running", "log-only-history"}, remainingIDs)
|
|
latest, err := GetLatestSystemTasks([]string{SystemTaskTypeModelUpdate, SystemTaskTypeChannelTest, SystemTaskTypeLogCleanup})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "model-latest", latest[SystemTaskTypeModelUpdate].TaskID)
|
|
assert.Equal(t, "channel-running", latest[SystemTaskTypeChannelTest].TaskID)
|
|
assert.Equal(t, "log-only-history", latest[SystemTaskTypeLogCleanup].TaskID)
|
|
deleted, err = DeleteSystemTaskHistory(SystemTaskFilter{})
|
|
require.NoError(t, err)
|
|
assert.Zero(t, deleted, "repeated cleanup preserves the scheduler's latest runs")
|
|
})
|
|
}
|
|
}
|
|
|
|
type testSystemTaskPayload struct {
|
|
TargetTimestamp int64 `json:"target_timestamp"`
|
|
BatchSize int `json:"batch_size"`
|
|
}
|
|
|
|
type testSystemTaskState struct {
|
|
Total int64 `json:"total"`
|
|
Processed int64 `json:"processed"`
|
|
Progress int `json:"progress"`
|
|
Remaining int64 `json:"remaining"`
|
|
}
|
|
|
|
func createLegacyPendingSystemTask(t *testing.T, taskType string) *SystemTask {
|
|
t.Helper()
|
|
taskID, err := GenerateSystemTaskID()
|
|
require.NoError(t, err)
|
|
task := &SystemTask{
|
|
TaskID: taskID,
|
|
Type: taskType,
|
|
Status: SystemTaskStatusPending,
|
|
}
|
|
require.NoError(t, DB.Create(task).Error)
|
|
return task
|
|
}
|
|
|
|
func TestSystemTaskCreateAndActiveLifecycle(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
payload := testSystemTaskPayload{TargetTimestamp: 1000, BatchSize: 100}
|
|
state := testSystemTaskState{}
|
|
task, err := CreateSystemTask(SystemTaskTypeLogCleanup, payload, state)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, task.ActiveKey)
|
|
assert.Equal(t, SystemTaskTypeLogCleanup, *task.ActiveKey)
|
|
|
|
var decodedPayload testSystemTaskPayload
|
|
require.NoError(t, task.DecodePayload(&decodedPayload))
|
|
assert.Equal(t, payload, decodedPayload)
|
|
|
|
activeTask, err := GetActiveSystemTask(SystemTaskTypeLogCleanup)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, activeTask)
|
|
assert.Equal(t, task.TaskID, activeTask.TaskID)
|
|
|
|
runnerID := "runner-a"
|
|
claimedTask, claimed, err := ClaimSystemTask(task.ID, SystemTaskTypeLogCleanup, runnerID, common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
|
|
err = FinishSystemTask(claimedTask.TaskID, runnerID, SystemTaskStatusSucceeded, map[string]int64{"deleted_count": 0}, "")
|
|
require.NoError(t, err)
|
|
|
|
finishedTask, err := GetSystemTaskByTaskID(task.TaskID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, finishedTask)
|
|
assert.Nil(t, finishedTask.ActiveKey)
|
|
|
|
activeTask, err = GetActiveSystemTask(SystemTaskTypeLogCleanup)
|
|
require.NoError(t, err)
|
|
require.Nil(t, activeTask)
|
|
|
|
_, err = CreateSystemTask(SystemTaskTypeLogCleanup, payload, state)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSystemTaskActiveKeyPreventsDuplicateActiveRun(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
payload := testSystemTaskPayload{TargetTimestamp: 1000, BatchSize: 100}
|
|
task, err := CreateSystemTask(SystemTaskTypeLogCleanup, payload, testSystemTaskState{})
|
|
require.NoError(t, err)
|
|
_, err = CreateSystemTask(SystemTaskTypeLogCleanup, payload, testSystemTaskState{})
|
|
require.Error(t, err)
|
|
|
|
activeTask, err := GetActiveSystemTask(SystemTaskTypeLogCleanup)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, activeTask)
|
|
assert.Equal(t, task.TaskID, activeTask.TaskID)
|
|
}
|
|
|
|
func TestSystemTaskLockPreventsConcurrentClaim(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
payload := testSystemTaskPayload{TargetTimestamp: 1000, BatchSize: 100}
|
|
task, err := CreateSystemTask(SystemTaskTypeLogCleanup, payload, testSystemTaskState{})
|
|
require.NoError(t, err)
|
|
secondTask := createLegacyPendingSystemTask(t, SystemTaskTypeLogCleanup)
|
|
|
|
claimedTask, claimed, err := ClaimSystemTask(task.ID, SystemTaskTypeLogCleanup, "runner-a", common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
|
|
_, claimed, err = ClaimSystemTask(secondTask.ID, SystemTaskTypeLogCleanup, "runner-b", common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.False(t, claimed)
|
|
|
|
assert.Equal(t, "runner-a", claimedTask.LockedBy)
|
|
|
|
reloadedSecond, err := GetSystemTaskByTaskID(secondTask.TaskID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, reloadedSecond)
|
|
assert.Equal(t, SystemTaskStatusPending, reloadedSecond.Status)
|
|
}
|
|
|
|
func TestExpiredSystemTaskLockFailsOldRunAndClaimsLegacyPendingRun(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
first, err := CreateSystemTask(SystemTaskTypeLogCleanup, nil, nil)
|
|
require.NoError(t, err)
|
|
_, claimed, err := ClaimSystemTask(first.ID, SystemTaskTypeLogCleanup, "runner-a", common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
|
|
require.NoError(t, DB.Model(&SystemTaskLock{}).
|
|
Where("task_id = ?", first.TaskID).
|
|
Update("locked_until", common.GetTimestamp()-1).Error)
|
|
|
|
second := createLegacyPendingSystemTask(t, SystemTaskTypeLogCleanup)
|
|
claimedTask, claimed, err := ClaimSystemTask(second.ID, SystemTaskTypeLogCleanup, "runner-b", common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
assert.Equal(t, second.TaskID, claimedTask.TaskID)
|
|
assert.Equal(t, "runner-b", claimedTask.LockedBy)
|
|
|
|
reloadedFirst, err := GetSystemTaskByTaskID(first.TaskID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, reloadedFirst)
|
|
assert.Equal(t, SystemTaskStatusFailed, reloadedFirst.Status)
|
|
assert.Equal(t, "task lease expired", reloadedFirst.Error)
|
|
assert.Nil(t, reloadedFirst.ActiveKey)
|
|
}
|
|
|
|
func TestExpireStaleSystemTaskLockFailsOldRunAndAllowsNewRun(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
first, err := CreateSystemTask(SystemTaskTypeLogCleanup, nil, nil)
|
|
require.NoError(t, err)
|
|
_, claimed, err := ClaimSystemTask(first.ID, SystemTaskTypeLogCleanup, "runner-a", common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
|
|
require.NoError(t, DB.Model(&SystemTaskLock{}).
|
|
Where("task_id = ?", first.TaskID).
|
|
Update("locked_until", common.GetTimestamp()-1).Error)
|
|
|
|
require.NoError(t, ExpireStaleSystemTaskLocks(common.GetTimestamp()))
|
|
|
|
reloadedFirst, err := GetSystemTaskByTaskID(first.TaskID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, reloadedFirst)
|
|
assert.Equal(t, SystemTaskStatusFailed, reloadedFirst.Status)
|
|
assert.Equal(t, "task lease expired", reloadedFirst.Error)
|
|
assert.Nil(t, reloadedFirst.ActiveKey)
|
|
|
|
var lockCount int64
|
|
require.NoError(t, DB.Model(&SystemTaskLock{}).Where("task_id = ?", first.TaskID).Count(&lockCount).Error)
|
|
assert.Equal(t, int64(0), lockCount)
|
|
|
|
second, err := CreateSystemTask(SystemTaskTypeLogCleanup, nil, nil)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, first.TaskID, second.TaskID)
|
|
}
|
|
|
|
func TestFindEarliestPendingSystemTasks(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
empty, err := FindEarliestPendingSystemTasks(nil)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, empty)
|
|
|
|
firstA, err := CreateSystemTask("type_a", nil, nil)
|
|
require.NoError(t, err)
|
|
ignoredB, err := CreateSystemTask("type_b", nil, nil)
|
|
require.NoError(t, err)
|
|
_, claimed, err := ClaimSystemTask(ignoredB.ID, "type_b", "runner-b", common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
require.NoError(t, FinishSystemTask(ignoredB.TaskID, "runner-b", SystemTaskStatusFailed, nil, "failed"))
|
|
firstB, err := CreateSystemTask("type_b", nil, nil)
|
|
require.NoError(t, err)
|
|
ignoredC, err := CreateSystemTask("type_c", nil, nil)
|
|
require.NoError(t, err)
|
|
_, claimed, err = ClaimSystemTask(ignoredC.ID, "type_c", "runner-c", common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
require.NoError(t, FinishSystemTask(ignoredC.TaskID, "runner-c", SystemTaskStatusFailed, nil, "failed"))
|
|
|
|
tasks, err := FindEarliestPendingSystemTasks([]string{"type_a", "type_b", "type_c", "missing"})
|
|
require.NoError(t, err)
|
|
require.Len(t, tasks, 2)
|
|
assert.Equal(t, firstA.TaskID, tasks["type_a"].TaskID)
|
|
assert.Equal(t, firstB.TaskID, tasks["type_b"].TaskID)
|
|
assert.Nil(t, tasks["type_c"])
|
|
assert.Nil(t, tasks["missing"])
|
|
}
|
|
|
|
func TestGetLatestSystemTask(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
latest, err := GetLatestSystemTask(SystemTaskTypeChannelTest)
|
|
require.NoError(t, err)
|
|
require.Nil(t, latest)
|
|
|
|
first, err := CreateSystemTask(SystemTaskTypeChannelTest, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
runnerID := "runner-a"
|
|
_, claimed, err := ClaimSystemTask(first.ID, SystemTaskTypeChannelTest, runnerID, common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
require.NoError(t, FinishSystemTask(first.TaskID, runnerID, SystemTaskStatusSucceeded, nil, ""))
|
|
|
|
second, err := CreateSystemTask(SystemTaskTypeChannelTest, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
latest, err = GetLatestSystemTask(SystemTaskTypeChannelTest)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, latest)
|
|
assert.Equal(t, second.TaskID, latest.TaskID)
|
|
}
|
|
|
|
func TestGetLatestSystemTasks(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
empty, err := GetLatestSystemTasks(nil)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, empty)
|
|
|
|
firstA, err := CreateSystemTask("type_a", nil, nil)
|
|
require.NoError(t, err)
|
|
firstB, err := CreateSystemTask("type_b", nil, nil)
|
|
require.NoError(t, err)
|
|
_, claimed, err := ClaimSystemTask(firstA.ID, "type_a", "runner-a", common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
require.NoError(t, FinishSystemTask(firstA.TaskID, "runner-a", SystemTaskStatusSucceeded, nil, ""))
|
|
secondA, err := CreateSystemTask("type_a", nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
tasks, err := GetLatestSystemTasks([]string{"type_a", "type_b", "missing"})
|
|
require.NoError(t, err)
|
|
require.Len(t, tasks, 2)
|
|
assert.NotEqual(t, firstA.TaskID, tasks["type_a"].TaskID)
|
|
assert.Equal(t, secondA.TaskID, tasks["type_a"].TaskID)
|
|
assert.Equal(t, firstB.TaskID, tasks["type_b"].TaskID)
|
|
assert.Nil(t, tasks["missing"])
|
|
}
|
|
|
|
func TestRenewSystemTaskLock(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
task, err := CreateSystemTask(SystemTaskTypeLogCleanup, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
runnerID := "runner-a"
|
|
_, claimed, err := ClaimSystemTask(task.ID, SystemTaskTypeLogCleanup, runnerID, common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
|
|
newLockUntil := common.GetTimestamp() + 600
|
|
require.NoError(t, RenewSystemTaskLock(task.TaskID, runnerID, newLockUntil))
|
|
|
|
var lock SystemTaskLock
|
|
require.NoError(t, DB.Where("task_id = ?", task.TaskID).First(&lock).Error)
|
|
assert.Equal(t, newLockUntil, lock.LockedUntil)
|
|
|
|
// A different runner cannot renew a lease it does not hold.
|
|
assert.ErrorIs(t, RenewSystemTaskLock(task.TaskID, "runner-b", common.GetTimestamp()+600), ErrSystemTaskLockLost)
|
|
|
|
// After the task finishes it is no longer running, so renew fails.
|
|
require.NoError(t, FinishSystemTask(task.TaskID, runnerID, SystemTaskStatusSucceeded, nil, ""))
|
|
assert.ErrorIs(t, RenewSystemTaskLock(task.TaskID, runnerID, common.GetTimestamp()+600), ErrSystemTaskLockLost)
|
|
}
|
|
|
|
func TestFinishSystemTaskRetainsExecutor(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
task, err := CreateSystemTask(SystemTaskTypeLogCleanup, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
runnerID := "node-1-abc123"
|
|
_, claimed, err := ClaimSystemTask(task.ID, SystemTaskTypeLogCleanup, runnerID, common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
|
|
require.NoError(t, FinishSystemTask(task.TaskID, runnerID, SystemTaskStatusSucceeded, nil, ""))
|
|
|
|
reloaded, err := GetSystemTaskByTaskID(task.TaskID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, reloaded)
|
|
assert.Equal(t, SystemTaskStatusSucceeded, reloaded.Status)
|
|
assert.Equal(t, runnerID, reloaded.LockedBy, "executor-of-record must be retained for history")
|
|
|
|
var lockCount int64
|
|
require.NoError(t, DB.Model(&SystemTaskLock{}).Where("task_id = ?", task.TaskID).Count(&lockCount).Error)
|
|
assert.Equal(t, int64(0), lockCount)
|
|
}
|
|
|
|
func TestSystemTaskUpdatesRequireCurrentLock(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
task, err := CreateSystemTask(SystemTaskTypeLogCleanup, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
runnerID := "runner-a"
|
|
_, claimed, err := ClaimSystemTask(task.ID, SystemTaskTypeLogCleanup, runnerID, common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
|
|
require.NoError(t, DB.Model(&SystemTaskLock{}).
|
|
Where("task_id = ?", task.TaskID).
|
|
Updates(map[string]any{"locked_by": "runner-b"}).Error)
|
|
|
|
assert.ErrorIs(t, UpdateSystemTaskState(task.TaskID, runnerID, testSystemTaskState{Progress: 10}), ErrSystemTaskLockLost)
|
|
assert.ErrorIs(t, FinishSystemTask(task.TaskID, runnerID, SystemTaskStatusSucceeded, nil, ""), ErrSystemTaskLockLost)
|
|
}
|
|
|
|
func TestSystemTaskUpdatesRequireUnexpiredLock(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
task, err := CreateSystemTask(SystemTaskTypeLogCleanup, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
runnerID := "runner-a"
|
|
_, claimed, err := ClaimSystemTask(task.ID, SystemTaskTypeLogCleanup, runnerID, common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
|
|
require.NoError(t, DB.Model(&SystemTaskLock{}).
|
|
Where("task_id = ?", task.TaskID).
|
|
Update("locked_until", common.GetTimestamp()-1).Error)
|
|
|
|
assert.ErrorIs(t, UpdateSystemTaskState(task.TaskID, runnerID, testSystemTaskState{Progress: 10}), ErrSystemTaskLockLost)
|
|
assert.ErrorIs(t, FinishSystemTask(task.TaskID, runnerID, SystemTaskStatusSucceeded, nil, ""), ErrSystemTaskLockLost)
|
|
|
|
reloaded, err := GetSystemTaskByTaskID(task.TaskID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, reloaded)
|
|
assert.Equal(t, SystemTaskStatusRunning, reloaded.Status)
|
|
assert.Empty(t, reloaded.State)
|
|
}
|
|
|
|
func TestUpdateSystemTaskStateIdenticalPayloadDoesNotLoseLock(t *testing.T) {
|
|
// SQLite reports matched rows for unchanged UPDATEs, so this case passed
|
|
// even before the fix. The MySQL regression is covered by
|
|
// TestUpdateSystemTaskStateIdenticalPayloadDoesNotLoseLockConfiguredDatabases.
|
|
truncateTables(t)
|
|
runUpdateSystemTaskStateIdenticalPayloadKeepsLock(t, SystemTaskTypeLogCleanup)
|
|
}
|
|
|
|
func runUpdateSystemTaskStateIdenticalPayloadKeepsLock(t *testing.T, taskType string) {
|
|
t.Helper()
|
|
// Two persists in the same second so MySQL's unchanged-row UPDATE returns 0.
|
|
|
|
task, err := CreateSystemTask(taskType, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
runnerID := "runner-a"
|
|
_, claimed, err := ClaimSystemTask(task.ID, taskType, runnerID, common.GetTimestamp()+60)
|
|
require.NoError(t, err)
|
|
require.True(t, claimed)
|
|
|
|
state := testSystemTaskState{Total: 10, Processed: 10, Progress: 100, Remaining: 0}
|
|
require.NoError(t, UpdateSystemTaskState(task.TaskID, runnerID, state))
|
|
require.NoError(t, UpdateSystemTaskState(task.TaskID, runnerID, state), "identical state persist must not be treated as lock loss")
|
|
|
|
require.NoError(t, FinishSystemTask(task.TaskID, runnerID, SystemTaskStatusSucceeded, map[string]int64{"deleted_count": 10}, ""))
|
|
finished, err := GetSystemTaskByTaskID(task.TaskID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, finished)
|
|
assert.Equal(t, SystemTaskStatusSucceeded, finished.Status)
|
|
}
|