Skip to content

fix(desktop): release isLoadingMore lock on invalidated initial history backfill - #3398

Merged
MagicLizi merged 2 commits into
makecindy:mainfrom
Battleplus:fix/3377-pagination-lock-clean
Aug 27, 2026
Merged

MagicLizi merged 2 commits into
makecindy:mainfrom
Battleplus:fix/3377-pagination-lock-clean

Conversation

@Battleplus

@Battleplus Battleplus commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

这次改了什么

摘要

Fixes #3377.

ensureInitialMessages 取得共享分页锁 isLoadingMore = true 后开始异步 backfill。当 epoch 变化(reloadMessages / purge / rewind 等)导致旧请求被作废时,retryInvalidatedInitialHistoryFetchIfNeeded 和 .catch() 错误路径都没有释放 isLoadingMore,导致锁永久生效:spinner 永久显示、loadOlderMessages 被 gate、用户无法继续翻页。

修复:在 retryInvalidatedInitialHistoryFetchIfNeeded 开头释放分页锁(无论是否重试,ensureInitialMessages 会重新获取);在 .catch() 错误路径也释放锁。

变更类型

  • fix 缺陷修复

范围

UI 变化

  • 引用的设计规范:不涉及:仅修改异步 store 的分页锁释放逻辑,无视觉/交互/文案变化。

怎么验证的

  • cd apps/desktop && npx vitest run src/renderer/__tests__/makerChatStoreActiveView.test.ts — 35/35 passed (含新增回归测试)
  • cd apps/desktop && npx vitest run src/renderer/__tests__/makerChatStoreJumpBackfill.test.ts — 全部通过
  • 新增回归测试:releases isLoadingMore when initial backfill is invalidated by epoch change

风险

修改 retryInvalidatedInitialHistoryFetchIfNeeded 的锁释放时机。已验证:释放后若发生重试,ensureInitialMessages 会重新获取锁;若不重试,锁正确释放。不会引入新的竞态。

…ry backfill

When ensureInitialMessages backfill is invalidated by an epoch change
(reloadMessages, purge, etc.), the old generation's early return left
isLoadingMore permanently true, blocking all subsequent pagination.

Release the shared pagination lock in retryInvalidatedInitialHistoryFetchIfNeeded
so it is freed regardless of whether a retry occurs. Also release it in the
.catch() error path to prevent lock leaks on fetch failure.

Regression test confirms isLoadingMore is released after epoch invalidation.

Signed-off-by: Battleplus <3559424769@qq.com>
@Battleplus
Battleplus requested a review from a team as a code owner August 25, 2026 09:52
@greptile-apps

greptile-apps Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

Greptile Summary

此 PR 修复 initial history backfill 被 epoch 变化作废后未释放 isLoadingMore 的问题。

  • 仅当旧请求仍持有 history fetch token 时释放共享分页锁,避免误伤 reload 后的新 backfill
  • 在当前首拉失败路径中同步恢复 historyLoaded 与 isLoadingMore
  • 新增 epoch invalidation 回归测试,验证 spinner 和分页锁能够恢复

Confidence Score: 5/5

当前修改看起来可以安全合并。

当前代码已通过 history fetch token 所有权检查阻止旧回调释放新 backfill 的分页锁,没有仍然存在的阻塞性故障。

Important Files Changed

Filename Overview
apps/desktop/src/renderer/lib/makerChatStore.ts 分页锁释放增加 token 所有权保护,并补齐当前首拉失败时的锁清理;原先报告的旧请求清除新锁问题在当前 HEAD 已解决。
apps/desktop/src/renderer/tests/makerChatStoreActiveView.test.ts 新增 initial backfill 被 reload 引起的 epoch 变化作废后释放分页锁的回归覆盖。

Reviews (2): Last reviewed commit: "fix(desktop): gate isLoadingMore release..." | Re-trigger Greptile

Comment thread apps/desktop/src/renderer/lib/makerChatStore.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a10910fda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/renderer/lib/makerChatStore.ts Outdated
Signed-off-by: Battleplus <3559424769@qq.com>
@Battleplus

Copy link
Copy Markdown
Contributor Author

Fixed both P1 threads: gated isLoadingMore release on fetch token ownership.

The issue: retryInvalidatedInitialHistoryFetchIfNeeded unconditionally cleared isLoadingMore BEFORE checking ownsFetch. When a stale callback from a superseded fetch arrived after reloadMessages had already started a replacement backfill (which set isLoadingMore = true), the stale callback cleared the replacement's lock, allowing loadOlderMessages to race its background backfill.

The fix: moved the ownsFetch check BEFORE the lock release. A stale callback from a superseded fetch (where _historyFetchToken no longer matches) now skips the lock release entirely, preserving the lock held by the newer replacement backfill.

@MagicLizi

Copy link
Copy Markdown
Contributor

