280 歌单链路三修:开A→开B 竞态防串(5194 串数据) + 92/200 批边界 Load Failed + 歌单列表盘缓存 + 孤儿 .part 回收
ncm_app(REPORT §26, ncm 158ec367 未部署留主会话): - 打开时快照 plid+代次:appstate_playlist_begin/open_gen/open_idx/reset + appstate_load_playlist_songs_ctx(快照 pl 不读 g_st.playlists[],detail 结果落 staging 锁内原子发布,代次/目标不匹配判 NCM_ERR_STALE 不写全局);playlist_tick 过期丢弃 + appstate_playlist_reset 清窗口防串。修 5194「我喜欢的音乐」3 首/串数据。 - appstate_song_ensure_range fill-and-continue:bn<m 部分返回/单批网络失败非致命 (记 NCM_ERR_NOTFOUND/r2),已物化保留+占位行;worker 首屏 track_ids_count>0 降级非致命(snack)。修 92/200 首歌单点开整页 Load Failed。 - 歌单列表盘缓存 <data_dir>/playlists.bin(magic+ver+ts+数组):home_reload 先缓存 秒渲 + 后台静默刷新,刷新失败有数据不置错态。修返回首页卡 LOADING(275 遗留)。 - 孤儿 .part/.tmp:evict/total_bytes LRU 扫描顺续删 mtime>7 天(in-flight mtime 新鲜安全)。 验证:新 bench/playlist_race_sim(竞态防串/批边界/非致命/盘缓存/reset)+cache_evict_sim case9(7 天回收)+window/pagination/search/stream_sim 零回归+test_ncm_core host+mipsel PASS;build_ncm.sh -Wall -Wextra 0 warning(text 978354/bss 1496484);0 真实请求。 遗留:appstate_logout 未联动删 playlists.bin;g_st.playlists[] 快照拷贝仍有极小交错窗。
This commit is contained in:
1 parent
d8eeef5c57
commit
f0b73e119d
11 files changed
+744
-48
No files matched your search
@@ -168,7 +168,7 @@ static void home_draw_empty(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_HOME) return;
|
||||
if (g_load_err == 0 && g_st.playlist_count != 0) return;
|
||||
if (g_st.playlist_count != 0) return; /* 有数据(缓存/已载)即显示列表,后台刷新失败不置空 */
|
||||
if (g_home_loading) { ncm_draw_empty(r_list, "LOADING", "正在加载歌单..."); return; }
|
||||
if (g_st.logged_in == 0) ncm_draw_empty(r_list, "NOT SIGNED IN", "请先登录");
|
||||
else if (g_load_err == NCM_ERR_AUTH) ncm_draw_empty(r_list, "SESSION EXPIRED", "请重新登录");
|
||||
@@ -201,7 +201,9 @@ static void home_apply(void)
|
||||
meow_ui_paint_full();
|
||||
}
|
||||
|
||||
/* 后台 worker:唯一走网络的地方(ensure_uid + user_playlist)。 */
|
||||
/* 后台 worker:唯一走网络的地方(ensure_uid + user_playlist)。
|
||||
* §26 stale-while-revalidate:网络成功 → appstate_load_playlists 覆写盘缓存 + 更新内存;
|
||||
* 失败但有缓存在展示 → 保持缓存列表(g_load_err=0),后台静默不打断(断网可离线浏览)。 */
|
||||
static void *home_worker(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
@@ -211,7 +213,7 @@ static void *home_worker(void *arg)
|
||||
if (rc == NCM_OK)
|
||||
account_vip_maybe_refresh(0); /* §20:超 1 天/到期后各补查一次(worker 线程,不卡 UI) */
|
||||
pthread_mutex_lock(&g_home_mu);
|
||||
g_load_err = rc;
|
||||
g_load_err = (g_st.playlist_count > 0) ? 0 : rc; /* 有数据(缓存/已载)即不置错误态 */
|
||||
g_home_loading = 0;
|
||||
g_home_gen++;
|
||||
pthread_mutex_unlock(&g_home_mu);
|
||||
@@ -222,9 +224,14 @@ void home_reload(void)
|
||||
{
|
||||
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;
|
||||
g_st.playlist_count = 0;
|
||||
g_home_loading = 1;
|
||||
if (!had_cache) {
|
||||
g_st.playlist_count = 0; /* 无缓存 → LOADING 空态 */
|
||||
g_home_loading = 1;
|
||||
}
|
||||
g_home_thr_on = 1;
|
||||
if (pthread_create(&g_home_thr, NULL, home_worker, NULL) != 0) {
|
||||
g_home_thr_on = 0; g_home_loading = 0; g_load_err = NCM_ERR_IO;
|
||||
|
||||
@@ -35,10 +35,23 @@ static volatile int g_plgen;
|
||||
static int g_placked;
|
||||
static volatile int g_plloading;
|
||||
static volatile int g_plloaded_idx;
|
||||
static volatile int g_plloaded_gen; /* worker 启动时代次(§26:过期结果丢弃判据) */
|
||||
static volatile int g_pl_stale; /* 1=本次结果被判过期(未写全局,需重拉当前目标) */
|
||||
static volatile int g_ens_lo, g_ens_hi; /* 滑窗补载目标区间(全局下标) */
|
||||
static volatile int g_ens_first; /* 1=首屏重头(offset=0) */
|
||||
static volatile int g_ens_err; /* 最近一次滑窗补载错误(0=ok;首屏错误走 g_load_err) */
|
||||
|
||||
/* 打开快照(§26 开A→未载完→开B 防串):refresh 在启动 worker 前把「目标歌单」拷进 g_tgt,
|
||||
* worker 启动时再拷到本地——detail 加载不再依赖 g_st.playlists[](会被回家时 home_worker 的
|
||||
* appstate_load_playlists 并发 memset 污染)。tgen = appstate_playlist_begin 返回的打开代次。 */
|
||||
typedef struct {
|
||||
ncm_playlist_t pl; /* 目标快照(id/name/cover/track_count) */
|
||||
int is_search; /* 1=搜索歌单(active_playlist==NCM_PL_SEARCH) */
|
||||
int idx; /* 目标下标(-2=搜索哨兵) */
|
||||
int gen; /* 打开代次(appstate_playlist_begin 返回) */
|
||||
} pl_target_t;
|
||||
static pl_target_t g_tgt;
|
||||
|
||||
static void pl_drag_reset(void)
|
||||
{
|
||||
g_press_row = -1; g_press_delta = 0; g_dragging = 0; g_drag_last_y = 0;
|
||||
@@ -218,19 +231,45 @@ static void pl_apply(int reset_scroll)
|
||||
static void *playlist_worker(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int my_idx = g_st.active_playlist;
|
||||
int first = g_ens_first;
|
||||
int lo = g_ens_lo, hi = g_ens_hi;
|
||||
/* 拷贝打开快照 + 任务参数:单飞(g_plthr_on)保证 worker 运行期间 g_tgt 不被改写。 */
|
||||
ncm_playlist_t tpl;
|
||||
int tsearch, tidx, tgen, first, lo, hi;
|
||||
pthread_mutex_lock(&g_plmu);
|
||||
tpl = g_tgt.pl; tsearch = g_tgt.is_search;
|
||||
tidx = g_tgt.idx; tgen = g_tgt.gen;
|
||||
first = g_ens_first; lo = g_ens_lo; hi = g_ens_hi;
|
||||
pthread_mutex_unlock(&g_plmu);
|
||||
int cur_active = g_st.active_playlist; /* ensure 路径结束校验用(当前目标) */
|
||||
int rc;
|
||||
if (g_host_mock) rc = 0;
|
||||
else if (my_idx < 0) rc = NCM_ERR_ARG;
|
||||
else if (first) rc = appstate_load_playlist_songs_page(my_idx, 0, NCM_SONG_BATCH);
|
||||
else rc = appstate_song_ensure_range(lo, hi);
|
||||
else if (first) {
|
||||
/* 首屏:快照式加载(detail 全量 trackIds + 物化首段)。ctx 校验代次/目标,过期自动丢弃。 */
|
||||
if (tidx < 0) rc = NCM_ERR_ARG;
|
||||
else rc = appstate_load_playlist_songs_ctx(&tpl, tsearch, tgen, tidx, 0, NCM_SONG_BATCH);
|
||||
} else {
|
||||
/* 滑窗补载:物化视口区间(盘缓存优先,批网格补载)。 */
|
||||
rc = (lo < hi) ? appstate_song_ensure_range(lo, hi) : NCM_OK;
|
||||
}
|
||||
pthread_mutex_lock(&g_plmu);
|
||||
if (first) g_load_err = rc; /* 首屏失败 → 整页空态(点按重试) */
|
||||
else g_ens_err = rc; /* 补载失败 → snack + 占位行保留(滚动/点按再试) */
|
||||
if (first) {
|
||||
if (rc == NCM_ERR_STALE) {
|
||||
/* 打开 A 未完→已切到 B:本次结果过期(ctx 未写全局),丢弃,tick 对当前目标重拉。 */
|
||||
g_pl_stale = 1; g_load_err = 0;
|
||||
} else if (rc != NCM_OK && rc != NCM_ERR_AUTH && g_st.track_ids_count > 0) {
|
||||
/* 索引成功但曲目批部分失败(缺歌/单批网络):非致命——保留已物化 + snack,
|
||||
* 不整页 Load Failed(修【92/200 首真机点开即「加载失败」】)。 */
|
||||
g_ens_err = rc; g_load_err = 0;
|
||||
} else {
|
||||
g_load_err = rc; /* 索引失败/鉴权失败 → 整页空态(点按重试) */
|
||||
}
|
||||
g_plloaded_idx = tidx;
|
||||
g_plloaded_gen = tgen;
|
||||
} else {
|
||||
g_ens_err = rc; /* 补载失败 → snack + 占位行保留(滚动/点按再试) */
|
||||
g_plloaded_idx = cur_active;
|
||||
g_plloaded_gen = tgen;
|
||||
}
|
||||
g_plloading = 0;
|
||||
g_plloaded_idx = my_idx;
|
||||
g_plgen++;
|
||||
pthread_mutex_unlock(&g_plmu);
|
||||
return NULL;
|
||||
@@ -266,7 +305,19 @@ void playlist_refresh(void)
|
||||
if (g_plthr_on) return;
|
||||
g_load_err = 0;
|
||||
g_ens_err = 0;
|
||||
/* 打开快照 + 代次(§26):锁内拷目标歌单,detail 加载不依赖 g_st.playlists[](会被
|
||||
* 回家时 home_worker 并发 memset 污染成空 id → 3 首歌/串数据);同时记录打开代次,
|
||||
* 过期结果在 worker 完成时判 NCM_ERR_STALE 丢弃(防「开 A→开 B 的迟到结果串 B」)。 */
|
||||
const ncm_playlist_t *pcur = pl_cur();
|
||||
if (!pcur || !pcur->id[0]) { g_load_err = NCM_ERR_ARG; pl_apply(1); return; }
|
||||
pthread_mutex_lock(&g_plmu);
|
||||
g_tgt.pl = *pcur;
|
||||
g_tgt.is_search = (g_st.active_playlist == NCM_PL_SEARCH);
|
||||
g_tgt.idx = g_st.active_playlist;
|
||||
g_tgt.gen = appstate_playlist_begin(g_tgt.idx);
|
||||
pthread_mutex_unlock(&g_plmu);
|
||||
g_ens_first = 1; /* 首屏:重建索引 + 物化首段 */
|
||||
g_pl_stale = 0;
|
||||
g_plloading = 1;
|
||||
g_plthr_on = 1;
|
||||
if (pthread_create(&g_plthr, NULL, playlist_worker, NULL) != 0) {
|
||||
@@ -283,7 +334,15 @@ void playlist_tick(uint64_t now)
|
||||
g_placked = g_plgen;
|
||||
g_plthr_on = 0;
|
||||
g_plloading = 0;
|
||||
if (g_plloaded_idx != g_st.active_playlist) { playlist_refresh(); return; }
|
||||
/* §26 防串:结果过期(代次不匹配)/已切到别的歌单 → 先清窗口(不把旧歌单数据画在新歌单
|
||||
* 标题下),再对当前 active_playlist 重新发起。 */
|
||||
if (g_pl_stale || g_plloaded_idx != g_st.active_playlist ||
|
||||
g_plloaded_gen != appstate_playlist_open_gen()) {
|
||||
g_pl_stale = 0;
|
||||
appstate_playlist_reset();
|
||||
playlist_refresh();
|
||||
return;
|
||||
}
|
||||
if (g_load_err != 0) {
|
||||
g_st.track_ids_count = 0; /* 首屏失败 → 整页空态(点按重试) */
|
||||
pl_apply(1);
|
||||
|
||||
Reference in new issue
Block a user