fix(ncm): 三连修 — 封面切歌不更新/缓冲无反馈失败无声/暂停切歌边界 (REPORT §18)
- cover worker 忙等死循环(成功后不清目标,每圈解码+换入+gen++,CPU 100%+每帧整盘重绘) → 成功清目标转怠速; 失败指数退避 0.5-4s 连 8 次放弃+WARN 日志; 去重加在拉目标; cover_get 持锁+cover_put 修绘制期 UAF; g_host_mock 防真网守卫 - 封面兜底污染修复: 不再把单曲 al_pic 写进 g_st.cover_url(否则 al_pic 缺失曲回落成 上一首封面且撞去重 → 封面永停); plr_page_enter 强制重估自愈; pl_page_enter 回填歌单封面 - 缓冲 UI: BUFFER 态「缓冲中… N%」(g_play.dl_done/total, 下载回调累加); 播放错误中文 错误条+snack(旧版 err 无 UI 消费者=无声死等); STOPPED 态播放键=重试; play/next 清 err - 写失败 set_err 修连环跳歌(旧版静默 break 被外层当自然播完,流死时跳完歌单+狂发 song_url); still_current 护栏修下载途中切歌的状态/快照过期覆盖 - 验证(0 真实请求): cover_chain_sim 7 组+player_flow_sim P1-P5 全 PASS, 旧代码对照 T2/P2/P4 必败坐实根因; audiod_chain_sim 14/14; pagination/window/core+qemu 回归 PASS; 宿主+mipsel 自有代码 0 warning; 9 页离屏渲染 rc=0 (含新 mock 页 playerbuf/playererr) - ncm md5 50d41bbf4a379e925699ba013b997ffd (未部署真机, 留主会话)
This commit is contained in:
1 parent
614694ad4a
commit
6200a41b71
13 files changed
+820
-20
No files matched your search
@@ -397,7 +397,8 @@ void appstate_apply_playlist_cover(int idx)
|
||||
ncm_strlcpy(g_st.cover_url, g_st.playlists[idx].cover, sizeof g_st.cover_url);
|
||||
}
|
||||
|
||||
/* 设置播放页当前歌曲 + 封面 URL(player 页 UI 读)。idx=-1 表示清除。 */
|
||||
/* 设置播放页当前歌曲下标。cover_url 非空时一并更新 g_st.cover_url(歌单封面兜底值);
|
||||
* §18 起调用方传 NULL——兜底值由 appstate_apply_playlist_cover 维护,勿用单曲 al_pic 污染。 */
|
||||
void appstate_set_playing_song(int idx, const char *cover_url)
|
||||
{
|
||||
g_st.playing_song = idx;
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
* 线程模型:一个持久的 cover 工作线程负责「目标 URL → 下载 → 解码 → 换入」;UI 线程只经
|
||||
* cover_request(url)(提交目标)与 cover_get()/cover_changed()(读取/判变)+ cover_ack()(消费)。
|
||||
* 工作线程不碰 meow_ui(绘制在 UI 线程 tick 判 cover_changed 后触发 invalidate)。
|
||||
*
|
||||
* §18 修复(切歌封面不更新/CPU 忙等根因):
|
||||
* - worker 换入成功后**清目标**(g_tgt_url[0]=0)转怠速——旧版成功后不清目标,每圈
|
||||
* 缓存读+JPEG 解码+换入+g_gen++ 无 sleep 死循环:CPU 占满、cover_changed() 恒真导致
|
||||
* 播放页每帧整盘重绘,慢网络下新封面请求被饿到表现如「切歌封面不更新」。
|
||||
* - 下载/解码失败**指数退避**重试(0.5s→4s),连续 8 次放弃(UI 重进页会重新提交目标);
|
||||
* 失败写 WARN 日志(排查资产),不再无声死等。
|
||||
* - 绘制安全:cover_get() 持锁返回、cover_put() 解锁——旧版 UI 绘制期 worker 可 free
|
||||
* 同一 meow_img(use-after-free);调用方须在 cover_get 后配对 cover_put。
|
||||
*/
|
||||
#include "../ncm_app_internal.h"
|
||||
#include <stdio.h>
|
||||
@@ -78,12 +87,20 @@ static void save_cover_raw(const char *url, size_t len)
|
||||
}
|
||||
}
|
||||
|
||||
#define COVER_RETRY_MAX 8 /* 连续失败放弃次数(UI 重进页/切歌会重新提交目标) */
|
||||
|
||||
static void *cover_worker(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int backoff_ms = 0, fails = 0;
|
||||
while (!g_quit) {
|
||||
pthread_mutex_lock(&g_cm);
|
||||
if (!g_tgt_url[0]) { pthread_mutex_unlock(&g_cm); usleep(250 * 1000); continue; }
|
||||
if (!g_tgt_url[0]) {
|
||||
pthread_mutex_unlock(&g_cm);
|
||||
backoff_ms = 0; fails = 0;
|
||||
usleep(250 * 1000);
|
||||
continue;
|
||||
}
|
||||
char url[512];
|
||||
ncm_strlcpy(url, g_tgt_url, sizeof url);
|
||||
pthread_mutex_unlock(&g_cm);
|
||||
@@ -104,7 +121,25 @@ static void *cover_worker(void *arg)
|
||||
if (!img) {
|
||||
blen = 0;
|
||||
img = decode_cover(url, &blen);
|
||||
if (!img) { usleep(500 * 1000); continue; }
|
||||
if (!img) {
|
||||
/* 失败:指数退避重试,连续 COVER_RETRY_MAX 次放弃(不无声死等;
|
||||
* 目标保留期间 UI 每帧重请求由 cover_request 去重挡住)。 */
|
||||
fails++;
|
||||
if (fails >= COVER_RETRY_MAX) {
|
||||
meow_log(MEOW_LOG_WARN, "cover: give up after %d fails url=%.64s", fails, url);
|
||||
pthread_mutex_lock(&g_cm);
|
||||
if (strcmp(g_tgt_url, url) == 0) g_tgt_url[0] = 0;
|
||||
pthread_mutex_unlock(&g_cm);
|
||||
backoff_ms = 0; fails = 0;
|
||||
continue;
|
||||
}
|
||||
meow_log(MEOW_LOG_WARN, "cover: fetch failed (%d) url=%.64s retry in %dms",
|
||||
fails, url, backoff_ms ? backoff_ms : 500);
|
||||
if (g_app_quit) return NULL;
|
||||
usleep(backoff_ms ? backoff_ms * 1000 : 500 * 1000);
|
||||
backoff_ms = backoff_ms ? (backoff_ms * 2 > 4000 ? 4000 : backoff_ms * 2) : 500;
|
||||
continue;
|
||||
}
|
||||
if (g_st.ctx.cache.dir[0]) {
|
||||
/* 写前检查:池大小超限先按 LRU 淘汰(与 mp3 同池;封面约 200KB,先腾空间) */
|
||||
ncm_cache_evict(&g_st.ctx.cache);
|
||||
@@ -117,6 +152,7 @@ static void *cover_worker(void *arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
backoff_ms = 0; fails = 0;
|
||||
|
||||
pthread_mutex_lock(&g_cm);
|
||||
/* 目标仍是 url 才换入(期间未提交更早) */
|
||||
@@ -126,6 +162,7 @@ static void *cover_worker(void *arg)
|
||||
ncm_strlcpy(g_cover_url, url, sizeof g_cover_url);
|
||||
save_cover_raw(url, blen);
|
||||
g_gen++;
|
||||
g_tgt_url[0] = 0; /* 目标达成即清:worker 转怠速(修忙等死循环,§18) */
|
||||
} else {
|
||||
meow_img_free(img);
|
||||
}
|
||||
@@ -138,8 +175,11 @@ static void *cover_worker(void *arg)
|
||||
void cover_request(const char *url)
|
||||
{
|
||||
if (!url || !url[0]) return;
|
||||
if (g_host_mock) return; /* 宿主 mock = 0 网络纪律(mock URL 不得发真实 HTTPS) */
|
||||
pthread_mutex_lock(&g_cm);
|
||||
if (g_cover_url[0] && strcmp(g_cover_url, url) == 0) { pthread_mutex_unlock(&g_cm); return; }
|
||||
/* 去重:已在展示 或 已在拉取途中的同一 URL 不重复提交。 */
|
||||
if ((g_cover_url[0] && strcmp(g_cover_url, url) == 0) ||
|
||||
(g_tgt_url[0] && strcmp(g_tgt_url, url) == 0)) { pthread_mutex_unlock(&g_cm); return; }
|
||||
ncm_strlcpy(g_tgt_url, url, sizeof g_tgt_url);
|
||||
pthread_mutex_unlock(&g_cm);
|
||||
if (!g_thr_on) {
|
||||
@@ -151,14 +191,17 @@ void cover_request(const char *url)
|
||||
}
|
||||
}
|
||||
|
||||
/* UI 线程:读取当前封面(不拥有)。 */
|
||||
/* UI 线程:读取当前封面——**持锁返回**,绘制完必须配对 cover_put()(§18 修 UAF:
|
||||
* 旧版 get 后无锁绘制,worker 换入时 free 同一张图 → use-after-free)。 */
|
||||
meow_img *cover_get(void)
|
||||
{
|
||||
meow_img *im;
|
||||
pthread_mutex_lock(&g_cm);
|
||||
im = g_cover_img;
|
||||
return g_cover_img;
|
||||
}
|
||||
|
||||
void cover_put(void)
|
||||
{
|
||||
pthread_mutex_unlock(&g_cm);
|
||||
return im;
|
||||
}
|
||||
|
||||
/* 是否有新封面待绘制(UI tick 判变)。 */
|
||||
|
||||
Reference in new issue
Block a user