280 歌单链路三修:开A→开B 竞态防串(5194 串数据) + 92/200 批边界 Load Failed + 歌单列表盘缓存 + 孤儿 .part 回收
ncm_app(REPORT §26, ncm 158ec367 未部署留主会话): - 打开时快照 plid+代次:appstate_playlist_begin/open_gen/open_idx/reset + appstate_load_playlist_songs_ctx(快照 pl 不读 g_st.playlists[],detail 结果落 staging 锁内原子发布,代次/目标不匹配判 NCM_ERR_STALE 不写全局);playlist_tick 过期丢弃 + appstate_playlist_reset 清窗口防串。修 5194「我喜欢的音乐」3 首/串数据。 - appstate_song_ensure_range fill-and-continue:bn<m 部分返回/单批网络失败非致命 (记 NCM_ERR_NOTFOUND/r2),已物化保留+占位行;worker 首屏 track_ids_count>0 降级非致命(snack)。修 92/200 首歌单点开整页 Load Failed。 - 歌单列表盘缓存 <data_dir>/playlists.bin(magic+ver+ts+数组):home_reload 先缓存 秒渲 + 后台静默刷新,刷新失败有数据不置错态。修返回首页卡 LOADING(275 遗留)。 - 孤儿 .part/.tmp:evict/total_bytes LRU 扫描顺续删 mtime>7 天(in-flight mtime 新鲜安全)。 验证:新 bench/playlist_race_sim(竞态防串/批边界/非致命/盘缓存/reset)+cache_evict_sim case9(7 天回收)+window/pagination/search/stream_sim 零回归+test_ncm_core host+mipsel PASS;build_ncm.sh -Wall -Wextra 0 warning(text 978354/bss 1496484);0 真实请求。 遗留:appstate_logout 未联动删 playlists.bin;g_st.playlists[] 快照拷贝仍有极小交错窗。
This commit is contained in:
1 parent
d8eeef5c57
commit
f0b73e119d
11 files changed
+744
-48
No files matched your search
@@ -2392,3 +2392,99 @@ d9bfe3d7a6eaa76b00161a4e97196eb8 bench/player_flow_sim.c # 新 P12:同 idx
|
||||
```
|
||||
|
||||
(`REPORT.md` 追加 §25;`WORK_STATE.md` 同步锚点见 devdocs。)
|
||||
|
||||
---
|
||||
|
||||
## 26. 歌单链路三修:开A→开B 竞态防串(5194 首 3 首歌/串数据/加载失败)+ 92/200 批边界 Load Failed + 歌单列表盘缓存 + 孤儿 .part 回收(2026-09-01,commit `cd216fa`)
|
||||
|
||||
> 范围:仅 `patch_v2/ncm_app/`。定案见 `devdocs/plan/NCM_CACHE_DESIGN.md §2.2`(歌单列表快照秒开+后台刷新)、
|
||||
> `WORK_STATE.md` 254(滑动窗口)/ 273(搜索不覆写歌单上下文)/ 275 遗留(歌单列表无离线缓存)。
|
||||
> 真机现象(用户实测):①「我喜欢的音乐」5194 首要么只显示 3 首(怀疑串数据)要么加载失败要么卡久后失败;
|
||||
> ②92 与 200 首歌单点开「加载失败」、72 能开;③从歌曲列表返回首页必卡几秒 LOADING;
|
||||
> ④孤儿 `.part` 无回收(275 遗留)。风控纪律:真实 NCM 请求 **0 次**(全离线 stub);**未部署真机**(留主会话)。
|
||||
|
||||
### 26.1 根因(文件:行)
|
||||
|
||||
- **① 5194 首(3 首/串数据/加载失败)= 歌单上下文并发竞态(三条叠加)**:
|
||||
1. **`appstate_load_playlists`(`app/logic/appstate.c:182`)`memset(g_st.playlists, ...)` 清空再回填**。用户「进歌单→快速返回首页→再进另一个歌单」时,home_worker 的网络拉取(`appstate_load_playlists`)与 playlist_worker 并发;旧 `appstate_load_playlist_songs_page` 只在起点取一次 `pl = &g_st.playlists[idx]`,`detail` 内再读 `pl->id`——若此刻数组被 memset,`pl->id` 为空串 → `detail("")` 取回**空/少量** trackIds(3 首)或异常 → 「只显示 3 首」「串数据」。
|
||||
2. **worker 单飞(`g_plthr_on`)只限制并发数量,不校验结果归属**:开 A→未载完→开 B,A 的迟到结果(全量 trackIds + 批物化)平铺进全局 `song_ids/window/track_ids_count`;UI 在 B 标题下读到 A 数据(串数据);旧 `playlist_tick` 仅在 `g_plloaded_idx != active` 时重拉,但重拉前的 `pl_apply` 已把 A 数据画在 B 标题下(瞬时串数据)。
|
||||
3. **首屏把任何曲目批错误映射整页失败**:`appstate_song_ensure_range`(旧 `app/logic/appstate.c:360`)批内 `bn<m` 置 `rc=NCM_ERR_JSON`,`playlist_worker` 首屏 `g_load_err=rc` → 整页「加载失败」(见根因②)。
|
||||
- **② 92/200 首歌单「加载失败」,72 能开 = 批内个别缺歌 → 整页失败(放大)**:服务端 `song_detail` 对个别歌(下架/版权/权限变化)少返回(`bn<m`,批内缺 1 首即触发);旧 `appstate_song_ensure_range` 对**任何** `bn<m` 直接 `rc=NCM_ERR_JSON`,`playlist_worker` 首屏把 `rc` 映射 `g_load_err=rc`(`app/ui/playlist.c` 旧)→ 整页「加载失败」。与歌单长度无关,纯「批内 1 首异常全灭」放大;72 首恰好全量返回、92/200 批内恰有异常。`NCM_SONG_BATCH=100`,92=1 批边界、200=2 批,均可能含个别异常歌。
|
||||
- **③ 返回首页必卡几秒 LOADING = 歌单列表无磁盘缓存**:`home_page_enter`(`app/ui/home.c:366`)每次进入都调 `home_reload()` → `home_worker` → `appstate_load_playlists`(`app/logic/appstate.c:177`)每次都走 `ncm_api_user_playlist` 网络拉取(`g_st.playlists[]` 仅内存),无磁盘缓存(WORK_STATE 275 遗留)。断网还回错误、列表空态。
|
||||
- **④ 孤儿 `.part` 无回收**:中断残体 `.part`/`.tmp` 不进 LRU 池(防在播文件误删,REPORT §24),但用户不再点该曲则不续传、占磁盘无回收。
|
||||
|
||||
### 26.2 修法
|
||||
|
||||
1. **打开时快照 plid + 代次 seq,回调校验不匹配即丢弃(修①,核心)**:
|
||||
- `core/ncm_common.h`:新增 `NCM_ERR_STALE = -16`(异步结果过期)。
|
||||
- `app/logic/appstate.c`:新增打开上下文 `appstate_playlist_begin(idx)/open_gen()/open_idx()/reset()`(`L27-49`,记「打开代次+目标下标」);
|
||||
新增 `appstate_load_playlist_songs_ctx(pl,is_search,ctx_gen,ctx_idx,off,lim)`(`L523`)与内部 `appstate_load_playlist_songs_impl`(`L477`):
|
||||
- `pl` 为**打开时快照副本**——`detail` 只读快照 `id`,不再读 `g_st.playlists[]`(免疫 home_worker 并发 memset);
|
||||
- offset=0 的 detail 结果落 `static s_build[]` staging(`L502`),锁内**原子发布索引**(防 worker 写 `song_ids` 与播放线程 `song_fetch` 读竞态;`appstate_song_fetch` `L437` 索引读改加锁);
|
||||
- 每次 `detail` 后/加载末校验 `ctx_gen==当前代次 && ctx_idx==当前目标`,不匹配 → `NCM_ERR_STALE` 且**不写任何全局**(`L529/L537-539`)。
|
||||
- `app/ui/playlist.c`:新增打开快照 `pl_target_t g_tgt`(`L53`)与 `g_pl_stale`/`g_plloaded_gen`;`playlist_refresh`(`L302`)启动 worker 前锁内快照目标并 `appstate_playlist_begin`;`playlist_worker`(`L231`)用快照 `id` 调 `appstate_load_playlist_songs_ctx(&tpl,...)`;`playlist_tick`(`L329`)`g_pl_stale || loaded_idx != active || loaded_gen != open_gen` → `appstate_playlist_reset()` 清窗口 + 对当前目标重拉(不把旧歌单数据画在新歌单标题下,瞬时串数据消除)。
|
||||
2. **批内缺失/单批网络失败非致命(修②)**:`appstate_song_ensure_range`(`L317`,批处理 `L423`)改 **fill-and-continue**:`bn<m`(部分返回)写已返回的、缺槽留 invalid(下次 ensure 再试),记 `first_err=NCM_ERR_NOTFOUND`;`r2!=OK`(单批网络失败)跳过本批网格、其余批照常补,记 `first_err=r2`;返回首个非致命错误码(而非把整段置失败)。`playlist_worker`(`L257-261`)首屏「索引成功但 `track_ids_count>0`」把非致命错误降级为 `g_ens_err`(snack)+ `g_load_err=0`——页面保留已物化 + 占位行,不再整页 Load Failed(索引失败/鉴权失败仍整页空态可点重试)。
|
||||
3. **歌单列表盘缓存(修③,NCM_CACHE_DESIGN §2.2 stale-while-revalidate)**:`appstate_playlists_cache_save/load`(`L200/L222`,`<data_dir>/playlists.bin`:magic+ver+时间戳+数组;刻意不进 LRU 音频池,防被「清理缓存」误清);`appstate_load_playlists`(`L188`)网络成功即覆写缓存。`home_reload`(`L223`)先 `appstate_playlists_cache_load` 秒渲(有缓存不显示 LOADING),后台 worker 静默刷新;`home_worker`(`L216`)刷新失败但有数据(`playlist_count>0`)不置错误态(断网可离线浏览);`home_draw_empty`(`L167`)有数据即显示列表。手动刷新按钮(`home_on_tap`↻ → `home_reload`)保留。
|
||||
4. **孤儿 `.part` 回收(修④)**:`core/ncm_cache.c` `ncm_cache_evict`(`L176/L187`)与 `ncm_cache_total_bytes`(`L325/L335`)LRU 扫描时顺手续删 `mtime > 7 天`(`NCM_ORPHAN_PART_DAYS=7`,`L25`)的 `.part`/`.tmp`(在播/刚中断的 mtime 新鲜安全);`ncm_cache_clear`(清理缓存)本删全部 `song_*`(含 `.part`/`.tmp`),满足「清理缓存一并清所有 .part」。
|
||||
|
||||
### 26.3 测试证据(0 真实 NCM 请求)
|
||||
|
||||
| 项 | 方式 | 结果 |
|
||||
|---|---|---|
|
||||
| ①异步竞态(开A未完→开B,A 迟到结果丢弃不串 B) | **新 `bench/playlist_race_sim.c`**(真实 appstate + 本地 stub) | **PASS**:open A 载 92 → begin(B)→A 以旧代次 load 返回 `NCM_ERR_STALE`(-16)不写全局 → B 以当代次 load 采纳(count=200);open 上下文正确指向 B |
|
||||
| 快照 plid 免疫 home_worker memset | `bench/playlist_race_sim.c` | **PASS**:打开时快照后 `memset(g_st.playlists)` → 快照载 92 仍准、detail 收到快照 `plFav`(非空) |
|
||||
| ②92/200 批边界 | `bench/playlist_race_sim.c` | **PASS**:72/92 首屏 1 detail+1 批全物化;200 首屏 1 批(100)+二批 ensure(200) 共 2 次 song_detail、tail 可达 |
|
||||
| ②部分返回/单批网络失败非致命 | `bench/playlist_race_sim.c` | **PASS**:`bn<m` 返回 `NCM_ERR_NOTFOUND` 但索引保留(92)、已返回的 91 首物化、worker 降级为非致命(页面不 wipe);单批网络失败同样非致命(page 不 wipe,占位保留) |
|
||||
| ②盘缓存回环 | `bench/playlist_race_sim.c` | **PASS**:save(2 歌单)→load 恢复 count=2、ts>0、id/name 一致 |
|
||||
| ④.part 7 天回收 | `bench/cache_evict_sim.c` 新 case9 | **PASS**:8 天 `.part`/`.tmp` 被 evict 扫描删、新鲜 `.part` 保留 |
|
||||
| 现有回归 | window_sim / pagination_sim / search_sim / cache_evict_sim(1-8) / stream_sim | 全 **PASS**(6000 首滚动 / 5191 无截断 / 搜索队列 / LRU 续传 / 边下边播全矩阵+resume) |
|
||||
| core 回归 | `test_ncm_core_host` + qemu `test_ncm_core_mipsel` | 宿主 + mipsel **PASS** |
|
||||
| mipsel 构建 | `bash build_ncm.sh` 自有代码 `-Wall -Wextra` | **0 warning**;`text 978354 / data 1624 / bss 1496484`(bss +~132KB 来自 `s_build[]` staging) |
|
||||
| 宿主离屏渲染 | `build_ncm_host.sh` + `NCM_PAGE=home/playlist/search` | rc=0,渲染正常 |
|
||||
|
||||
### 26.4 md5(本批改动文件 + 产物)
|
||||
|
||||
```
|
||||
158ec3672aa7708f791dce7d79f7d8b1 ncm # mipsel 静态 App(新;未部署,留主会话)
|
||||
a3c336a9e863017a84827c3ad3e204de ncm_host # 宿主离屏调试二进制(重链)
|
||||
7922fc00d19666e64eb353d6685aba0a core/ncm_common.h # +NCM_ERR_STALE(-16)
|
||||
a6d561a33797da2ac260d46d53d76357 core/ncm_cache.c # §26:7 天孤儿 .part/.tmp 顺手续删(evict/total_bytes)
|
||||
6c4780a3253b2187224db684a9243fc9 app/ncm_app_internal.h # 打开上下文 API + 快照 loader + 盘缓存声明
|
||||
cca1a51d031092e0bdbf184266031e67 app/logic/appstate.c # §26:打开代次/快照 ctx loader + 部分非致命 ensure + 歌单列表盘缓存
|
||||
c098a80b69e55f0be4b38ee84a73b5c7 app/ui/playlist.c # §26:快照+代次 worker,tick 丢弃+reset 防串
|
||||
01975811d562c231022c607eed766f3a app/ui/home.c # §26:歌单列表缓存秒渲 + 后台静默刷新
|
||||
b7a2bfaec3c3d7bd29a151072cd40f32 bench/cache_evict_sim.c # 新 case9:.part/.tmp 7 天回收(+ncm_now_ms 桩)
|
||||
56673b0e56ebb605ac575ce97c5e27a8 bench/playlist_race_sim.c # 新:竞态防串/批边界/非致命/盘缓存/reset(0 网络)
|
||||
```
|
||||
|
||||
### 26.5 出入清单(只改 `patch_v2/ncm_app/`)
|
||||
|
||||
```
|
||||
修改:
|
||||
core/ncm_common.h # +NCM_ERR_STALE(-16)
|
||||
core/ncm_cache.c # 孤儿 .part/.tmp 7 天顺续删(evict/total_bytes 扫描时)
|
||||
app/ncm_app_internal.h # 打开上下文(gen/idx/begin/reset) + 快照 loader 声明 + 盘缓存声明 +
|
||||
# appstate_range_loaded 声明恢复(补回)
|
||||
app/logic/appstate.c # 打开代次上下文 + 快照式 appstate_load_playlist_songs_ctx/impl +
|
||||
# 索引用 staging 锁内原子发布 + song_fetch 索引读加锁 +
|
||||
# ensure_range fill-and-continue(部分/单批非致命)+ 歌单列表盘缓存
|
||||
app/ui/playlist.c # 打开快照(g_tgt)+代次;worker 用快照 id 走 ctx loader;
|
||||
# tick 过期(idx/gen/stale)丢弃 + appstate_playlist_reset 防串;
|
||||
# 首屏部分失败降级为非致命(snack)
|
||||
app/ui/home.c # home_reload 缓存秒渲 + 后台静默刷新;home_worker 刷新失败不置错;
|
||||
# home_draw_empty 有数据即显示列表
|
||||
bench/cache_evict_sim.c # 新 case9(7 天回收)+ ncm_now_ms 桩
|
||||
新增:
|
||||
bench/playlist_race_sim.c # 歌单链路三问题离线回归(0 网络)
|
||||
REPORT.md # 本批记录(§26)
|
||||
(未动 sdk/、meow_launcherd/、vendor/;未部署真机;未动设备系统/恢复区)
|
||||
```
|
||||
|
||||
### 26.6 诚实出入 / 遗留
|
||||
|
||||
- **真机未部署未验**(留主会话统一)。真机验收点:①「我喜欢的音乐」5194 首正常显示/滚动/回滚无 3 首或串数据;快进回首页再进其它歌单不再串;②92/200 首歌单点开正常(不再整页 Load Failed,个别缺歌只占位+snack);③从歌曲列表返回首页秒出(缓存渲染)再被后台刷新替换;④清理缓存清掉全部 `.part`,LRU 扫描顺手清 7 天前 `.part`。
|
||||
- **打开快照残留竞态窗口**:`playlist_refresh` 的 `g_tgt.pl = *pcur`(读 `g_st.playlists[idx]`)与 home_worker 的 `memset` 仍可能瞬时交错(相差若干指令);`pl_cur()` 快照拷贝前加了 `!pcur->id[0]` 防御(空 id 直接报错可重试),不产生 3 首垃圾数据,但理论上仍有一次「取到半零 id」的极小概率——后续可给 `g_st.playlists[]` 加互斥彻底消除(本次未做,保持最小改动)。
|
||||
- **`appstate_song_ensure_range` 返回首个非致命错误码**(`NCM_ERR_NOTFOUND`/网络码)供 UI snack;语义从「整段失败」改为「已物化保留+占位自愈」,`appstate_range_loaded` 对未覆盖区间仍返回 0(触发后续 ensure 重试)。
|
||||
- **歌单列表缓存无版本化清理**:`playlists.bin` 靠 ver/magic 判 miss;数组改版/账号切换不主动失效(账号掉线重登前 `appstate_logout` 未清 playlists.bin——本次 `logout` 未联动删缓存,**下一账号可能看到上一账号列表直到首次网络成功覆写**;留主会话)。
|
||||
- **`.part` 7 天回收只在 LRU 扫描(evict/total_bytes)时触发**:无网/不触发 evict 时不会主动清;属「惰性删除」(任务要求)。`.tmp`(在播写穿)mtime 持续更新,安全。
|
||||
- **未动 sdk/、meow_launcherd/、vendor/**;部署链路同 §13.5 留主会话。commit `cd216fa`(git 记录)。
|
||||
Reference in new issue
Block a user