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

@@ -137,6 +137,15 @@ int ncm_api_user_playlist(const char *device_id, const char *cookie, const char
return NCM_OK;
}
/* M9 出参变体(appstate_load_playlists_ex 引用;本 sim 恒「走网络」) */
int ncm_api_user_playlist_ex(const char *device_id, const char *cookie, const char *uid,
int limit, int offset, ncm_playlist_t *out, int max, int *out_n,
int *out_from_cache)
{
if (out_from_cache) *out_from_cache = 0;
return ncm_api_user_playlist(device_id, cookie, uid, limit, offset, out, max, out_n);
}
static int g_fail = 0;
#define CHECK(cond, fmt, ...) do { \
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
+6
View File
@@ -69,6 +69,12 @@ int ncm_ctx_clear_cookie(ncm_ctx_t *ctx) { (void)ctx; return NCM_OK; }
int ncm_api_user_playlist(const char *device_id, const char *cookie, const char *uid,
int limit, int offset, ncm_playlist_t *out, int max, int *out_n)
{ (void)device_id;(void)cookie;(void)uid;(void)limit;(void)offset;(void)out;(void)max;(void)out_n; return NCM_ERR_NET; }
/* M9 出参变体(appstate_load_playlists_ex 引用;本 sim 不到达) */
int ncm_api_user_playlist_ex(const char *device_id, const char *cookie, const char *uid,
int limit, int offset, ncm_playlist_t *out, int max, int *out_n,
int *out_from_cache)
{ if (out_from_cache) *out_from_cache = 0;
return ncm_api_user_playlist(device_id, cookie, uid, limit, offset, out, max, out_n); }
/* ---- 网络端点 stub(离线,合成数据;计调用次数 + 可注入单批失败) ---- */
#define SIM_TOTAL 400 /* 4 页:100 × 4(> NCM_SONG_WINDOW 256,回滚必滑窗) */
+49
View File
@@ -9,6 +9,9 @@
* D. 真实 ncm_api(ncm_http_request 桩)HTTP 状态与业务码映射:
* 401/403 与业务 301/302 → AUTH(退登录);5xx/429、业务 -460 → SVC(保登录态/离线);
* HTML 体 → JSON(other);其它业务码 → HTTP(other)。
* D2. `ncm_api_user_playlist_ex` 的 from_cache 出参:miss→0(真网络 +1 次)、命中→1(0 请求)、
* 失败→0;兼容入口不崩;out_n 必填契约(NULL→NCM_ERR_ARG)(离线判定不得用全局计数,
* 评审 P2-2/P2-1)。
* E. 真实 ncm_login_status_check / ncm_login_refresh 同上(AUTH 只在服务端明确否定时)。
* F. 会话加密密钥稳定性(M9 根因回归):session.enc 保存后改变网卡 MAC(NCM_MAC 桩)仍能解出
* cookie;旧版 MAC 派生密文可解且自动迁移到 .mkey 密钥。
@@ -259,6 +262,51 @@ static void test_api_map(void)
}
}
/* ============================================================
* D2. user_playlist_ex:本次结果是否来自接口缓存(评审 P2-2)
* ============================================================ */
static void test_api_from_cache(void)
{
printf("== D2. ncm_api_user_playlist_ex 缓存命中出参(离线判定用)==\n");
ncm_apicache_init(NULL);
ncm_apicache_clear();
const char *body = "{\"code\":200,\"playlist\":[{\"id\":1,\"name\":\"a\",\"trackCount\":1}]}";
ncm_playlist_t pls[4];
int n = 0, fc = -1, rc;
stub(200, body, NULL);
int calls0 = g_http_calls;
rc = ncm_api_user_playlist_ex("dev", "MUSIC_U=x", "1", 30, 0, pls, 4, &n, &fc);
CHECK(rc == NCM_OK && fc == 0 && g_http_calls == calls0 + 1 && n == 1,
"首次(缓存 miss):rc=0 n=%d from_cache=%d,真网络 +1 次(calls %d→%d)",
n, fc, calls0, g_http_calls);
fc = -1;
calls0 = g_http_calls;
rc = ncm_api_user_playlist_ex("dev", "MUSIC_U=x", "1", 30, 0, pls, 4, &n, &fc);
CHECK(rc == NCM_OK && fc == 1 && g_http_calls == calls0 && n == 1,
"二次同参(缓存命中):rc=0 from_cache=%d,未发网络请求(calls=%d)",
fc, g_http_calls);
/* 失败不得把 from_cache 留成 1(App 会据此判离线,方向必须正确) */
ncm_apicache_clear();
stub_err(NCM_ERR_NET);
fc = -1;
rc = ncm_api_user_playlist_ex("dev", "MUSIC_U=x", "1", 30, 0, pls, 4, &n, &fc);
CHECK(rc == NCM_ERR_NET && fc == 0, "网络失败:rc=%d from_cache=%d(期望 -4/0)", rc, fc);
/* 兼容入口仍可用(out_from_cache=NULL 不崩) */
ncm_apicache_clear();
stub(200, body, NULL);
rc = ncm_api_user_playlist("dev", "MUSIC_U=x", "1", 30, 0, pls, 4, &n);
CHECK(rc == NCM_OK && n == 1, "兼容入口 ncm_api_user_playlist 正常(rc=%d n=%d)", rc, n);
/* 契约:out_n 必填(与原接口一致);out_from_cache 可 NULL(评审 P2-1) */
rc = ncm_api_user_playlist_ex("dev", "MUSIC_U=x", "1", 30, 0, pls, 4, NULL, &fc);
CHECK(rc == NCM_ERR_ARG, "out_n=NULL → NCM_ERR_ARG(契约与原接口一致)rc=%d", rc);
ncm_apicache_clear();
}
/* ============================================================
* E. 真实 ncm_login:cookie 校验 / 续期
* ============================================================ */
@@ -414,6 +462,7 @@ int main(void)
test_http_class();
test_gate();
test_api_map();
test_api_from_cache();
test_login_map();
test_session_key_stability();
printf("\n== SESSION GATE SIM: %s (http stubs called %d times) ==\n",