fix(tasks): 任务队列 - 限制缓存回源并修复并发租约与过期写入

This commit is contained in:
ddCat
2026-09-06 00:28:45 +08:00
parent 1ff5e5cada
commit bf0c4454fa
23 changed files with 1098 additions and 59 deletions

View File

@@ -174,7 +174,7 @@ func RegisterTaskRoutes(r *gin.RouterGroup, svc *service.Service) {
fail(c, http.StatusBadRequest, err)
return
}
initial, err := svc.TaskTextReplay(user.ID, c.Param("id"), after)
initial, err := svc.CachedTaskTextReplay(c.Request.Context(), user.ID, c.Param("id"), after)
if err != nil {
failService(c, err)
return
@@ -308,7 +308,7 @@ func streamTaskTextEvents(c *gin.Context, svc *service.Service, userID string, t
c.Writer.Flush()
case <-pollTicker.C:
}
next, err := svc.TaskTextReplay(userID, taskID, after)
next, err := svc.CachedTaskTextReplay(c.Request.Context(), userID, taskID, after)
if err != nil {
writeTaskTextSSE(c, "error", 0, map[string]string{"message": "任务文本流不可用"})
return

View File

@@ -1,14 +1,19 @@
package handler
import (
"context"
"net/http/httptest"
"strings"
"testing"
"time"
"infinite-canvas/backend/internal/model"
"infinite-canvas/backend/internal/repository"
"infinite-canvas/backend/internal/service"
"github.com/gin-gonic/gin"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func TestTaskTextEventCursorPrefersQueryAndSupportsLastEventID(t *testing.T) {
@@ -32,6 +37,42 @@ func TestTaskTextEventCursorPrefersQueryAndSupportsLastEventID(t *testing.T) {
}
}
func TestStreamTaskTextEventsCachedReplayReachesTerminalWithoutDuplicateDelta(t *testing.T) {
t.Setenv("REDIS_URL", "")
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
if err := db.AutoMigrate(&model.Task{}, &model.TaskTextDelta{}); err != nil {
t.Fatal(err)
}
if err := db.Create(&model.Task{ID: "task", UserID: "user", Status: model.TaskStatusRunning}).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&model.TaskTextDelta{ID: "delta", UserID: "user", TaskID: "task", Sequence: 8, Content: "text", ExpiresAt: time.Now().Add(time.Hour)}).Error; err != nil {
t.Fatal(err)
}
svc := service.New(repository.New(db), t.TempDir())
t.Cleanup(func() { _ = svc.Close() })
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
defer cancel()
initial, err := svc.CachedTaskTextReplay(ctx, "user", "task", 7)
if err != nil {
t.Fatal(err)
}
if err := db.Model(&model.Task{}).Where("id = ?", "task").Updates(map[string]any{"status": model.TaskStatusSucceeded, "progress": 100}).Error; err != nil {
t.Fatal(err)
}
response := httptest.NewRecorder()
c, _ := gin.CreateTestContext(response)
c.Request = httptest.NewRequest("GET", "/api/tasks/task/text-events", nil).WithContext(ctx)
streamTaskTextEvents(c, svc, "user", "task", 7, initial)
body := response.Body.String()
if strings.Count(body, "id: 8\nevent: delta") != 1 || !strings.Contains(body, "event: terminal") || !strings.Contains(body, `"progress":100`) {
t.Fatalf("unexpected cached SSE stream: %q", body)
}
}
func TestStreamTaskTextEventsWritesSequenceAndTerminalEvent(t *testing.T) {
gin.SetMode(gin.TestMode)
response := httptest.NewRecorder()