fix(ncm): 离线重试改单调钟节奏闸,断网不再每个 tick 重试(M10)

根因(真机日志 19000100_000000_7534.log):meow_ui_time_ms() 在设备上返回坏值——
低 32 位是「开机秒数×1000」(无毫秒),高 32 位每次调用随机(REPORT §42)。
旧守卫 `!g_home_thr_on && (!g_offline_retry_ms || now - g_offline_retry_ms >= 30000)`
因此恒真:worker 一收工,下一个 tick 立刻再重试;真机 74s 内 10 次
(间隔 5-7s 只是失败请求本身的连接超时)。第二条链是 else 分支清零重试时刻,
online/offline 一抖动就再试。

修复:
- 新增 app/logic/retry_ctl.{h,c}:节奏闸只做差值,时钟由调用方给可信单调钟
  (home.c 改传 core 的 ncm_monotonic_ms());回退不下溢(重定基准、不重试);
  busy 单飞;在线/离线抖动不重置基准;首次进离线立即探测一次,之后严格 ≥30s。
- home_tick 不再使用入参 now(真机坏值无效化);新增 home_retry_reset()。
- 静默重试不再走完整 home_reload:新增 home_reload_ex(silent) 不预读盘
  (旧实现每轮在 UI 线程同步读 playlists.bin)、home_apply_ex(keep_scroll)
  保留滚动位置、worker 记 g_home_changed 决定是否需要重绘——旧实现每轮
  home_apply() 复位滚动+整帧重绘,是「歌单列表滑动特别卡」的直接来源。
- worker 线程 pthread_detach(旧代码每轮泄漏线程栈)。
This commit is contained in:
AskaEth committed 2026-11-05 13:00:24 +08:00
1 parent aa225a1973
commit 35a636ef42
4 files changed
+149 -27

No files matched your search

