Skip to content

Fix task polling result URLs and reset locking - #5976

Open
iranyxiao-lab wants to merge 1 commit into
QuantumNous:mainfrom
iranyxiao-lab:fix/task-polling-subscription-reset
Open

Fix task polling result URLs and reset locking#5976
iranyxiao-lab wants to merge 1 commit into
QuantumNous:mainfrom
iranyxiao-lab:fix/task-polling-subscription-reset

Conversation

@iranyxiao-lab

@iranyxiao-lab iranyxiao-lab commented Jul 7, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

📝 变更描述 / Description

修复两个异步任务和订阅重置相关问题:

  1. 任务轮询解析 New API 响应时改用 dto.TaskResponse[dto.TaskDto],读取顶层 result_url 并保存到任务私有结果 URL。这样 New API 作为上游异步任务服务时,成功任务不会因为按 model.Task 解析而丢失结果地址。
  2. 管理员重置用户订阅/套餐订阅时改用现有 lockForUpdate(tx) helper。GORM v2 会忽略旧的 Set("gorm:query_option", "FOR UPDATE"),该 helper 能在支持的数据库上生成行锁,并在 SQLite 下保持兼容。

补充了一个回归测试,覆盖 New API TaskDto.result_url 轮询结果保存流程。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。已注意到 fix video task successful upstream result parsing #5805/fix(task): support xAI grok-imagine-video task polling #5927 与任务轮询相关,但它们没有覆盖顶层 TaskDto.result_url 解析,也没有覆盖订阅重置行锁问题。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

通过:

go test ./service -run "TestUpdateVideoTasksPreservesNewAPITaskDtoResultURL|TestUpdateVideoTasks"
go test ./model -run "TestAdminReset|TestLockForUpdate|TestSubscription"

补充验证:

go test ./model ./service

结果:./model 通过;./service 中现有的 channel affinity usage cache 用例失败,失败项为 TestObserveChannelAffinityUsageCacheByRelayFormat_MixedModeTestObserveChannelAffinityUsageCacheByRelayFormat_UnsupportedModeKeepsEmpty。这两个失败与本 PR 修改的任务轮询和订阅重置代码无关。

Summary by CodeRabbit

  • Bug Fixes
    • Improved subscription reset handling to use more reliable row locking during admin operations.
    • Fixed video task polling so newer API responses correctly preserve the result URL, status, and completion progress.
    • Added a fallback to use the failure message as the result URL when the upstream success response omits it.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Admin subscription reset transactions now use a shared lockForUpdate helper instead of a no-op GORM v1 query option, ensuring actual row locking. Task polling parses upstream responses as dto.TaskDto to preserve result_url, with a fallback to FailReason and corresponding test updates.

Changes

Subscription Reset Row Locking

Layer / File(s) Summary
Row lock helper usage
model/subscription.go
adminResetUserSubscriptionsByPlanTx and adminResetPlanSubscriptionsTx now call lockForUpdate(tx) instead of the ineffective tx.Set("gorm:query_option", "FOR UPDATE") before filtering subscriptions to reset.

Estimated code review effort: 2 (Simple) | ~10 minutes

Task Polling result_url Preservation

Layer / File(s) Summary
TaskDto parsing and Url derivation
service/task_polling.go
The "new API response format" branch now unmarshals into dto.TaskResponse[dto.TaskDto], assigns Status directly, and derives Url from trimmed ResultURL with a fallback to FailReason when empty and status is success.
Test adaptor and coverage
service/task_polling_test.go
taskPollingFetchAdaptor gains a responseBody map for deterministic per-task HTTP responses; a new test TestUpdateVideoTasksPreservesNewAPITaskDtoResultURL verifies ResultURL is preserved and status/progress updated correctly.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Poller as UpdateVideoTasks
  participant Adaptor as TaskAdaptor
  participant Upstream as Upstream API
  participant DB as Task Storage

  Poller->>Adaptor: FetchTask(taskID)
  Adaptor->>Upstream: request task status
  Upstream-->>Adaptor: TaskResponse[TaskDto] with ResultURL
  Adaptor-->>Poller: response body
  Poller->>Poller: unmarshal into dto.TaskDto
  alt ResultURL present
    Poller->>Poller: taskResult.Url = trimmed ResultURL
  else ResultURL empty and status success
    Poller->>Poller: taskResult.Url = FailReason
  end
  Poller->>DB: persist updated task status and Url
Loading

Poem

A lock that slipped, now firmly clasped,
No more races, reset tasks grasped 🔒
A URL once lost in the void,
Now trimmed and kept, not destroyed 🎬
Hop, hop — tests confirm it's true,
This rabbit ships clean code for you! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses both linked requirements: preserving top-level TaskDto.result_url and restoring row locking for subscription resets.
Out of Scope Changes check ✅ Passed The added regression test supports the polling fix, and no unrelated code changes are evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: task polling result URL handling and reset locking.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
service/task_polling_test.go (1)