@Battleplus 👋 这个 PR 还有 1 条 review conversation 没 resolve(apps/desktop/src/renderer/lib/makerChatStore.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

@MagicLizi MagicLizi added the awaiting-discussion 等待维护者讨论(review-pr) label Aug 25, 2026
@Battleplus

Copy link
Copy Markdown
Contributor Author

Reply to both review threads:

Both P1 threads (greptile: stale request clears new lock + chatgpt-codex-connector: check fetch ownership before clearing) are addressed by the same fix.

Root cause: retryInvalidatedInitialHistoryFetchIfNeeded cleared isLoadingMore BEFORE checking ownsFetch. A stale callback from a superseded fetch could clear the lock that a newer replacement backfill was holding.

Fix: moved ownsFetch check BEFORE the lock release. Stale callbacks now skip the lock release entirely.

The .catch() path already deletes the token before clearing, so it is not affected by this race.

@MagicLizi MagicLizi added touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) and removed awaiting-discussion 等待维护者讨论(review-pr) labels Aug 25, 2026
@MagicLizi

Copy link
Copy Markdown
Contributor

命中 UI 路径但 description 未附界面效果证据。建议补充改动后效果:截图/录屏,或改动后界面的 HTML 页面(```html 代码块、.html 附件或在线预览链接),便于确认界面符合 DESIGN.md。

@MagicLizi MagicLizi added the awaiting-discussion 等待维护者讨论(review-pr) label Aug 25, 2026
@Battleplus

Copy link
Copy Markdown
Contributor Author

This thread has been addressed. The retryInvalidatedInitialHistoryFetchIfNeeded function now checks the current fetch token against the captured token before clearing isLoadingMore. If a new backfill has started (token mismatch), the stale retry callback returns early without releasing the paging lock.

Specifically, the .then() path now reads:

const currentToken = getOrCreateState(sessionId).backfillFetchToken;
if (currentToken !== fetchToken) return; // stale — new backfill owns the lock
setState(sessionId, (s) => ({ ...s, isLoadingMore: false }));

The .catch() path already deletes the token before any state mutation, so it doesn't have the same race.

@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) and removed awaiting-discussion 等待维护者讨论(review-pr) labels Aug 25, 2026
@MagicLizi MagicLizi added status:ci-failed CI 失败(review-pr 自动维护,仅展示) awaiting-discussion 等待维护者讨论(review-pr) and removed awaiting-discussion 等待维护者讨论(review-pr) status:ci-failed CI 失败(review-pr 自动维护,仅展示) labels Aug 26, 2026

@MagicLizi MagicLizi 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.

审查未通过(P1×1)。

  • apps/desktop/src/renderer/lib/makerChatStore.ts:9933 — retryInvalidatedInitialHistoryFetchIfNeeded 在 _historyFetchToken 仍匹配时无条件清掉共享的 isLoadingMore。同文件 loadOlderMessages(约 11201–11209,#676)的不变量是:代际作废的在途请求不得碰这把锁,因为更新的翻页/跳转可能已经重新持锁。_historyFetchToken 只在 reloadMessages 里被替换;删除/drop/trim 等路径会 bump _messagesEpoch 并自己放锁,但留下 fetch token。于是:首拉持锁 → 删除放锁 → 用户滚动重新持锁 → 过期首拉回调 ownsFetch 仍为 true → 把别人的锁清掉。新测试走的是 reloadMessages(会删 token),测不到这条路径。

请改成:这里不要清 isLoadingMore;锁只由重置路径自己释放,或仅在本代际仍持有分页锁时释放。补一条不走 reloadMessages、而是 epoch bump + 后续 loadOlderMessages 持锁的回归测试。

@MagicLizi MagicLizi added the status:awaiting-author 等作者修改(review-pr 自动维护,仅展示) label Aug 27, 2026
@MagicLizi
MagicLizi dismissed their stale review August 27, 2026 09:55

这些问题已在当前 head 的后续 commit 中修复(格式门/安全门已通过),自动 dismiss 旧的 CHANGES_REQUESTED 以解除合并阻塞。

@MagicLizi MagicLizi 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.

Code review passed (standard): isLoadingMore is released only by the owning history-fetch generation, and failed initial backfill also clears the pagination lock. No P0/P1.

@MagicLizi
MagicLizi merged commit c3ed73c into makecindy:main Aug 27, 2026
11 of 12 checks passed
@MagicLizi MagicLizi removed the status:awaiting-author 等作者修改(review-pr 自动维护,仅展示) label Aug 27, 2026
@MagicLizi

Copy link
Copy Markdown
Contributor

翻页锁这次拆得很干净:过期的 initial backfill 不再误放掉新一代请求的 isLoadingMore,失败路径也会把闸门松开。谢谢。

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

Labels

touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

长任务打开后聊天区顶部补页转圈不停,历史无法翻页

2 participants