+76 -27
View File
@@ -12,8 +12,14 @@
*
* 左缘返回 = 退出 App(app.c)。歌单加载后台 worker(home_worker,保留 REPORT §10.11
* 的异步结构,不回退成同步——P1 启动卡顿已修,勿回退)。
*
* M10:离线自动重试不再用「裸 u64 差值 + 状态抖动清零」的 30s 守卫(真机 meow_ui_time_ms()
* 高 32 位是垃圾 → 守卫恒真 → 断网重试风暴 / META 洪泛 / 列表被反复拽回顶部)。改由
* logic/retry_ctl.c 统一管节奏(core 单调钟、单飞、时钟回退安全、抖动不重置),
* 静默重试不预读盘/不复位滚动/无可见变化不重绘。详见 REPORT §42。
*/
#include "../ncm_app_internal.h"
#include "../logic/retry_ctl.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
@@ -36,6 +42,13 @@ static int g_home_thr_on;
static volatile int g_home_gen;
static int g_home_acked;
static volatile int g_home_loading;
static int g_home_silent; /* 1=本次加载是后台静默刷新(M10 离线重试) */
static int g_home_changed; /* 1=本次加载改了可见数据/离线态(决定是否重绘) */
/* M10:离线自动重试节奏闸。时钟用 core 单调钟(ncm_monotonic_ms),不用 SDK meow_ui_time_ms
* ——真机后者高 32 位是垃圾,任何 `now - last >= N` 都会变成「恒真」(REPORT §42)。 */
#define HOME_OFFLINE_RETRY_MS 30000
static ncm_retry_ctl g_retry;
static const char *tab_label(int tab) { return tab ? "我收藏的" : "我创建的"; }
@@ -234,12 +247,17 @@ static void home_draw_footer(void *ctx)
ncm_draw_footer(r_cont, foot);
}
/* 把当前 tab 子集刷进列表 + 重绘(UI 线程)。 */
static void home_apply(void)
/* 把当前 tab 子集刷进列表 + 重绘(UI 线程)。
* keep_scroll=1:后台静默刷新用——保留用户当前滚动位置(数量变化由 SDK 钳位);
* 0=复位到顶部(进页/切 tab/手动刷新,与原行为一致)。
* M10:离线重试不再每轮把列表拽回顶部(旧代码每轮调本函数复位滚动 + 整帧重绘,
* 重试风暴时表现为「歌单列表滑动特别卡」)。 */
static void home_apply_ex(int keep_scroll)
{
if (g_list) {
int keep = keep_scroll ? meow_listview_scroll(g_list) : 0;
meow_listview_set_count(g_list, tab_count(g_tab));
meow_listview_set_scroll(g_list, 0);
meow_listview_set_scroll(g_list, keep);
meow_listview_set_sel(g_list, -1);
}
if (g_tabs) meow_tabbar_set_selected(g_tabs, g_tab);
@@ -248,6 +266,8 @@ static void home_apply(void)
meow_ui_paint_full();
}
static void home_apply(void) { home_apply_ex(0); }
/* 后台 worker:唯一走网络的地方(ensure_uid + user_playlist)。
* §26 stale-while-revalidate:网络成功 → appstate_load_playlists 覆写盘缓存 + 更新内存;
* 失败但有缓存在展示 → 保持缓存列表(g_load_err=0),后台静默不打断(断网可离线浏览)。 */
@@ -256,6 +276,8 @@ static void *home_worker(void *arg)
(void)arg;
int rc;
int from_cache = 0;
int off_before = appstate_is_offline();
int cnt_before = g_st.playlist_count;
if (!g_st.logged_in) rc = NCM_ERR_AUTH;
else {
/* §NEW MUSIC_U 临期续期:先于歌单网络请求(后续 eapi/weapi 用新 cookie)。
@@ -273,44 +295,65 @@ static void *home_worker(void *arg)
pthread_mutex_lock(&g_home_mu);
g_load_err = (g_st.playlist_count > 0) ? 0 : rc; /* 有数据(缓存/已载)即不置错误态 */
g_home_loading = 0;
/* M10:本次加载是否产生「看得见的变化」——离线态翻转 / 歌单数量变化 / 仍处空态
* (空态文案随 load_err、离线提示变化)。无变化则静默重试不重绘,不打断滑动。 */
g_home_changed = (appstate_is_offline() != off_before) ||
(g_st.playlist_count != cnt_before) ||
(g_st.playlist_count == 0);
g_home_gen++;
pthread_mutex_unlock(&g_home_mu);
return NULL;
}
void home_reload(void)
/* silent=0:进页/手动刷新(读盘缓存秒开 + 复位滚动);1:后台静默刷新(不动界面)。 */
static void home_reload_ex(int silent)
{
if (g_host_mock) { g_load_err = 0; home_apply(); return; }
if (g_home_thr_on) return;
/* §26:进首页先用磁盘缓存秒渲染(若有),后台 worker 静默刷新;无缓存才显示 LOADING。 */
int64_t ts = 0;
int had_cache = (appstate_playlists_cache_load(&ts) == NCM_OK && g_st.playlist_count > 0);
g_load_err = 0;
if (!had_cache) {
g_st.playlist_count = 0; /* 无缓存 → LOADING 空态 */
g_home_loading = 1;
if (g_host_mock) { if (!silent) { g_load_err = 0; home_apply(); } return; }
if (g_home_thr_on) return; /* 单飞:同一时刻只一个 worker */
if (!silent) {
/* §26:进首页先用磁盘缓存秒渲染(若有),后台 worker 静默刷新;无缓存才显示 LOADING。 */
int64_t ts = 0;
int had_cache = (appstate_playlists_cache_load(&ts) == NCM_OK && g_st.playlist_count > 0);
g_load_err = 0;
if (!had_cache) {
g_st.playlist_count = 0; /* 无缓存 → LOADING 空态 */
g_home_loading = 1;
}
}
/* M10 静默刷新:界面已有数据 → 不预读盘(旧实现每轮都在 UI 线程同步读 playlists.bin)、
* 不复位滚动、不重绘;等 worker 完成后由 home_tick 按 g_home_changed 决定是否重绘。 */
g_home_silent = silent;
g_home_thr_on = 1;
if (ncm_thread_create(&g_home_thr, home_worker, NULL) != 0) {
g_home_thr_on = 0; g_home_loading = 0; g_load_err = NCM_ERR_IO;
return;
}
home_apply();
pthread_detach(g_home_thr); /* 只靠 gen 代次收工、不 join:线程栈即刻回收(旧代码每轮泄漏) */
if (!silent) home_apply();
}
void home_reload(void) { home_reload_ex(0); }
/* M10:清离线重试节奏(页创建/登出/宿主测试用)。 */
void home_retry_reset(void) { ncm_retry_ctl_init(&g_retry); }
void home_tick(uint64_t now)
{
/* M9 离线恢复:离线时每 30s 静默重试一次(home worker 单飞;成功后 appstate_session_gate
* 自动回在线并重绘)。仅在 home 可见时轮询,不轰炸服务端(风控纪律)。 */
static uint64_t g_offline_retry_ms;
if (appstate_is_offline() && !g_host_mock) {
if (!g_home_thr_on && (!g_offline_retry_ms || now - g_offline_retry_ms >= 30000)) {
g_offline_retry_ms = now;
meow_log(MEOW_LOG_INFO, "home: offline retry");
ncm_api_cache_refresh_path("/api/user/playlist"); /* 离线恢复探测必须真打网络 */
home_reload();
}
} else {
g_offline_retry_ms = 0;
/* M10 离线恢复:断网下严格 ≥30s 静默重试一次(home worker 单飞;成功后 appstate_session_gate
* 自动回在线并重绘)。仅在 home 可见时轮询,不轰炸服务端(风控纪律)。
*
* 节奏判定不使用入参 now:真机 meow_ui_time_ms() 的高 32 位是垃圾(REPORT §42),
* `now - last >= 30000` 会恒真 → 旧实现每个 tick 都重试(真机 74s 内 10 次,CPU 48%、
* load 3.65、整机卡死)。这里统一用 core 单调钟 + retry_ctl 闸:
* - 单飞(worker 未收工不重试)、
* - 时钟回退不下溢(回退时重定基准,不立即重试)、
* - 在线/离线抖动不重置基准(旧 else 分支清零 g_offline_retry_ms,一抖就重试)。 */
(void)now;
if (appstate_is_offline() && !g_host_mock &&
ncm_retry_ctl_poll(&g_retry, 1, g_home_thr_on, ncm_monotonic_ms(), HOME_OFFLINE_RETRY_MS)) {
meow_log(MEOW_LOG_INFO, "home: offline retry");
ncm_api_cache_refresh_path("/api/user/playlist"); /* 离线恢复探测必须真打网络 */
home_reload_ex(1);
}
if (!g_home_thr_on) return;
@@ -318,7 +361,12 @@ void home_tick(uint64_t now)
g_home_acked = g_home_gen;
g_home_thr_on = 0;
g_home_loading = 0;
home_apply();
int changed, silent;
pthread_mutex_lock(&g_home_mu);
changed = g_home_changed;
silent = g_home_silent;
pthread_mutex_unlock(&g_home_mu);
if (!silent || changed) home_apply_ex(silent ? 1 : 0); /* 静默刷新保留滚动位置 */
}
int home_is_active(void) { return g_hc.active; }
@@ -403,6 +451,7 @@ static void home_page_create(meow_view *v, void *ctx)
{
(void)v;
((home_ctrl *)ctx)->view = v;
home_retry_reset(); /* M10:离线重试节奏闸(首次进离线立即探测一次) */
r_cont = ncm_content_rect();
ncm_rect_t pc = NR(16, r_cont.y + 8, r_cont.w - 32, 72);
r_tabs = NR(16, pc.y + pc.h + 8, r_cont.w - 32, 48);