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

+12 -5
View File
@@ -259,8 +259,11 @@ int appstate_ensure_uid(void)
* 之间的发布点。持久化(playlists.bin)同单写者(home worker)在发布后读,无相互竞态。 */
static pthread_mutex_t g_pls_mu = PTHREAD_MUTEX_INITIALIZER;
int appstate_load_playlists(void)
/* from_cache(可 NULL):透传 ncm_api_user_playlist_ex 的「本次是否接口缓存命中」。
* 仅真正走网络成功时才应把 App 判为在线(M9 评审 P2-2:缓存命中不代表网络恢复)。 */
int appstate_load_playlists_ex(int *from_cache)
{
if (from_cache) *from_cache = 0;
if (!g_st.logged_in) return NCM_ERR_AUTH;
int rc = appstate_ensure_uid();
if (rc != NCM_OK) return rc;
@@ -273,8 +276,9 @@ int appstate_load_playlists(void)
* 单飞),免 96KB 栈。 */
static ncm_playlist_t s_pls[NCM_PLAYLIST_MAX];
int n = 0;
int rc2 = ncm_api_user_playlist(g_st.ctx.device_id, g_st.ctx.cookie, g_st.uid,
NCM_PLAYLIST_MAX, 0, s_pls, NCM_PLAYLIST_MAX, &n);
int rc2 = ncm_api_user_playlist_ex(g_st.ctx.device_id, g_st.ctx.cookie, g_st.uid,
NCM_PLAYLIST_MAX, 0, s_pls, NCM_PLAYLIST_MAX, &n,
from_cache);
if (rc2 == NCM_OK) {
int cnt = (n > NCM_PLAYLIST_MAX) ? NCM_PLAYLIST_MAX : n;
pthread_mutex_lock(&g_pls_mu);
@@ -283,11 +287,14 @@ int appstate_load_playlists(void)
pthread_mutex_unlock(&g_pls_mu);
appstate_playlists_cache_save(); /* §26:网络成功 → 覆写盘缓存(stale-while-revalidate) */
}
meow_log(MEOW_LOG_INFO, "home: playlists rc=%d count=%d uid=%s", rc2,
(rc2 == NCM_OK) ? n : g_st.playlist_count, g_st.uid);
meow_log(MEOW_LOG_INFO, "home: playlists rc=%d count=%d uid=%s%s", rc2,
(rc2 == NCM_OK) ? n : g_st.playlist_count, g_st.uid,
(from_cache && *from_cache) ? " (cache)" : "");
return rc2;
}
int appstate_load_playlists(void) { return appstate_load_playlists_ex(NULL); }
/* ---- 歌单列表磁盘缓存(§26,NCM_CACHE_DESIGN §2.2)----
* 快照 <data_dir>/playlists.bin:magic(4) + ver(2) + 时间戳(8) + count(4) + 数组(ncm_playlist_t)。
* 进首页先渲染缓存秒开(断网可离线浏览),后台 worker 拉网络成功后覆写;结构体改版靠 ver 判 miss。
+4
View File
@@ -447,6 +447,10 @@ int appstate_session_gate(int rc);
int appstate_is_offline(void); /* 1=离线模式(UI 画「离线模式」提示) */
void appstate_offline_set(int on); /* 直接置离线态(登出/初始化清零) */
int appstate_load_playlists(void); /* 拉我的歌单 */
int appstate_load_playlists_ex(int *from_cache);
/* 同上,额外回填「本次是否接口缓存命中」:
* 1=缓存(未走网络,不能判为已回在线);0=走网络/失败。
* from_cache 可为 NULL(等价 appstate_load_playlists)。 */
int appstate_load_playlist_songs(int idx); /* 兼容:载索引 + 首个窗口(内部走 ensure) */
int appstate_load_playlist_songs_page(int idx, int offset, int limit);
/* 兼容入口(滑动窗口语义,REPORT §17):
+5 -5
View File
@@ -255,21 +255,21 @@ static void *home_worker(void *arg)
{
(void)arg;
int rc;
uint64_t hits0 = ncm_apicache_hits();
int from_cache = 0;
if (!g_st.logged_in) rc = NCM_ERR_AUTH;
else {
/* §NEW MUSIC_U 临期续期:先于歌单网络请求(后续 eapi/weapi 用新 cookie)。
* AUTH=会话确失效(置回登录页标志,UI 线程消费);瞬断=保持旧 cookie 走缓存/旧会话。 */
int crc = account_cookie_maybe_refresh();
if (crc == NCM_ERR_AUTH) rc = NCM_ERR_AUTH;
else rc = appstate_load_playlists();
else rc = appstate_load_playlists_ex(&from_cache);
}
if (rc == NCM_OK)
account_vip_maybe_refresh(0); /* §20:超 1 天/到期后各补查一次(worker 线程,不卡 UI) */
/* M9:网络类失败→离线(保留登录态);成功→回在线。但「接口缓存命中」不算回在线——
* 离线时读到 120s 内的旧缓存同样返回 NCM_OK,不能据此清掉离线提示/重试节奏。 */
int from_cache = (rc == NCM_OK) && (ncm_apicache_hits() != hits0);
if (!from_cache) appstate_session_gate(rc);
* 离线时读到 120s 内的旧缓存同样返回 NCM_OK,不能据此清掉离线提示/重试节奏。
* 判定用本次请求的出参(评审 P2-2:不再用全局命中计数做前后差值)。 */
if (!(rc == NCM_OK && from_cache)) appstate_session_gate(rc);
pthread_mutex_lock(&g_home_mu);
g_load_err = (g_st.playlist_count > 0) ? 0 : rc; /* 有数据(缓存/已载)即不置错误态 */
g_home_loading = 0;
+3 -2
View File
@@ -314,7 +314,8 @@ static void *playlist_worker(void *arg)
} else {
g_load_err = rc; /* 索引失败/鉴权失败 → 整页空态(点按重试) */
}
/* M9:本次首屏结果同步会话/离线态(网络类→离线提示;成功→回在线;STALE 不采纳)。 */
/* M9:本次首屏失败(非 STALE)同步离线态;成功不在此判「回在线」——歌单详情可能命中
* 接口缓存,缓存命中不代表网络恢复(回在线由 home 的离线探测/真实成功判定)。 */
gate_rc = rc;
g_plloaded_idx = tidx;
g_plloaded_gen = tgen;
@@ -330,7 +331,7 @@ static void *playlist_worker(void *arg)
g_plloading = 0;
g_plgen++;
pthread_mutex_unlock(&g_plmu);
if (gate_rc != NCM_ERR_STALE) appstate_session_gate(gate_rc);
if (gate_rc != NCM_ERR_STALE && gate_rc != NCM_OK) appstate_session_gate(gate_rc);
return NULL;
}