fix(audiod): 孤儿会话回收 — 客户端崩溃/被杀(sig=9)/正常退出后会话完整回收,STATE 回干净无流基线
根因: cli_free(EOF)与 STOP 路径只置 active=0 + 唤醒排空,未清 sid/written/played/underrun/ rms/peak/lvlseq——ncm 被内核 OOM 杀后面板 /api/overview audio 块残留(active=0 但 sid/统计挂着)。 修法: 两段式 session_end_locked(即时置死+清 sid/app_id,保留 rate/ch/fmt 供排空算 frame_size)+ session_clean_locked(播放线程排空完成后彻底归零 rate/ch/fmt/written/played/underrun/drop/stall/ rms/peak/lvlseq)。顺带修 dac_vol_set 一处 -Wmisleading-indentation。 宿主 51/51 PASS(47 既有 + T9 SIGKILL 孤儿回收断言 STATE 全清,模拟子进程崩溃). meow_audiod b122c9b8 交叉0warning
This commit is contained in:
1 parent
19f7e59927
commit
121d30ec95
5 files changed
+135
-27
No files matched your search
Binary file not shown.
@@ -111,8 +111,9 @@ static int g_logfd = -1;
|
|||||||
static volatile sig_atomic_t g_stop = 0;
|
static volatile sig_atomic_t g_stop = 0;
|
||||||
|
|
||||||
/* ---- 共享态(g_mu 保护 + g_cond 唤醒;g_cond 用 CLOCK_MONOTONIC,main 中初始化) ---- */
|
/* ---- 共享态(g_mu 保护 + g_cond 唤醒;g_cond 用 CLOCK_MONOTONIC,main 中初始化) ---- */
|
||||||
/* 前向声明(供 cli_free/dispatch 使用;定义见下) */
|
/* 前向声明(供 cli_free/dispatch/播放线程使用;定义见下) */
|
||||||
static void g_sid_reset(void);
|
static void session_end_locked(void);
|
||||||
|
static void session_clean_locked(void);
|
||||||
static void _audio_mark_reconfig_needed(void);
|
static void _audio_mark_reconfig_needed(void);
|
||||||
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||||
static pthread_cond_t g_cond;
|
static pthread_cond_t g_cond;
|
||||||
@@ -556,7 +557,8 @@ static void dac_vol_set(int pct)
|
|||||||
int t = 100 - pct, dac;
|
int t = 100 - pct, dac;
|
||||||
if (pct <= 0) dac = 255; /* 真静音(DS1039:255=-127.5dB) */
|
if (pct <= 0) dac = 255; /* 真静音(DS1039:255=-127.5dB) */
|
||||||
else dac = t * t * 192 / 10000; /* 感知平方锥度:dB 衰减=(1-pct)^2×96dB */
|
else dac = t * t * 192 / 10000; /* 感知平方锥度:dB 衰减=(1-pct)^2×96dB */
|
||||||
if (dac > 255) dac = 255; if (dac < 0) dac = 0; /* CS43198=衰减量(0最响255静音)+平方锥度(听感线性) */
|
if (dac > 255) dac = 255;
|
||||||
|
if (dac < 0) dac = 0; /* CS43198=衰减量(0最响255静音)+平方锥度(听感线性) */
|
||||||
amixer_set(1, dac, 1);
|
amixer_set(1, dac, 1);
|
||||||
amixer_set(2, dac, 1);
|
amixer_set(2, dac, 1);
|
||||||
logmsg("DAC vol -> %d%% (L/R=%d)", pct, dac);
|
logmsg("DAC vol -> %d%% (L/R=%d)", pct, dac);
|
||||||
@@ -1010,6 +1012,7 @@ static void *playback_thread(void *arg)
|
|||||||
if ((draining && av < (size_t)fs) || paused) {
|
if ((draining && av < (size_t)fs) || paused) {
|
||||||
/* 排空完毕(或暂停):若设备仍开且已排空 → 关;否则等待 */
|
/* 排空完毕(或暂停):若设备仍开且已排空 → 关;否则等待 */
|
||||||
if (draining && g_dev_open) dev_close();
|
if (draining && g_dev_open) dev_close();
|
||||||
|
if (draining) session_clean_locked(); /* 流已死且排空完成:会话统计彻底归零 */
|
||||||
cond_wait_tmo(COND_WAIT_MS);
|
cond_wait_tmo(COND_WAIT_MS);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1065,18 +1068,44 @@ static void cli_free(struct client *c)
|
|||||||
c->fd = -1;
|
c->fd = -1;
|
||||||
if (g_st.active && g_st.sid == c->sid) {
|
if (g_st.active && g_st.sid == c->sid) {
|
||||||
logmsg("client EOF: stream %u recycled (app=%s)", (unsigned)c->sid, g_st.app_id);
|
logmsg("client EOF: stream %u recycled (app=%s)", (unsigned)c->sid, g_st.app_id);
|
||||||
g_st.active = 0;
|
session_end_locked();
|
||||||
g_sid_reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static void g_sid_reset(void)
|
/* ---- 流结束/会话回收(客户端死亡或显式 STOP 后,STATE 须回到干净无流基线) ----
|
||||||
|
* 两段式:
|
||||||
|
* - session_end_locked():流结束标记(停止/EOF 即时调用)。置 inactive + 清会话身份
|
||||||
|
* (sid/app_id)+ 唤醒播放线程排空 ring。**不清空 ring、不清 rate/ch/fmt**:未播完的缓冲
|
||||||
|
* 要写完(排空)才关设备,否则 tone/播放末尾被截断;排空期间 frame_size() 仍依赖 rate/ch/fmt。
|
||||||
|
* ring 的重置只发生在 OPEN 授予新流时(ring_rebuild)。
|
||||||
|
* - session_clean_locked():会话统计彻底清零(流已结束且 ring 排空完毕的迟后调用)——
|
||||||
|
* STATE 归零:rate/ch/fmt=0、written=played=0、underrun/drop/stall=0、rms/peak=-inf、lvlseq=0。
|
||||||
|
* 此刻 rate/ch/fmt 已不再被排空引用,可安全清零。txdrop 为全局客户端级(不随流清),不动。 */
|
||||||
|
static void session_end_locked(void)
|
||||||
{
|
{
|
||||||
/* 停止/EOF 只标记流结束 + 唤醒播放线程排空 ring(**不清空 ring**:未播完的缓冲要写完
|
g_st.active = 0;
|
||||||
* 才关设备,否则 tone/播放末尾被截断)。ring 的重置只发生在 OPEN 授予新流时(ring_rebuild)。 */
|
g_st.sid = 0;
|
||||||
|
g_st.app_id[0] = 0;
|
||||||
g_paused = 0;
|
g_paused = 0;
|
||||||
g_reconfig_pending = 0;
|
g_reconfig_pending = 0;
|
||||||
pthread_cond_signal(&g_cond);
|
pthread_cond_signal(&g_cond);
|
||||||
}
|
}
|
||||||
|
static void session_clean_locked(void)
|
||||||
|
{
|
||||||
|
g_st.rate = 0;
|
||||||
|
g_st.ch = 0;
|
||||||
|
g_st.fmt = 0;
|
||||||
|
g_written_frames = 0;
|
||||||
|
g_played_frames = 0;
|
||||||
|
g_underrun = 0;
|
||||||
|
g_drop_frames = 0;
|
||||||
|
g_stall = 0;
|
||||||
|
g_m_sumsq = 0;
|
||||||
|
g_m_peak = 0;
|
||||||
|
g_m_frames = 0;
|
||||||
|
g_m_win = 0;
|
||||||
|
g_lvl_rms_db10 = g_lvl_peak_db10 = METER_FLOOR_DB10;
|
||||||
|
g_lvl_seq = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 处理一个客户端帧(调用方已持 g_mu) */
|
/* 处理一个客户端帧(调用方已持 g_mu) */
|
||||||
static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uint8_t *pl, uint32_t len)
|
static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uint8_t *pl, uint32_t len)
|
||||||
@@ -1176,11 +1205,9 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin
|
|||||||
pthread_cond_signal(&g_cond);
|
pthread_cond_signal(&g_cond);
|
||||||
else /* STOP */ {
|
else /* STOP */ {
|
||||||
logmsg("STOP sid=%u (app=%s)", (unsigned)sid, g_st.app_id);
|
logmsg("STOP sid=%u (app=%s)", (unsigned)sid, g_st.app_id);
|
||||||
g_st.active = 0;
|
|
||||||
g_st.sid = 0;
|
|
||||||
c->sid = 0;
|
c->sid = 0;
|
||||||
c->state = CLS_OPEN;
|
c->state = CLS_OPEN;
|
||||||
g_sid_reset();
|
session_end_locked();
|
||||||
}
|
}
|
||||||
} else if (type == MSG_AUDIO_STOP) {
|
} else if (type == MSG_AUDIO_STOP) {
|
||||||
/* 非当前/已 draining 流:仅清客户端绑定 */
|
/* 非当前/已 draining 流:仅清客户端绑定 */
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -370,26 +370,25 @@ int main(void)
|
|||||||
(unsigned long long)sent, (unsigned long long)(sent / fs5), probe_ok, probe_n);
|
(unsigned long long)sent, (unsigned long long)(sent / fs5), probe_ok, probe_n);
|
||||||
check(probe_n >= 10 && probe_ok == probe_n, "listener/io alive during long feed");
|
check(probe_n >= 10 && probe_ok == probe_n, "listener/io alive during long feed");
|
||||||
do_ctrl(&l5, MSG_AUDIO_STOP, ackL5.stream_id);
|
do_ctrl(&l5, MSG_AUDIO_STOP, ackL5.stream_id);
|
||||||
char expect[64];
|
/* 排空证明改为「WAV 写满 + STATE 归零」双判据:流结束后 played 已由 session_clean 清零
|
||||||
snprintf(expect, sizeof expect, "played=%llu", (unsigned long long)total_frames);
|
* (会话回收契约),不再作排空判据;WAV size==44+fed 才是「帧无丢失」的 ground truth。 */
|
||||||
int drained = 0;
|
long wavsz5 = -1;
|
||||||
char q5[1024];
|
char q5[1024];
|
||||||
for (int i = 0; i < 300; i++) { /* 最多 30s 等排空 */
|
int clean5 = 0;
|
||||||
if (do_query(&l5, q5, sizeof q5) == 0 && strstr(q5, expect)) { drained = 1; break; }
|
for (int i = 0; i < 300; i++) { /* 最多 30s 等排空+回填 WAV 头 */
|
||||||
usleep(100000);
|
usleep(100000);
|
||||||
}
|
do_query(&l5, q5, sizeof q5);
|
||||||
check(drained, "long stream fully played (played == fed frames)");
|
|
||||||
if (drained)
|
|
||||||
check(strstr(q5, "underrun=0") != NULL && strstr(q5, "dropped=0") != NULL,
|
|
||||||
"no underrun/drop on long stream");
|
|
||||||
usleep(300000); /* 等 dev_close 回填 WAV 头 */
|
|
||||||
close(l5.fd);
|
|
||||||
{
|
|
||||||
FILE *f = fopen(TWAV, "rb");
|
FILE *f = fopen(TWAV, "rb");
|
||||||
long sz = -1;
|
if (f) { fseek(f, 0, SEEK_END); wavsz5 = ftell(f); fclose(f); }
|
||||||
if (f) { fseek(f, 0, SEEK_END); sz = ftell(f); fclose(f); }
|
if (wavsz5 == (long)(44 + total_bytes) && strstr(q5, "active=0") && strstr(q5, "played=0")) {
|
||||||
check(sz == (long)(44 + total_bytes), "long WAV size == 44 + fed bytes");
|
clean5 = 1; break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
check(clean5, "long stream fully played (WAV==fed bytes, STATE clean)");
|
||||||
|
check(strstr(q5, "underrun=0") != NULL && strstr(q5, "dropped=0") != NULL,
|
||||||
|
"no underrun/drop on long stream");
|
||||||
|
check(wavsz5 == (long)(44 + total_bytes), "long WAV size == 44 + fed bytes");
|
||||||
|
close(l5.fd);
|
||||||
|
|
||||||
/* ---- T4:tone 写 WAV(由 CLI 走完整 IPC 链路;mipsel/host CLI 共用) ---- */
|
/* ---- T4:tone 写 WAV(由 CLI 走完整 IPC 链路;mipsel/host CLI 共用) ---- */
|
||||||
printf("[T4 tone -> WAV]\n");
|
printf("[T4 tone -> WAV]\n");
|
||||||
@@ -572,6 +571,88 @@ int main(void)
|
|||||||
close(m8.fd);
|
close(m8.fd);
|
||||||
stop_audiod();
|
stop_audiod();
|
||||||
|
|
||||||
|
/* ---- T9:孤儿会话回收(客户端崩溃/被杀 → 会话完整回收 → STATE 归零)。
|
||||||
|
* 复现 ncm 被内核 OOM 杀(sig=9)场景:客户端子进程 OPEN + 写正弦(使 written/played/rms/peak/
|
||||||
|
* lvlseq 非零、流活跃),随即 SIGKILL 子进程(内核自动关 socket fd)——audiod 应检测 EOF 并完整
|
||||||
|
* 回收:active=0/sid=0/written=0/played=0/underrun=0/rms=peak=-inf/lvlseq=0,STATE 回干净无流基线。 ---- */
|
||||||
|
printf("[T9 orphan reclaim (client SIGKILL)]\n");
|
||||||
|
unlink(TSOCK); unlink(TWAV);
|
||||||
|
g_audiod_pid = start_audiod(); /* file sink */
|
||||||
|
usleep(400000);
|
||||||
|
{
|
||||||
|
int p9[2];
|
||||||
|
if (pipe(p9) == 0) {
|
||||||
|
pid_t kid = fork();
|
||||||
|
if (kid == 0) {
|
||||||
|
/* 子进程 = 客户端:OPEN + 写足够正弦(≥2 窗口,使电平表结算)→ 通知父进程后挂死 */
|
||||||
|
close(p9[0]);
|
||||||
|
setenv("MEOW_AUDIO_SOCK", TSOCK, 1);
|
||||||
|
struct cli c9;
|
||||||
|
if (cli_connect(&c9) != 0) _exit(2);
|
||||||
|
m_audio_open_ack_t ack9;
|
||||||
|
if (do_open(&c9, 44100, 2, AUDIO_FMT_S16, &ack9) != 0 || ack9.err != MEOW_OK) _exit(3);
|
||||||
|
uint8_t pc9[4096];
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
size_t w9 = gen_sine(pc9, sizeof pc9, AUDIO_FMT_S16, 1000, 440, 44100);
|
||||||
|
if (do_write(&c9, pc9, (uint32_t)w9) != 0) _exit(4);
|
||||||
|
}
|
||||||
|
char ok = 1;
|
||||||
|
if (write(p9[1], &ok, 1) != 1) _exit(5);
|
||||||
|
for (;;) pause(); /* 等父进程 SIGKILL(模拟 OOM sig=9) */
|
||||||
|
}
|
||||||
|
close(p9[1]);
|
||||||
|
char ok = 0;
|
||||||
|
int got = (int)read(p9[0], &ok, 1); /* 等子进程写完 */
|
||||||
|
close(p9[0]);
|
||||||
|
check(got == 1 && ok == 1, "child client wrote audio (stream active)");
|
||||||
|
/* pre-death:流活跃 + 电平表已污染(证明待回收的会话有非零统计) */
|
||||||
|
int pre9 = 0;
|
||||||
|
char q9b[1024] = "";
|
||||||
|
for (int i = 0; i < 20; i++) { /* 最多 2s 等电平窗口结算 */
|
||||||
|
struct cli q9c;
|
||||||
|
if (cli_connect(&q9c) != 0) { usleep(100000); continue; }
|
||||||
|
if (do_query(&q9c, q9b, sizeof q9b) == 0) {
|
||||||
|
int rms9 = atoi(strstr(q9b, "rms=") + 4);
|
||||||
|
int lvl9 = atoi(strstr(q9b, "lvlseq=") + 7);
|
||||||
|
if (strstr(q9b, "active=1") && rms9 > -990 && lvl9 > 0) { pre9 = 1; close(q9c.fd); break; }
|
||||||
|
}
|
||||||
|
close(q9c.fd);
|
||||||
|
usleep(100000);
|
||||||
|
}
|
||||||
|
printf(" pre-crash: %s", q9b);
|
||||||
|
check(pre9, "stream active & meter populated before crash");
|
||||||
|
/* 模拟崩溃:SIGKILL 子进程(内核关 socket fd → audiod poll 到 EOF → 回收) */
|
||||||
|
kill(kid, SIGKILL);
|
||||||
|
waitpid(kid, NULL, 0);
|
||||||
|
/* post-death:轮询直到 STATE 完全归零(排空后 session_clean) */
|
||||||
|
int clean9 = 0;
|
||||||
|
char q9a[1024] = "";
|
||||||
|
for (int i = 0; i < 40; i++) { /* 最多 4s */
|
||||||
|
usleep(100000);
|
||||||
|
struct cli q9c;
|
||||||
|
if (cli_connect(&q9c) != 0) continue;
|
||||||
|
if (do_query(&q9c, q9a, sizeof q9a) == 0) {
|
||||||
|
int rr = atoi(strstr(q9a, "rms=") + 4);
|
||||||
|
int pp = atoi(strstr(q9a, "peak=") + 5);
|
||||||
|
int lv = atoi(strstr(q9a, "lvlseq=") + 7);
|
||||||
|
if (strstr(q9a, "active=0") && strstr(q9a, "sid=0") &&
|
||||||
|
strstr(q9a, "written=0") && strstr(q9a, "played=0") &&
|
||||||
|
strstr(q9a, "underrun=0") && rr <= -990 && pp <= -990 && lv == 0) {
|
||||||
|
clean9 = 1; close(q9c.fd); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(q9c.fd);
|
||||||
|
}
|
||||||
|
printf(" post-crash: %s", q9a);
|
||||||
|
check(clean9, "SIGKILL orphan -> STATE fully clean (sid/written/played/rms/peak/lvlseq zeroed)");
|
||||||
|
check(strstr(q9a, "active=0") != NULL && strstr(q9a, "app=-") != NULL,
|
||||||
|
"orphan query shows no-stream baseline");
|
||||||
|
} else {
|
||||||
|
check(0, "pipe for T9 child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stop_audiod();
|
||||||
|
|
||||||
printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail);
|
printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail);
|
||||||
return g_fail ? 1 : 0;
|
return g_fail ? 1 : 0;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user