135-178: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for the FailReason fallback branch.

The new test only exercises the case where ResultURL is populated. It doesn't cover the ResultURL == "" && Status == Success fallback to FailReason that was introduced in service/task_polling.go (Lines 478-480), leaving that branch of the fix untested.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/task_polling_test.go` around lines 135 - 178, Add a test case in
TestUpdateVideoTasksPreservesNewAPITaskDtoResultURL coverage for the TaskDto
success path where ResultURL is empty and FailReason is populated. Use the
existing UpdateVideoTasks flow with taskPollingFetchAdaptor and TaskDto so the
saved model.Task.PrivateData.ResultURL is asserted to fall back to FailReason
when Status is TaskStatusSuccess and ResultURL is blank.
service/task_polling.go (1)

471-486: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

LGTM on parsing as dto.TaskResponse[dto.TaskDto]; verify the FailReason→Url fallback and consider deduplicating with Task.GetResultURL.

The new branch replicates the same "ResultURL, else FailReason" compatibility fallback already encapsulated in model.Task.GetResultURL() (per the retrieved context snippet), but re-implements it inline instead of reusing shared logic. This duplicates the compatibility rule in two places, and if it changes in one but not the other, the codebase will silently diverge.

Separately, when ResultURL is empty and status is Success, taskResult.Url is set straight from FailReason without any validation that it actually looks like a URL/proxy string. Since taskResult.Url is later stored verbatim into task.PrivateData.ResultURL (Line 540) when non-empty, an upstream that reports Success with an ordinary (non-URL) fail_reason string would get that text persisted as the task's result URL.

♻️ Suggested consolidation
-		taskResult.Url = strings.TrimSpace(t.ResultURL)
-		if taskResult.Url == "" && model.TaskStatus(t.Status) == model.TaskStatusSuccess {
-			taskResult.Url = strings.TrimSpace(t.FailReason)
-		}
+		if model.TaskStatus(t.Status) == model.TaskStatusSuccess {
+			// Mirrors model.Task.GetResultURL() historical compatibility fallback.
+			taskResult.Url = strings.TrimSpace(t.ResultURL)
+			if taskResult.Url == "" {
+				taskResult.Url = strings.TrimSpace(t.FailReason)
+			}
+		}

Based on learnings, this same file was previously flagged for keeping auth/context derivation consistent across call sites (see service/task_polling.go learning on reusing PrivateData.Key); the same principle of not re-deriving shared logic applies here to the ResultURL/FailReason fallback semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/task_polling.go` around lines 471 - 486, The `updateVideoSingleTask`
parsing path is duplicating the `ResultURL`/`FailReason` compatibility fallback
instead of reusing the shared `model.Task.GetResultURL()` logic. Replace the
inline `FailReason` assignment with a call to the existing `Task.GetResultURL`
behavior (or equivalent shared helper) so the fallback rule stays consistent in
one place. Also ensure `taskResult.Url` only accepts a valid URL/proxy string
before persisting it, rather than storing any non-empty `FailReason` verbatim.
Keep the new `dto.TaskResponse[dto.TaskDto]` branch aligned with the same
result-url semantics used elsewhere in the file.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@service/task_polling_test.go`:
- Around line 135-178: Add a test case in
TestUpdateVideoTasksPreservesNewAPITaskDtoResultURL coverage for the TaskDto
success path where ResultURL is empty and FailReason is populated. Use the
existing UpdateVideoTasks flow with taskPollingFetchAdaptor and TaskDto so the
saved model.Task.PrivateData.ResultURL is asserted to fall back to FailReason
when Status is TaskStatusSuccess and ResultURL is blank.

In `@service/task_polling.go`:
- Around line 471-486: The `updateVideoSingleTask` parsing path is duplicating
the `ResultURL`/`FailReason` compatibility fallback instead of reusing the
shared `model.Task.GetResultURL()` logic. Replace the inline `FailReason`
assignment with a call to the existing `Task.GetResultURL` behavior (or
equivalent shared helper) so the fallback rule stays consistent in one place.
Also ensure `taskResult.Url` only accepts a valid URL/proxy string before
persisting it, rather than storing any non-empty `FailReason` verbatim. Keep the
new `dto.TaskResponse[dto.TaskDto]` branch aligned with the same result-url
semantics used elsewhere in the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 017db407-d561-4e83-8a90-1371e16deb71

📥 Commits

Reviewing files that changed from the base of the PR and between becc18e and 05b5ff1.

📒 Files selected for processing (3)
  • model/subscription.go
  • service/task_polling.go
  • service/task_polling_test.go

@seefs001

seefs001 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

PR还是拆开俩吧,for update那个可以合并

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New API 异步任务轮询会丢失顶层 result_url 订阅重置仍使用 GORM v1 FOR UPDATE 写法导致行锁不生效

2 participants