fix(ncm): 缓存命中改单请求出参,移除全局命中计数(评审 P2-2/P2-1)

问题(P2-2):离线判定用 `ncm_apicache_hits()` 前后差值判「本次是否缓存命中」——
①其它 worker 的命中会污染差值 → 误判「已回在线」;②盘缓存分支的 g_hits++ 在 g_mu 锁外,
跨线程自增/64 位读存在竞争与撕裂可能。

修法(出参方案):
- `api_post_cached(..., int *from_cache)`:命中置 1(未走网络),miss/失败为 0;
  四个调用点显式传参。
- 新增 `ncm_api_user_playlist_ex(..., int *out_from_cache)`;out_n 与原接口一致**必填**
  (NULL→NCM_ERR_ARG,修入口容忍 NULL、成功路径却无条件解引用的契约不一致,评审 P2-1);
  out_from_cache 可 NULL。旧 `ncm_api_user_playlist` 为薄包装(调用方零改动)。
- `appstate_load_playlists_ex` 透传该标志;home_worker 用本次请求的 from_cache 判定是否
  回在线;歌单页只在失败时同步离线态(成功可能是缓存命中,不据此清离线)。
- 删除 `ncm_apicache_hits()`/g_hits(无使用者,同时消除数据竞争);盘缓存命中分支两行
  合并为一行(行为不变,重构)。
- 测试:session_gate_sim 新增 D2 五例(miss→0 且真网络 +1 次 / 命中→1 且 0 请求 /
  失败→0 / 兼容入口不崩 / out_n 必填契约);playlist_race_sim、plpage_sim 补 `_ex` 桩。

验证:session_gate_sim 88 PASS/0 FAIL;test_ncm_core_host 139 PASS/0 FAIL;
http_truncate_sim 13 PASS/0 FAIL;核心 13 TU(-Wall -Wextra)0 warning。
This commit is contained in:
AskaEth committed 2026-11-05 11:59:33 +08:00
1 parent b1d607d43f
commit 31879f3302
12 files changed
+134 -49

No files matched your search

