Repository navigation
Conversation
added 2 commits
October 12, 2026 21:00
改自 TNT-Likely#368(该 PR 被关闭未并入 main,3.8.6 == main 仍存在此缺陷)。 根因: _applyPullPage 整页事务失败时 return blocked=true,cursor 不推进。 下次 pull 仍拉同一页 → 同一条坏 change 再次失败 → 死循环。 现场实证(iOS 3.8.6, change_id 3501-4000 页, 500 条 = 239 交易 + 235 带自定义图标的分类 + 13 标签 + 13 账户): - 反复 since=3500&limit=500, 服务端正常回 server_cursor=4000 - 整页 apply 失败 → blocked=true → cursor 停住 - 外层调度重试 73 次后 19:52:07 才推进到 4500(瞬时错误,非永久脏数据) 修复: 快路径整页批量事务 → 失败则降级逐条独立事务, 瞬时错误(busy/locked)中止本页重试,持久错误隔离跳过并推进 cursor。 Fixes TNT-Likely#367
drift 2.28.2 全仓不设置 busy_timeout(SQLite 默认 0),任何并发写争用都会 立刻抛 SQLITE_BUSY。sync pull 的整页事务撞上后台写(图标下载队列/后台 widget isolate)即整页回滚 → blocked=true → cursor 不推进。 结合前一个提交的逐条隔离,把瞬时争用即失败变成等锁 5s 后成功, 进一步降低整页重拉概率。
Author
|
在 iOS 3.8.6 (build 224) 上稳定复现,附服务端侧观测,可作为定位参考: 症状:全量同步永远同步不完 / 卡在同一页。 服务端侧(
卡住页
即该页 commit 后触发的 两个叠加的成因:
建议:#368 的方案(批量事务失败 → 降级逐条独立事务 + 隔离跳过 + 推进 cursor) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景:现场实证(服务端侧可复现)
服务端 侧观测到 iOS 3.8.6 客户端死循环:
GET /sync/pull?since=3500&limit=500反复拉同一页200 returned=500 has_more=true server_cursor=4000(服务端游标逻辑正确)blocked=true)dev_2e6d018a712f6de023c2cad1e8ff3fe6(iOS 3.8.6 build 224);随后dev_2535…复现该页
change_id 3501–4000(500 条)构成:即"每页 commit 后
unawaited(drainCustomIconQueue())用 4 并发下载图标并写库"与"下一页 500 条整页事务"在同一 SQLite 文件上抢写锁。
根因(两层,缺一不可)
层 1:整页事务没有 per-change 隔离(= #367)
_applyPullPage在单个整页事务里 apply 全部 change。页内任一条抛非 busy 异常→ 整页回滚 → 返回
blocked: true→ cursor 不推进 → 外层永远重拉同一页。_applyOneWithBusyRetry只认sqlite/database+busy/locked,且只有 2 次快速重试(~50/100ms)。
层 2:SQLite 没有 busy_timeout(瞬时争用即失败)
lib/data/db.dart用NativeDatabase.createInBackground(file),没有setup回调。实测 drift 2.28.2 全仓不设置
busy_timeout(grep -rn busy_timeout于 pub.dev发布的
drift-2.28.2包内 0 命中),SQLite 默认值 = 0:这解释了"为什么重试 73 次才过":不是数据有毒,而是撞锁窗口随机。
修复
commit 1(对应 #368 的最终版,逐字节套用后仅偏移,无冲突):
记入
pullErrors隔离跳过,cursor 照常推进,死循环被打破blocked=true中止本页重试,避免好 change 被永久跳过(
applyRemoteChange幂等,重放安全)markResolved,UI 仍可见commit 2(层 2 的对症加固,3 行):
把"撞锁即失败"变成"等锁最多 5s",从源头消掉层 2。
测试
test/cloud/sync/sync_engine_e2e_test.dart新增/修改:批量事务失败 → 降级逐条隔离:好 change 入库,坏 change 进 pullErrors,cursor 推进降级路径:失败的 changeId 不被 markResolved,UI 仍可见_isTransientError 判定逻辑正确兼容性