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:
AskaEth committed 2026-09-02 09:32:48 +08:00
1 parent 19f7e59927
commit 121d30ec95
5 files changed
+135 -27

No files matched your search

+97 -16
View File
@@ -370,26 +370,25 @@ int main(void)
(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");
do_ctrl(&l5, MSG_AUDIO_STOP, ackL5.stream_id);
char expect[64];
snprintf(expect, sizeof expect, "played=%llu", (unsigned long long)total_frames);
int drained = 0;
/* 排空证明改为「WAV 写满 + STATE 归零」双判据:流结束后 played 已由 session_clean 清零
* (会话回收契约),不再作排空判据;WAV size==44+fed 才是「帧无丢失」的 ground truth。 */
long wavsz5 = -1;
char q5[1024];
for (int i = 0; i < 300; i++) { /* 最多 30s 等排空 */
if (do_query(&l5, q5, sizeof q5) == 0 && strstr(q5, expect)) { drained = 1; break; }
int clean5 = 0;
for (int i = 0; i < 300; i++) { /* 最多 30s 等排空+回填 WAV 头 */
usleep(100000);
}
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);
{
do_query(&l5, q5, sizeof q5);
FILE *f = fopen(TWAV, "rb");
long sz = -1;
if (f) { fseek(f, 0, SEEK_END); sz = ftell(f); fclose(f); }
check(sz == (long)(44 + total_bytes), "long WAV size == 44 + fed bytes");
if (f) { fseek(f, 0, SEEK_END); wavsz5 = ftell(f); fclose(f); }
if (wavsz5 == (long)(44 + total_bytes) && strstr(q5, "active=0") && strstr(q5, "played=0")) {
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 共用) ---- */
printf("[T4 tone -> WAV]\n");
@@ -572,6 +571,88 @@ int main(void)
close(m8.fd);
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);
return g_fail ? 1 : 0;
}