fix(ncm §31): 修两处 UI 数据竞态——返回首页行空白(staging发布) + 进第二个歌单显示上一歌单(换歌单清窗)

A. 返回首页各行 2~3s 空白:appstate_load_playlists 就地 memset(g_st.playlists[])
   与 home_src2 无锁逐行绑定竞态,刷新期间 playlist_count 保留旧值 → UI 读清零行。
   照搬 s_build[] staging 范式:网络落 s_pls[] → 成功后锁内原子发布(拷回+更新count),
   失败保留旧快照(断网离线浏览)。绝不就地清零 UI 可见数组。

B. 进第二个歌单显示上一歌单:playlist_open/refresh 同步路径不调 appstate_playlist_reset,
   pl_apply(1) 绑到上一歌单残留窗口(track_ids_count/songs)。
   新增窗口归属(歌单 id 唯一标识, 对搜索歌单同样成立): worker 首屏发布后
   appstate_playlist_window_set;refresh 目标≠归属(窗口is) → 同步 reset 再 pl_apply。
   同歌单手动刷新(id 同)保留旧内容(stale-while-revalidate)。

宿主回归:playlist_race_sim(新 F 并发读无清零态/G 开A→开B 不串)、window/search/
cache_evict/player_flow/stream_sim、test_ncm_core 宿主+mipsel 全 PASS;
build_ncm.sh -Wall -Wextra 0 warning;0 真实请求;未部署。
This commit is contained in:
AskaEth committed 2026-09-02 03:03:48 +08:00
1 parent 97b741cf56
commit 10690bc348
9 files changed
+290 -9

No files matched your search

+52 -7
View File
@@ -34,6 +34,29 @@ int appstate_playlist_begin(int idx)
return g;
}
/* 当前窗口归属歌单 id(§NEW bug B:新进歌单显示上一个歌单内容)。窗口数据(song_ids/songs/
* track_ids_count)属于哪个歌单由 id 唯一标识(对搜索歌单同样成立——不同搜索结果 = 不同 id)。
* worker 首屏发布窗口后调 appstate_playlist_window_set 标记;playlist_refresh 在起 worker 前用
* appstate_playlist_window_is 判「目标歌单是否=当前窗口归属」,不一致 → 同步 appstate_playlist_reset()
* 清掉上一歌单残留,防 pl_apply(1) 把新歌单标题绑到上一歌单数据(「开 B 显示 A」)。 */
static pthread_mutex_t g_winpl_mu = PTHREAD_MUTEX_INITIALIZER;
static char g_win_plid[32];
void appstate_playlist_window_set(const char *plid)
{
pthread_mutex_lock(&g_winpl_mu);
if (plid && plid[0]) ncm_strlcpy(g_win_plid, plid, sizeof g_win_plid);
else g_win_plid[0] = 0;
pthread_mutex_unlock(&g_winpl_mu);
}
int appstate_playlist_window_is(const char *plid)
{
pthread_mutex_lock(&g_winpl_mu);
int same = (plid && plid[0] && g_win_plid[0] && strcmp(g_win_plid, plid) == 0);
pthread_mutex_unlock(&g_winpl_mu);
return same;
}
/* 清窗口/索引(UI 丢弃过期歌单结果时用;播放快照不受影响——播放经 play_song 快照)。 */
void appstate_playlist_reset(void)
{
@@ -44,6 +67,9 @@ void appstate_playlist_reset(void)
g_st.song_count = 0;
g_st.track_ids_count = 0;
pthread_mutex_unlock(&g_song_mu);
pthread_mutex_lock(&g_winpl_mu); /* 窗口已清 → 归属歌单一并清空(下一 worker 重新标记) */
g_win_plid[0] = 0;
pthread_mutex_unlock(&g_winpl_mu);
}
static void appstate_srch_hist_init(void); /* §23:读入本地搜索历史(appstate_init 首调) */
@@ -174,19 +200,38 @@ int appstate_ensure_uid(void)
return rc;
}
/* 歌单列表 staging 发布锁(§NEW bug A):appstate_load_playlists 的「网络成功后锁内原子发布
* (拷回 g_st.playlists[] + 更新 playlist_count)」与 UI 线程 home_src2/home_draw 无锁逐行绑定
* 之间的发布点。持久化(playlists.bin)同单写者(home worker)在发布后读,无相互竞态。 */
static pthread_mutex_t g_pls_mu = PTHREAD_MUTEX_INITIALIZER;
int appstate_load_playlists(void)
{
if (!g_st.logged_in) return NCM_ERR_AUTH;
int rc = appstate_ensure_uid();
if (rc != NCM_OK) return rc;
memset(g_st.playlists, 0, sizeof g_st.playlists);
/* §NEW bug A(返回首页各行 2~3 秒空白):旧实现在**UI 可见数组**就地 memset→网络回填,与
* home_src2 无锁逐行绑定竞态——刷新期间 playlist_count 保留旧值(cache),UI 读到被清零的行
* (无标题/曲目数 0),仅下标 0 因先读幸存。照搬本文件 appstate_load_playlist_songs_impl 的
* s_build[] staging 范式:网络结果落 staging(s_pls[]),成功后锁内**一次性拷回** g_st.playlists[]
* + 更新 playlist_count(原子发布——UI 全程读到旧或新完整快照,绝不读到清零态);失败保留旧
* 快照(不改 playlists/playlist_count,断网可离线浏览旧列表)。静态数组:单写者(home worker
* 单飞),免 96KB 栈。 */
static ncm_playlist_t s_pls[NCM_PLAYLIST_MAX];
int n = 0;
rc = ncm_api_user_playlist(g_st.ctx.device_id, g_st.ctx.cookie, g_st.uid,
NCM_PLAYLIST_MAX, 0, g_st.playlists, NCM_PLAYLIST_MAX, &n);
g_st.playlist_count = (rc == NCM_OK) ? n : 0;
meow_log(MEOW_LOG_INFO, "home: playlists rc=%d count=%d uid=%s", rc, g_st.playlist_count, g_st.uid);
if (rc == NCM_OK) appstate_playlists_cache_save(); /* §26:网络成功 → 覆写盘缓存(stale-while-revalidate) */
return rc;
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);
if (rc2 == NCM_OK) {
int cnt = (n > NCM_PLAYLIST_MAX) ? NCM_PLAYLIST_MAX : n;
pthread_mutex_lock(&g_pls_mu);
if (cnt > 0) memcpy(g_st.playlists, s_pls, (size_t)cnt * sizeof(ncm_playlist_t));
g_st.playlist_count = cnt; /* count 在拷回后更新:读侧绝不见「新 count + 未拷回行」 */
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);
return rc2;
}
/* ---- 歌单列表磁盘缓存(§26,NCM_CACHE_DESIGN §2.2)----