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:
1 parent
b1d607d43f
commit
31879f3302
12 files changed
+134
-49
No files matched your search
+14
-10
@@ -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_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_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_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 行)。 |
|
| `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_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_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/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/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/home.c` | worker 结果经 `appstate_session_gate`;「本次是否接口缓存命中」用 `appstate_load_playlists_ex(&from_cache)` 的单请求出参判定(缓存命中→不判回在线),个人卡右侧「离线模式」红标 + 页脚提示 + 空态「离线模式/可播放已缓存」;离线时每 30s 静默重试一次(强制绕过接口缓存)。 |
|
||||||
| `app/ui/playlist.c` | 首屏结果同步会话/离线态(锁外,333 行);空态「离线模式」。 |
|
| `app/ui/playlist.c` | 首屏**失败**时同步离线态(成功不判回在线:歌单详情可能命中接口缓存;锁外应用);空态「离线模式」。 |
|
||||||
| `app/ui/search.c` / `app/ui/player.c` / `app/ui/cache.c` | 离线提示文案:搜索需网络、播放页「离线模式:网络不可用,可播放已缓存」、缓存页页脚「· 离线模式(可播放)」。 |
|
| `app/ui/search.c` / `app/ui/player.c` / `app/ui/cache.c` | 离线提示文案:搜索需网络、播放页「离线模式:网络不可用,可播放已缓存」、缓存页页脚「· 离线模式(可播放)」。 |
|
||||||
| `build_core.sh` / `build_ncm.sh` | 核心源列表加 `core/ncm_session.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` 构建/运行。 |
|
| `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/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` | **12 PASS**(原 8 截断用例零回归 + 新 4 个传输层归一用例) |
|
| `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/降级链全量) |
|
| `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/window_sim` / `bench/search_sim` | PASS(window 需可写 TMPDIR:本机 `/tmp` 不可写时盘缓存用例环境性失败,换 `$TMPDIR` 后 6000/6000 落盘通过) |
|
||||||
| `bench/apicache_sim` / `bench/cookie_refresh_sim` | PASS(apicache 同样仅盘缓存文件路径受 `/tmp` 影响,换可写目录后通过) |
|
| `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,可能造成
|
- **HTTP 403 仍按 AUTH**(标准语义:服务端理解请求但拒绝)。若真机遇到 CDN/WAF 的 403,可能造成
|
||||||
一次误登出;NCM 的风控通常以业务码 `-460` 表达(已改为 SVC)。如真机复现可再收窄为「仅 401」。
|
一次误登出;NCM 的风控通常以业务码 `-460` 表达(已改为 SVC)。如真机复现可再收窄为「仅 401」。
|
||||||
- **离线时接口缓存命中不算「回在线」**:`ncm_apicache_hits()` 只解决 UI/重试节奏误判;离线恢复
|
- **离线时接口缓存命中不算「回在线」**:用 `ncm_api_user_playlist_ex` 的 `from_cache` 出参判定本次
|
||||||
探测(home 每 30s)强制 `ncm_api_cache_refresh_path` 绕过缓存,确保成功即真在线。
|
结果是否真走网络(评审 P2-2:不用全局计数差值);离线恢复探测(home 每 30s)强制
|
||||||
|
`ncm_api_cache_refresh_path` 绕过缓存,确保成功即真在线。歌单页的缓存命中成功同样不判回在线
|
||||||
|
(该路径只在失败时更新离线态,在线恢复由 home 探测统一裁决)。
|
||||||
|
- **畸形分块/帧体归 `NCM_ERR_JSON`(数据错误)**,`http_ret_norm` 不再把它改判为网络不可达
|
||||||
|
(评审 P2-1:否则会误报离线)。
|
||||||
- **离线播放未缓存曲目**仍走原 `song_url` 重试(15s 超时 ×3),未改播放重试策略(不属本任务);
|
- **离线播放未缓存曲目**仍走原 `song_url` 重试(15s 超时 ×3),未改播放重试策略(不属本任务);
|
||||||
已缓存曲目离线可播(`song_local_file` 先查缓存)。
|
已缓存曲目离线可播(`song_local_file` 先查缓存)。
|
||||||
- **`app/app.c` 未能宿主编译**:其 SIGSEGV 定位处理器用 MIPS `ucontext.gregs[]`,aarch64 宿主
|
- **`app/app.c` 未能宿主编译**:其 SIGSEGV 定位处理器用 MIPS `ucontext.gregs[]`,aarch64 宿主
|
||||||
|
|||||||
@@ -259,8 +259,11 @@ int appstate_ensure_uid(void)
|
|||||||
* 之间的发布点。持久化(playlists.bin)同单写者(home worker)在发布后读,无相互竞态。 */
|
* 之间的发布点。持久化(playlists.bin)同单写者(home worker)在发布后读,无相互竞态。 */
|
||||||
static pthread_mutex_t g_pls_mu = PTHREAD_MUTEX_INITIALIZER;
|
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;
|
if (!g_st.logged_in) return NCM_ERR_AUTH;
|
||||||
int rc = appstate_ensure_uid();
|
int rc = appstate_ensure_uid();
|
||||||
if (rc != NCM_OK) return rc;
|
if (rc != NCM_OK) return rc;
|
||||||
@@ -273,8 +276,9 @@ int appstate_load_playlists(void)
|
|||||||
* 单飞),免 96KB 栈。 */
|
* 单飞),免 96KB 栈。 */
|
||||||
static ncm_playlist_t s_pls[NCM_PLAYLIST_MAX];
|
static ncm_playlist_t s_pls[NCM_PLAYLIST_MAX];
|
||||||
int n = 0;
|
int n = 0;
|
||||||
int rc2 = ncm_api_user_playlist(g_st.ctx.device_id, g_st.ctx.cookie, g_st.uid,
|
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);
|
NCM_PLAYLIST_MAX, 0, s_pls, NCM_PLAYLIST_MAX, &n,
|
||||||
|
from_cache);
|
||||||
if (rc2 == NCM_OK) {
|
if (rc2 == NCM_OK) {
|
||||||
int cnt = (n > NCM_PLAYLIST_MAX) ? NCM_PLAYLIST_MAX : n;
|
int cnt = (n > NCM_PLAYLIST_MAX) ? NCM_PLAYLIST_MAX : n;
|
||||||
pthread_mutex_lock(&g_pls_mu);
|
pthread_mutex_lock(&g_pls_mu);
|
||||||
@@ -283,11 +287,14 @@ int appstate_load_playlists(void)
|
|||||||
pthread_mutex_unlock(&g_pls_mu);
|
pthread_mutex_unlock(&g_pls_mu);
|
||||||
appstate_playlists_cache_save(); /* §26:网络成功 → 覆写盘缓存(stale-while-revalidate) */
|
appstate_playlists_cache_save(); /* §26:网络成功 → 覆写盘缓存(stale-while-revalidate) */
|
||||||
}
|
}
|
||||||
meow_log(MEOW_LOG_INFO, "home: playlists rc=%d count=%d uid=%s", rc2,
|
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);
|
(rc2 == NCM_OK) ? n : g_st.playlist_count, g_st.uid,
|
||||||
|
(from_cache && *from_cache) ? " (cache)" : "");
|
||||||
return rc2;
|
return rc2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int appstate_load_playlists(void) { return appstate_load_playlists_ex(NULL); }
|
||||||
|
|
||||||
/* ---- 歌单列表磁盘缓存(§26,NCM_CACHE_DESIGN §2.2)----
|
/* ---- 歌单列表磁盘缓存(§26,NCM_CACHE_DESIGN §2.2)----
|
||||||
* 快照 <data_dir>/playlists.bin:magic(4) + ver(2) + 时间戳(8) + count(4) + 数组(ncm_playlist_t)。
|
* 快照 <data_dir>/playlists.bin:magic(4) + ver(2) + 时间戳(8) + count(4) + 数组(ncm_playlist_t)。
|
||||||
* 进首页先渲染缓存秒开(断网可离线浏览),后台 worker 拉网络成功后覆写;结构体改版靠 ver 判 miss。
|
* 进首页先渲染缓存秒开(断网可离线浏览),后台 worker 拉网络成功后覆写;结构体改版靠 ver 判 miss。
|
||||||
|
|||||||
@@ -447,6 +447,10 @@ int appstate_session_gate(int rc);
|
|||||||
int appstate_is_offline(void); /* 1=离线模式(UI 画「离线模式」提示) */
|
int appstate_is_offline(void); /* 1=离线模式(UI 画「离线模式」提示) */
|
||||||
void appstate_offline_set(int on); /* 直接置离线态(登出/初始化清零) */
|
void appstate_offline_set(int on); /* 直接置离线态(登出/初始化清零) */
|
||||||
int appstate_load_playlists(void); /* 拉我的歌单 */
|
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(int idx); /* 兼容:载索引 + 首个窗口(内部走 ensure) */
|
||||||
int appstate_load_playlist_songs_page(int idx, int offset, int limit);
|
int appstate_load_playlist_songs_page(int idx, int offset, int limit);
|
||||||
/* 兼容入口(滑动窗口语义,REPORT §17):
|
/* 兼容入口(滑动窗口语义,REPORT §17):
|
||||||
|
|||||||
@@ -255,21 +255,21 @@ static void *home_worker(void *arg)
|
|||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
int rc;
|
int rc;
|
||||||
uint64_t hits0 = ncm_apicache_hits();
|
int from_cache = 0;
|
||||||
if (!g_st.logged_in) rc = NCM_ERR_AUTH;
|
if (!g_st.logged_in) rc = NCM_ERR_AUTH;
|
||||||
else {
|
else {
|
||||||
/* §NEW MUSIC_U 临期续期:先于歌单网络请求(后续 eapi/weapi 用新 cookie)。
|
/* §NEW MUSIC_U 临期续期:先于歌单网络请求(后续 eapi/weapi 用新 cookie)。
|
||||||
* AUTH=会话确失效(置回登录页标志,UI 线程消费);瞬断=保持旧 cookie 走缓存/旧会话。 */
|
* AUTH=会话确失效(置回登录页标志,UI 线程消费);瞬断=保持旧 cookie 走缓存/旧会话。 */
|
||||||
int crc = account_cookie_maybe_refresh();
|
int crc = account_cookie_maybe_refresh();
|
||||||
if (crc == NCM_ERR_AUTH) rc = NCM_ERR_AUTH;
|
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)
|
if (rc == NCM_OK)
|
||||||
account_vip_maybe_refresh(0); /* §20:超 1 天/到期后各补查一次(worker 线程,不卡 UI) */
|
account_vip_maybe_refresh(0); /* §20:超 1 天/到期后各补查一次(worker 线程,不卡 UI) */
|
||||||
/* M9:网络类失败→离线(保留登录态);成功→回在线。但「接口缓存命中」不算回在线——
|
/* M9:网络类失败→离线(保留登录态);成功→回在线。但「接口缓存命中」不算回在线——
|
||||||
* 离线时读到 120s 内的旧缓存同样返回 NCM_OK,不能据此清掉离线提示/重试节奏。 */
|
* 离线时读到 120s 内的旧缓存同样返回 NCM_OK,不能据此清掉离线提示/重试节奏。
|
||||||
int from_cache = (rc == NCM_OK) && (ncm_apicache_hits() != hits0);
|
* 判定用本次请求的出参(评审 P2-2:不再用全局命中计数做前后差值)。 */
|
||||||
if (!from_cache) appstate_session_gate(rc);
|
if (!(rc == NCM_OK && from_cache)) appstate_session_gate(rc);
|
||||||
pthread_mutex_lock(&g_home_mu);
|
pthread_mutex_lock(&g_home_mu);
|
||||||
g_load_err = (g_st.playlist_count > 0) ? 0 : rc; /* 有数据(缓存/已载)即不置错误态 */
|
g_load_err = (g_st.playlist_count > 0) ? 0 : rc; /* 有数据(缓存/已载)即不置错误态 */
|
||||||
g_home_loading = 0;
|
g_home_loading = 0;
|
||||||
|
|||||||
@@ -314,7 +314,8 @@ static void *playlist_worker(void *arg)
|
|||||||
} else {
|
} else {
|
||||||
g_load_err = rc; /* 索引失败/鉴权失败 → 整页空态(点按重试) */
|
g_load_err = rc; /* 索引失败/鉴权失败 → 整页空态(点按重试) */
|
||||||
}
|
}
|
||||||
/* M9:本次首屏结果同步会话/离线态(网络类→离线提示;成功→回在线;STALE 不采纳)。 */
|
/* M9:本次首屏失败(非 STALE)同步离线态;成功不在此判「回在线」——歌单详情可能命中
|
||||||
|
* 接口缓存,缓存命中不代表网络恢复(回在线由 home 的离线探测/真实成功判定)。 */
|
||||||
gate_rc = rc;
|
gate_rc = rc;
|
||||||
g_plloaded_idx = tidx;
|
g_plloaded_idx = tidx;
|
||||||
g_plloaded_gen = tgen;
|
g_plloaded_gen = tgen;
|
||||||
@@ -330,7 +331,7 @@ static void *playlist_worker(void *arg)
|
|||||||
g_plloading = 0;
|
g_plloading = 0;
|
||||||
g_plgen++;
|
g_plgen++;
|
||||||
pthread_mutex_unlock(&g_plmu);
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,15 @@ int ncm_api_user_playlist(const char *device_id, const char *cookie, const char
|
|||||||
return NCM_OK;
|
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;
|
static int g_fail = 0;
|
||||||
#define CHECK(cond, fmt, ...) do { \
|
#define CHECK(cond, fmt, ...) do { \
|
||||||
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
|
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
|
||||||
|
|||||||
@@ -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 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)
|
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; }
|
{ (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(离线,合成数据;计调用次数 + 可注入单批失败) ---- */
|
/* ---- 网络端点 stub(离线,合成数据;计调用次数 + 可注入单批失败) ---- */
|
||||||
#define SIM_TOTAL 400 /* 4 页:100 × 4(> NCM_SONG_WINDOW 256,回滚必滑窗) */
|
#define SIM_TOTAL 400 /* 4 页:100 × 4(> NCM_SONG_WINDOW 256,回滚必滑窗) */
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
* D. 真实 ncm_api(ncm_http_request 桩)HTTP 状态与业务码映射:
|
* D. 真实 ncm_api(ncm_http_request 桩)HTTP 状态与业务码映射:
|
||||||
* 401/403 与业务 301/302 → AUTH(退登录);5xx/429、业务 -460 → SVC(保登录态/离线);
|
* 401/403 与业务 301/302 → AUTH(退登录);5xx/429、业务 -460 → SVC(保登录态/离线);
|
||||||
* HTML 体 → JSON(other);其它业务码 → HTTP(other)。
|
* 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 只在服务端明确否定时)。
|
* E. 真实 ncm_login_status_check / ncm_login_refresh 同上(AUTH 只在服务端明确否定时)。
|
||||||
* F. 会话加密密钥稳定性(M9 根因回归):session.enc 保存后改变网卡 MAC(NCM_MAC 桩)仍能解出
|
* F. 会话加密密钥稳定性(M9 根因回归):session.enc 保存后改变网卡 MAC(NCM_MAC 桩)仍能解出
|
||||||
* cookie;旧版 MAC 派生密文可解且自动迁移到 .mkey 密钥。
|
* 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 校验 / 续期
|
* E. 真实 ncm_login:cookie 校验 / 续期
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
@@ -414,6 +462,7 @@ int main(void)
|
|||||||
test_http_class();
|
test_http_class();
|
||||||
test_gate();
|
test_gate();
|
||||||
test_api_map();
|
test_api_map();
|
||||||
|
test_api_from_cache();
|
||||||
test_login_map();
|
test_login_map();
|
||||||
test_session_key_stability();
|
test_session_key_stability();
|
||||||
printf("\n== SESSION GATE SIM: %s (http stubs called %d times) ==\n",
|
printf("\n== SESSION GATE SIM: %s (http stubs called %d times) ==\n",
|
||||||
|
|||||||
@@ -174,9 +174,14 @@ static void api_cache_key(const char *path, const char *json, char *out, size_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 带缓存的 POST:先查内存/盘缓存(TTL 内命中则直接解析回填);miss/过期/手动刷新 → 真网络,
|
/* 带缓存的 POST:先查内存/盘缓存(TTL 内命中则直接解析回填);miss/过期/手动刷新 → 真网络,
|
||||||
* 成功且非手动刷新 → 写缓存。 */
|
* 成功且非手动刷新 → 写缓存。
|
||||||
|
* from_cache(可 NULL):1=本次结果来自接口缓存(**没有**真正走网络),0=走了网络/失败。
|
||||||
|
* 离线判定用它——缓存命中返回 NCM_OK 不代表网络已恢复(M9 评审 P2-2:不能用全局计数
|
||||||
|
* 前后差值判定,其它 worker 的命中会污染,且计数自增存在跨线程竞争)。 */
|
||||||
static int api_post_cached(const char *device_id, const char *cookie, const char *path,
|
static int api_post_cached(const char *device_id, const char *cookie, const char *path,
|
||||||
const char *json, int kind, size_t resp_cap, api_json_t *c) {
|
const char *json, int kind, size_t resp_cap, api_json_t *c,
|
||||||
|
int *from_cache) {
|
||||||
|
if (from_cache) *from_cache = 0;
|
||||||
int refresh = api_refresh_match(path);
|
int refresh = api_refresh_match(path);
|
||||||
char key[NCM_APICACHE_KEY_MAX];
|
char key[NCM_APICACHE_KEY_MAX];
|
||||||
api_cache_key(path, json, key, sizeof key);
|
api_cache_key(path, json, key, sizeof key);
|
||||||
@@ -186,6 +191,7 @@ static int api_post_cached(const char *device_id, const char *cookie, const char
|
|||||||
if (buf) {
|
if (buf) {
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
if (ncm_apicache_get(key, buf, resp_cap, &len) == NCM_OK) {
|
if (ncm_apicache_get(key, buf, resp_cap, &len) == NCM_OK) {
|
||||||
|
if (from_cache) *from_cache = 1;
|
||||||
c->rbuf = buf;
|
c->rbuf = buf;
|
||||||
c->resp.body = buf; /* 缓存的 body 从缓冲头开始 */
|
c->resp.body = buf; /* 缓存的 body 从缓冲头开始 */
|
||||||
c->resp.body_len = len;
|
c->resp.body_len = len;
|
||||||
@@ -300,10 +306,13 @@ static int parse_songs_array(const ncm_json_t *j, int arr, ncm_song_t *out, int
|
|||||||
return NCM_OK;
|
return NCM_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ncm_api_user_playlist(const char *device_id, const char *cookie, const char *uid,
|
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 limit, int offset, ncm_playlist_t *out, int max, int *out_n,
|
||||||
|
int *out_from_cache) {
|
||||||
|
/* 与原接口契约一致:out_n 必填(成功路径无条件回填);out_from_cache 可 NULL。 */
|
||||||
if (!out_n) return NCM_ERR_ARG;
|
if (!out_n) return NCM_ERR_ARG;
|
||||||
*out_n = 0;
|
*out_n = 0;
|
||||||
|
if (out_from_cache) *out_from_cache = 0;
|
||||||
if (!uid) return NCM_ERR_ARG;
|
if (!uid) return NCM_ERR_ARG;
|
||||||
char json[256];
|
char json[256];
|
||||||
snprintf(json, sizeof json, "{\"uid\":\"%s\",\"limit\":%d,\"offset\":%d,\"includeVideo\":true}",
|
snprintf(json, sizeof json, "{\"uid\":\"%s\",\"limit\":%d,\"offset\":%d,\"includeVideo\":true}",
|
||||||
@@ -314,7 +323,7 @@ int ncm_api_user_playlist(const char *device_id, const char *cookie, const char
|
|||||||
* (日志 home: playlists rc=-11)。该响应不含 tracks(每首 tracks:null),1MB 足够,
|
* (日志 home: playlists rc=-11)。该响应不含 tracks(每首 tracks:null),1MB 足够,
|
||||||
* 用 NCM_RESP_MAX(与 vip/info 同口径的堆瞬态,解析后即释,非 bss)。 */
|
* 用 NCM_RESP_MAX(与 vip/info 同口径的堆瞬态,解析后即释,非 bss)。 */
|
||||||
int rc = api_post_cached(device_id, cookie, "/api/user/playlist", json, NCM_CRYPT_WEAPI,
|
int rc = api_post_cached(device_id, cookie, "/api/user/playlist", json, NCM_CRYPT_WEAPI,
|
||||||
NCM_RESP_MAX, &c);
|
NCM_RESP_MAX, &c, out_from_cache);
|
||||||
if (rc != NCM_OK) return rc;
|
if (rc != NCM_OK) return rc;
|
||||||
|
|
||||||
int arr = ncm_json_path(&c.j, "playlist");
|
int arr = ncm_json_path(&c.j, "playlist");
|
||||||
@@ -337,6 +346,12 @@ int ncm_api_user_playlist(const char *device_id, const char *cookie, const char
|
|||||||
return NCM_OK;
|
return NCM_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 兼容入口(不关心是否缓存命中):等价 ncm_api_user_playlist_ex(..., NULL)。 */
|
||||||
|
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) {
|
||||||
|
return ncm_api_user_playlist_ex(device_id, cookie, uid, limit, offset, out, max, out_n, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
int ncm_api_playlist_detail(const char *device_id, const char *cookie, const char *playlist_id,
|
int ncm_api_playlist_detail(const char *device_id, const char *cookie, const char *playlist_id,
|
||||||
char *name, size_t name_sz, char *cover, size_t cover_sz,
|
char *name, size_t name_sz, char *cover, size_t cover_sz,
|
||||||
int64_t *ids, int id_cap, int *out_id_n) {
|
int64_t *ids, int id_cap, int *out_id_n) {
|
||||||
@@ -350,7 +365,7 @@ int ncm_api_playlist_detail(const char *device_id, const char *cookie, const cha
|
|||||||
|
|
||||||
api_json_t c; memset(&c, 0, sizeof c);
|
api_json_t c; memset(&c, 0, sizeof c);
|
||||||
int rc = api_post_cached(device_id, cookie, "/api/v6/playlist/detail", json, NCM_CRYPT_EAPI,
|
int rc = api_post_cached(device_id, cookie, "/api/v6/playlist/detail", json, NCM_CRYPT_EAPI,
|
||||||
2 * 1024 * 1024, &c);
|
2 * 1024 * 1024, &c, NULL);
|
||||||
if (rc != NCM_OK) return rc;
|
if (rc != NCM_OK) return rc;
|
||||||
|
|
||||||
int pl = ncm_json_path(&c.j, "playlist");
|
int pl = ncm_json_path(&c.j, "playlist");
|
||||||
@@ -394,7 +409,7 @@ int ncm_api_song_detail(const char *device_id, const char *cookie, const int64_t
|
|||||||
|
|
||||||
api_json_t c; memset(&c, 0, sizeof c);
|
api_json_t c; memset(&c, 0, sizeof c);
|
||||||
int rc = api_post_cached(device_id, cookie, "/api/v3/song/detail", json, NCM_CRYPT_EAPI,
|
int rc = api_post_cached(device_id, cookie, "/api/v3/song/detail", json, NCM_CRYPT_EAPI,
|
||||||
RESP_MED, &c);
|
RESP_MED, &c, NULL);
|
||||||
if (rc != NCM_OK) return rc;
|
if (rc != NCM_OK) return rc;
|
||||||
|
|
||||||
int arr = ncm_json_path(&c.j, "songs");
|
int arr = ncm_json_path(&c.j, "songs");
|
||||||
@@ -677,7 +692,7 @@ static int cloudsearch_post(const char *device_id, const char *cookie, const cha
|
|||||||
snprintf(json, sizeof json, "{\"s\":\"%s\",\"type\":%d,\"offset\":%d,\"limit\":%d,\"total\":true}",
|
snprintf(json, sizeof json, "{\"s\":\"%s\",\"type\":%d,\"offset\":%d,\"limit\":%d,\"total\":true}",
|
||||||
esc, type, offset > 0 ? offset : 0, limit > 0 ? limit : 30);
|
esc, type, offset > 0 ? offset : 0, limit > 0 ? limit : 30);
|
||||||
return api_post_cached(device_id, cookie, "/api/cloudsearch/pc", json, NCM_CRYPT_WEAPI,
|
return api_post_cached(device_id, cookie, "/api/cloudsearch/pc", json, NCM_CRYPT_WEAPI,
|
||||||
RESP_MED, c);
|
RESP_MED, c, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 解析一处「歌单对象数组」(cloudsearch type=1000)。id 可为数字/字符串(均文本拷贝)。 */
|
/* 解析一处「歌单对象数组」(cloudsearch type=1000)。id 可为数字/字符串(均文本拷贝)。 */
|
||||||
|
|||||||
@@ -89,7 +89,13 @@ typedef struct ncm_play_url {
|
|||||||
} ncm_play_url_t;
|
} ncm_play_url_t;
|
||||||
|
|
||||||
/* 用户歌单列表(weapi /user/playlist)。uid 必填;limit/offset 翻页。
|
/* 用户歌单列表(weapi /user/playlist)。uid 必填;limit/offset 翻页。
|
||||||
* 返回 NCM_OK;songs/out_n 为列表。 */
|
* 返回 NCM_OK;songs/out_n 为列表。
|
||||||
|
* _ex 变体额外回填 *out_from_cache(可 NULL):1=本次结果来自接口缓存(未走网络),
|
||||||
|
* 0=走了网络或失败。离线判定用——缓存命中返回 NCM_OK 不代表网络已恢复(M9)。
|
||||||
|
* 契约与原接口一致:out_n 必填(NULL → NCM_ERR_ARG)。 */
|
||||||
|
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);
|
||||||
int ncm_api_user_playlist(const char *device_id, const char *cookie, const char *uid,
|
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);
|
int limit, int offset, ncm_playlist_t *out, int max, int *out_n);
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ static char g_disk_dir[DISK_DIR_MAX];
|
|||||||
static int g_disk_on = 0;
|
static int g_disk_on = 0;
|
||||||
static int g_ttl_s = NCM_APICACHE_TTL_S; /* 默认 120s */
|
static int g_ttl_s = NCM_APICACHE_TTL_S; /* 默认 120s */
|
||||||
static int64_t g_last_purge_ms = 0; /* 盘过期清理节流(单调钟)*/
|
static int64_t g_last_purge_ms = 0; /* 盘过期清理节流(单调钟)*/
|
||||||
static uint64_t g_hits = 0; /* 命中计数(M9 离线判定用,仅单调递增) */
|
|
||||||
|
|
||||||
static int64_t clock_ms(clockid_t id) {
|
static int64_t clock_ms(clockid_t id) {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
@@ -214,7 +213,6 @@ int ncm_apicache_get(const char *key, char *buf, size_t buf_sz, size_t *len)
|
|||||||
memcpy(buf, g_e[idx].body, n);
|
memcpy(buf, g_e[idx].body, n);
|
||||||
g_e[idx].last_ms = now; /* LRU touch */
|
g_e[idx].last_ms = now; /* LRU touch */
|
||||||
if (len) *len = n;
|
if (len) *len = n;
|
||||||
g_hits++;
|
|
||||||
pthread_mutex_unlock(&g_mu);
|
pthread_mutex_unlock(&g_mu);
|
||||||
return NCM_OK;
|
return NCM_OK;
|
||||||
}
|
}
|
||||||
@@ -224,23 +222,13 @@ int ncm_apicache_get(const char *key, char *buf, size_t buf_sz, size_t *len)
|
|||||||
/* 内存 miss → 盘缓存兜底(命中回填内存)。 */
|
/* 内存 miss → 盘缓存兜底(命中回填内存)。 */
|
||||||
if (g_disk_on) {
|
if (g_disk_on) {
|
||||||
int rc = disk_get(key, buf, buf_sz, len);
|
int rc = disk_get(key, buf, buf_sz, len);
|
||||||
if (rc == NCM_OK) {
|
/* 合并为一行,行为不变(重构):len 为空时不回填内存。 */
|
||||||
if (len) ncm_apicache_put(key, buf, *len);
|
if (rc == NCM_OK && len) ncm_apicache_put(key, buf, *len);
|
||||||
g_hits++;
|
|
||||||
}
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
return NCM_ERR_NOENT;
|
return NCM_ERR_NOENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ncm_apicache_hits(void)
|
|
||||||
{
|
|
||||||
pthread_mutex_lock(&g_mu);
|
|
||||||
uint64_t v = g_hits;
|
|
||||||
pthread_mutex_unlock(&g_mu);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ncm_apicache_put(const char *key, const char *body, size_t len)
|
void ncm_apicache_put(const char *key, const char *body, size_t len)
|
||||||
{
|
{
|
||||||
if (!key || !body || len == 0) return;
|
if (!key || !body || len == 0) return;
|
||||||
|
|||||||
@@ -55,10 +55,6 @@ void ncm_apicache_ttl_set(int seconds);
|
|||||||
/* 当前内存条目数(诊断/测试)。 */
|
/* 当前内存条目数(诊断/测试)。 */
|
||||||
int ncm_apicache_mem_count(void);
|
int ncm_apicache_mem_count(void);
|
||||||
|
|
||||||
/* 命中计数(单调递增;M9 离线判定用:App 据此区分「真·网络成功」与「离线时读到旧缓存」,
|
|
||||||
* 避免离线提示/账号态被缓存命中误判为「已回在线」)。 */
|
|
||||||
uint64_t ncm_apicache_hits(void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in new issue
Block a user