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:
1 parent
97b741cf56
commit
10690bc348
9 files changed
+290
-9
No files matched your search
@@ -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)----
|
||||
|
||||
@@ -358,6 +358,12 @@ int appstate_playlist_open_gen(void); /* 当前打开代次(0=从未
|
||||
int appstate_playlist_open_idx(void); /* 当前打开目标下标(-2=搜索歌单哨兵;-1=无) */
|
||||
int appstate_playlist_begin(int idx); /* 递增代次并记目标;返回新代次(UI 打开/刷新时调) */
|
||||
void appstate_playlist_reset(void); /* 清窗口/索引(UI 丢弃过期结果、在播安全)。加锁。 */
|
||||
/* 窗口归属(§NEW bug B:新进歌单显示上一个歌单内容)——
|
||||
* worker 首屏发布窗口后 appstate_playlist_window_set 标记
|
||||
* 归属歌单 id;playlist_refresh 在换歌单前用 window_is 判
|
||||
* 目标≠归属 → 同步 reset 清窗,防「开 B 显示 A」。 */
|
||||
void appstate_playlist_window_set(const char *plid); /* 标记当前窗口归属歌单 id */
|
||||
int appstate_playlist_window_is(const char *plid); /* 1=窗口归属该歌单(换歌单清窗判据) */
|
||||
int appstate_load_playlist_songs_ctx(const ncm_playlist_t *pl, int is_search,
|
||||
int ctx_gen, int ctx_idx, int offset, int limit);
|
||||
/* 快照式加载(歌单详情/曲目物化):
|
||||
|
||||
@@ -272,6 +272,10 @@ static void *playlist_worker(void *arg)
|
||||
}
|
||||
g_plloaded_idx = tidx;
|
||||
g_plloaded_gen = tgen;
|
||||
/* §NEW bug B:首屏发布后标记窗口归属=目标歌单(失败/部分也归属,支持同歌单重试不清窗)。
|
||||
* 过期(STALE)不标记——窗口仍属旧歌单。playlist_refresh 据此换歌单清窗。 */
|
||||
if (rc != NCM_ERR_STALE)
|
||||
appstate_playlist_window_set(tpl.id);
|
||||
} else {
|
||||
g_ens_err = rc; /* 补载失败 → snack + 占位行保留(滚动/点按再试) */
|
||||
g_plloaded_idx = cur_active;
|
||||
@@ -324,6 +328,14 @@ void playlist_refresh(void)
|
||||
g_tgt.idx = g_st.active_playlist;
|
||||
g_tgt.gen = appstate_playlist_begin(g_tgt.idx);
|
||||
pthread_mutex_unlock(&g_plmu);
|
||||
|
||||
/* §NEW bug B(新进歌单显示上一个歌单内容):目标歌单与当前窗口归属不一致(跨歌单/搜索不同
|
||||
* 结果)→ 同步清窗口,再 pl_apply。否则 pl_apply(1) 把新歌单标题绑到上一歌单残留窗口
|
||||
* (track_ids_count/songs),新页瞬间渲染上一歌单行。同歌单手动刷新(id 相同)不清窗,
|
||||
* 保留旧内容(stale-while-revalidate)——worker 异步重载替换。 */
|
||||
if (!appstate_playlist_window_is(g_tgt.pl.id))
|
||||
appstate_playlist_reset();
|
||||
|
||||
g_ens_first = 1; /* 首屏:重建索引 + 物化首段 */
|
||||
g_pl_stale = 0;
|
||||
g_plloading = 1;
|
||||
|
||||
Reference in new issue
Block a user