+14 -10
View File
@@ -3539,16 +3539,16 @@ MP3 ≤128k→标准,>128k→极高 HQ。列表显示**实际档**(如 hires
|---|---|
| `core/ncm_session.h/.c`(新) | 纯逻辑分类层:`ncm_err_classify`(NETWORK/AUTH/OTHER;`-4/-5/-6/-17` 与历史 mbedtls 裸负码→NETWORK,仅 `-13`→AUTH)、`ncm_http_status_class/err`(401/403→AUTH;408/425/429/5xx→SVC;其它非 2xx→HTTP)、`ncm_gate_decide`(一次请求→在线/离线/退登录/保持)、`ncm_session_boot_decide`(有本地会话就先信它,无网→离线,绝不因连不上进扫码)。 |
| `core/ncm_common.h` | 新增 `NCM_ERR_SVC = -17`(服务端 5xx/429/-460:暂不可用,可重试,**不是**登录失效)。 |
| `core/ncm_http.c` | 新增 `http_ret_norm`:超时→`NCM_ERR_TIMEOUT`、证书→`NCM_ERR_TLS`、其它 mbedtls 负码→`NCM_ERR_NET`,在 `done:` 统一归一(532 行)。 |
| `core/ncm_api.c` | `api_status_gate`:非 2xx 先按状态定性(401/403→AUTH、5xx/429→SVC、其它→HTTP),不再把 5xx HTML 丢给 JSON 解析;业务码映射加 302→AUTH、`-460`→SVC(113-141 行)。 |
| `core/ncm_http.c` | 新增 `http_ret_norm`:超时→`NCM_ERR_TIMEOUT`、证书→`NCM_ERR_TLS`、其它 mbedtls 负码→`NCM_ERR_NET`,在 `done:` 统一归一(532 行)。**已是 NCM 错误码的一律原样返回**(含 `NCM_ERR_JSON`:畸形分块/帧体是数据错误,不得改判成网络不可达——评审 P2-1)。 |
| `core/ncm_api.c` | `api_status_gate`:非 2xx 先按状态定性(401/403→AUTH、5xx/429→SVC、其它→HTTP),不再把 5xx HTML 丢给 JSON 解析;业务码映射加 302→AUTH、`-460`→SVC(113-141 行)。`api_post_cached` 新增 `from_cache` 出参;新增 `ncm_api_user_playlist_ex`(回填「本次是否接口缓存命中」;`out_n` 与原接口一致必填、`out_from_cache` 可 NULL),旧接口为薄包装(评审 P2-2)。 |
| `core/ncm_login.c` | `ncm_login_status_check` / `ncm_login_refresh` 先过 HTTP 状态门,再按业务码:301/302→AUTH、`-460`→SVC、其它→HTTP(163-249 行)。`-460` 不再触发退扫码。 |
| `core/ncm_store.c/.h` | 会话密钥改为设备私有 `<data_dir>/.mkey` 派生(`key_from_mkey`/`session_key`,175-193 行);旧 MAC 派生密文仍可解(`cookie_decrypt_sel`,270 行)且 `load_session` 命中旧密钥时自动重写迁移(306-317 行)。 |
| `core/ncm_apicache.c/.h` | 新增命中计数 `ncm_apicache_hits()`:离线时读到 120s 旧缓存仍是 `NCM_OK`,不能据此判「已回在线」。 |
| `core/ncm_apicache.c/.h` | 盘缓存命中分支的两行合并为一行(行为不变,重构)。**不再提供全局命中计数**(评审 P2-2:跨 worker 差值会误判、自增在锁外有竞争;改为单请求出参,见下)。 |
| `app/ncm_app_internal.h` | `ncm_app_state_t` 增 `offline`;新增 `appstate_session_gate/is_offline/offline_set`。 |
| `app/logic/appstate.c` | 新增离线状态机(177-197 行);`appstate_ensure_uid` 仅 AUTH 清 cookie,网络类只切离线(231-256 行);init/logout 清零离线态。 |
| `app/logic/appstate.c` | 新增离线状态机(177-197 行);`appstate_ensure_uid` 仅 AUTH 清 cookie,网络类只切离线(231-256 行);init/logout 清零离线态;新增 `appstate_load_playlists_ex(from_cache)` 透传单请求缓存命中标志。 |
| `app/app.c` | 启动根页改由 `ncm_session_boot_decide` 决定并打日志(245-252 行);`need_relogin` 路径保持(仅 AUTH 会置位)。 |
| `app/ui/home.c` | worker 结果经 `appstate_session_gate`(缓存命中不算回在线,271-272 行);个人卡右侧「离线模式」红标 + 页脚提示 + 空态「离线模式/可播放已缓存」;离线时每 30s 静默重试一次(强制绕过接口缓存,301-312 行)。 |
| `app/ui/playlist.c` | 首屏结果同步会话/离线态(锁外,333 行);空态「离线模式」。 |
| `app/ui/home.c` | worker 结果经 `appstate_session_gate`;「本次是否接口缓存命中」用 `appstate_load_playlists_ex(&from_cache)` 的单请求出参判定(缓存命中→不判回在线),个人卡右侧「离线模式」红标 + 页脚提示 + 空态「离线模式/可播放已缓存」;离线时每 30s 静默重试一次(强制绕过接口缓存)。 |
| `app/ui/playlist.c` | 首屏**失败**时同步离线态(成功不判回在线:歌单详情可能命中接口缓存;锁外应用);空态「离线模式」。 |
| `app/ui/search.c` / `app/ui/player.c` / `app/ui/cache.c` | 离线提示文案:搜索需网络、播放页「离线模式:网络不可用,可播放已缓存」、缓存页页脚「· 离线模式(可播放)」。 |
| `build_core.sh` / `build_ncm.sh` | 核心源列表加 `core/ncm_session.c`。 |
| `build_ncm_sims.sh` | window/search/plpage/apicache/cookie_refresh 链入 `ncm_session.o`;新增 `session_gate_sim` 构建/运行。 |
@@ -3562,8 +3562,8 @@ MP3 ≤128k→标准,>128k→极高 HQ。列表显示**实际档**(如 hires
| 项 | 结果 |
|---|---|
| `bench/session_gate_sim`(新,真实 core + `ncm_http_request` 桩) | **83 PASS / 0 FAIL**:A 分类矩阵 11 例(-5/-4/-6/-17/裸 mbedtls/…)、B HTTP 状态 14 例、C 登录门 10 + 启动门 6 例、D `ncm_api` 15 例(401/403、5xx/429、业务 301/302、-460、非 JSON、传输码)、E `login_status` 8 + `login_refresh` 8 例、F 会话密钥 9 例(换 MAC 仍可解、旧密文可解并自动迁移) |
| `bench/http_truncate_sim` | **12 PASS**(原 8 截断用例零回归 + 新 4 个传输层归一用例) |
| `bench/session_gate_sim`(新,真实 core + `ncm_http_request` 桩) | **88 PASS / 0 FAIL**:A 分类矩阵 11 例(-5/-4/-6/-17/裸 mbedtls/…)、B HTTP 状态 14 例、C 登录门 10 + 启动门 6 例、D `ncm_api` 15 例(401/403、5xx/429、业务 301/302、-460、非 JSON、传输码)、D2 `from_cache` 出参 5 例(miss→0/命中→1 且 0 请求/失败→0/兼容入口/out_n 必填契约)、E `login_status` 8 + `login_refresh` 8 例、F 会话密钥 9 例(换 MAC 仍可解、旧密文可解并自动迁移) |
| `bench/http_truncate_sim` | **13 PASS**(原 8 截断用例零回归 + 4 个传输层归一用例 + 1 个畸形分块(NCM_ERR_JSON 不被改判 NET)用例) |
| `test/test_ncm_core_host`(全量核心库) | **139 PASS / 0 FAIL**(`TMPDIR` 指向可写目录;含 crypto/store/cache/tag/api/降级链全量) |
| `bench/window_sim` / `bench/search_sim` | PASS(window 需可写 TMPDIR:本机 `/tmp` 不可写时盘缓存用例环境性失败,换 `$TMPDIR` 后 6000/6000 落盘通过) |
| `bench/apicache_sim` / `bench/cookie_refresh_sim` | PASS(apicache 同样仅盘缓存文件路径受 `/tmp` 影响,换可写目录后通过) |
@@ -3576,8 +3576,12 @@ MP3 ≤128k→标准,>128k→极高 HQ。列表显示**实际档**(如 hires
只是服务端暂不可用;文案取「可播放已缓存」这一用户可执行动作,未再细分。
- **HTTP 403 仍按 AUTH**(标准语义:服务端理解请求但拒绝)。若真机遇到 CDN/WAF 的 403,可能造成
一次误登出;NCM 的风控通常以业务码 `-460` 表达(已改为 SVC)。如真机复现可再收窄为「仅 401」。
- **离线时接口缓存命中不算「回在线」**:`ncm_apicache_hits()` 只解决 UI/重试节奏误判;离线恢复
探测(home 每 30s)强制 `ncm_api_cache_refresh_path` 绕过缓存,确保成功即真在线。
- **离线时接口缓存命中不算「回在线」**:用 `ncm_api_user_playlist_ex` 的 `from_cache` 出参判定本次
结果是否真走网络(评审 P2-2:不用全局计数差值);离线恢复探测(home 每 30s)强制
`ncm_api_cache_refresh_path` 绕过缓存,确保成功即真在线。歌单页的缓存命中成功同样不判回在线
(该路径只在失败时更新离线态,在线恢复由 home 探测统一裁决)。
- **畸形分块/帧体归 `NCM_ERR_JSON`(数据错误)**,`http_ret_norm` 不再把它改判为网络不可达
(评审 P2-1:否则会误报离线)。
- **离线播放未缓存曲目**仍走原 `song_url` 重试(15s 超时 ×3),未改播放重试策略(不属本任务);
已缓存曲目离线可播(`song_local_file` 先查缓存)。
- **`app/app.c` 未能宿主编译**:其 SIGSEGV 定位处理器用 MIPS `ucontext.gregs[]`,aarch64 宿主