根因(2026-11-04 真机 dev.player.ncm pid=7520): - bg_page.c bgm_app_meta 把 app.json 的 framework 字段映射进 bgmgr_row.framework,而 bgmgr_can_kill 把它当「系统/框架 App 禁止 kill」。但 framework=1 在 MeowOS 里意为「用 meow_ui.a 的 SDK 框架 App」(docs/APP_DEV_GUIDE.md §9,推荐值,真机 4 个 App 全写 1) → 后台页每行「杀」按钮被置灰,bgm_row_tap 命中后直接 return 1、不调 meow_bg_terminate; 「一键清除后台」同理 0 行可杀。整条链路无日志,故只能靠翻 app.json 才定位(全部设备日志里 "后台管理页终止后台App" 出现 0 次 = 4 次进页从未真正杀过)。 - 面板 killfg 正常是因为走 am kill → amsd → MSG_AMS_KILL,无 framework 门控;但它其实靠 amsd 1200ms×2 超时兜底 kill(pid,SIGTERM) 才杀成:util.c 原本在无前台 App 会话时只回 ACK -2((uint32_t)MEOW_ERR_NOENT),ACK 及时到达时 amsd 判「launcher refused」完全不杀 (ams_20260915_031339.log 实证),ACK 迟到才侥幸成功(2026-11-04 真机)。 修复: - meow_bgmgr: 门控位 framework → protected_app(系统级受保护标记;当前无 App 置位,launcher 自身组件本就不在 App 表/后台表内)。bg_page 不再把 a->framework 送进门控。 - bg_page: 行副文本改存 ctx 持久缓冲(原 rbuf[].sub 指向 bgm_rows 栈帧 → 悬垂,副标题乱码); 门控拒绝/未命中后台表各留一行 logmsg,「点了没反应」从此可查。 - util.c MSG_AMS_KILL: 先查后台保活表 → meow_bg_terminate 就地走真退序列(与页面「杀」按钮 同一通道,故 am kill 与页面按钮行为对称),无前台会话也能杀;否则仍走 pending / ACK -2。 - amsd cmd_kill/cmd_start: ipc 失败原因细分(connect failed / timeout, no reply / ...),日志 不再笼统写 "launcher offline"(响应串保留该字样,MeowPanel api_ams_ctl 依此映射 503)。 - meow_bgplay: meow_bg_terminate 注释/日志写明「后台管理页 / am kill」双入口同通道。 测试(宿主 gcc,产物在 $TMPDIR): - test_bgpage 40 checks:新增 framework=1 仍判可杀且按钮真 terminate(本 bug 回归)、 受保护行按钮不杀但留诊断日志、sub 持久化 + 栈抖动不变(悬垂回归)。 - test_bgmgr 30 checks:protected_app 门控矩阵 + framework=1 可杀回归。 - test_bgplay 92 PASS:新增 4b meow_bg_terminate 全链路(受理→term_at_ms→tick SIGTERM→ on_bg_dead→清槽;空/不在表分别 INVAL/NOENT);TEST_SVC_PATH 改取 $TMPDIR (Termux 无 /tmp,原 fputs(NULL) 直接 abort)。 - amsd 宿主集成(fake launcher 桩,4 例):ACK 0→terminate 且不盲杀;ACK -2→refused 不盲杀; 无 launcher→direct SIGTERM (connect failed);挂起不回→direct SIGTERM (timeout, no reply)。 0warning:util.c 与 main 同告警逐行一致(仅 SDK 头既有 typedef 重定义),其余文件干净。
525 lines
23 KiB
C
525 lines
23 KiB
C
/* test_bgplay.c — 宿主单测:meow_bgplay 后台播放保活 + 无头后台服务状态机(M4)。
|
||
*
|
||
* 验证:
|
||
* 1) meow_bg_should_keep 判定矩阵(background_audio × media_playing);
|
||
* 2) 后台播放保活全链路(真 fork 子进程):META PLAYING → should_keep=1 → bg_add →
|
||
* 进程存活 + PLAYCTL 经会话 fd 往返(后台化不断连)→ take 恢复 → META STOPPED →
|
||
* should_keep=0(停播后再退出=真退);
|
||
* 3) 后台 App 崩溃照收:子进程被杀 → bg_tick WNOHANG 回收 → on_bg_dead + 清槽;
|
||
* 4) 卡拔例外:on_card=1 + card_present 假 0 → tick SIGTERM 回收(防 SIGBUS);
|
||
* 4b) meow_bg_terminate(后台管理页「杀」按钮 / `am kill` 共用通道):受理置 term_at_ms →
|
||
* tick SIGTERM 回收 + on_bg_dead;不在表/空 id 分别 NOENT/INVAL;
|
||
* 4b) 批①+宽限:未声明(keepalive=0)媒体不活跃超 MEOW_BG_GRACE_MS 回收(含恰等边界不回收);
|
||
* 声明常驻(keepalive=1)或媒体活跃(META PLAYING)超 grace 不清;
|
||
* 5) bg 表满(MEOW_BG_MAX)→ LRU 回收最久未激活的未声明槽新增成功;全为声明常驻 → MEOW_ERR_NOMEM
|
||
* (不回收声明常驻);pid<=0 → MEOW_ERR_INVAL;重复 add → MEOW_ERR_EXISTS;
|
||
* 6) 服务注册:exec 不可执行 → NOENT;同名在跑 → EXISTS;
|
||
* 7) 退避重启 1s/2s + max_restart=2 熔断(假时钟注入):清通知回调 + 注册连接收到
|
||
* MSG_SYS{SVC_DEAD=2} + circuit 后不再拉起;重注册复位熔断重新拉起;
|
||
* 8) MSG_SERVICE_STOP:SIGTERM + stopping,不触发重启,槽释放;
|
||
* 9) kill_all:bg+svc 全回收 + 注册连接收到 MSG_SYS{SHUTDOWN=1}。
|
||
* 只依赖 meow_bgplay.c + meow_media.c + meow_ipc.h(纯 C),宿主 gcc 直编:
|
||
* gcc -Wall -Wextra -std=gnu99 test_bgplay.c meow_bgplay.c meow_media.c -o test_bgplay
|
||
*/
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdint.h>
|
||
#include <signal.h>
|
||
#include <unistd.h>
|
||
#include <fcntl.h>
|
||
#include <poll.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/wait.h>
|
||
#include "meow_bgplay.h"
|
||
#include "meow_media.h"
|
||
#include "meow_ipc.h"
|
||
|
||
static int g_fails = 0;
|
||
#define CHECK(cond, msg) do { \
|
||
if (cond) printf("PASS: %s\n", msg); \
|
||
else { printf("FAIL: %s\n", msg); g_fails++; } } while (0)
|
||
|
||
/* ---- 假件(ops 注入)---- */
|
||
static uint64_t g_ms = 100000; /* 假时钟(退避断言用,手动推进) */
|
||
static int g_card = 1; /* 卡在位 */
|
||
static int g_bg_dead = 0; /* on_bg_dead 计数 */
|
||
static int g_bg_dead_status = 0;
|
||
static char g_bg_dead_app[MEOW_APP_ID_MAX];
|
||
static int g_notif_cleared = 0; /* notif_clear_app 计数 */
|
||
static char g_notif_cleared_app[MEOW_APP_ID_MAX];
|
||
|
||
static uint64_t fake_now_ms(void) { return g_ms; }
|
||
static int fake_card(void) { return g_card; }
|
||
static void fake_bg_dead(const char *app_id, pid_t pid, int status)
|
||
{
|
||
(void)pid;
|
||
g_bg_dead++;
|
||
g_bg_dead_status = status;
|
||
snprintf(g_bg_dead_app, sizeof g_bg_dead_app, "%s", app_id);
|
||
}
|
||
static int fake_notif_clear(const char *app_id)
|
||
{
|
||
g_notif_cleared++;
|
||
snprintf(g_notif_cleared_app, sizeof g_notif_cleared_app, "%s", app_id);
|
||
return 1;
|
||
}
|
||
/* send_frame:整帧写到 fd(socketpair 一端),对端读回校验。 */
|
||
static size_t fake_send_frame(int fd, uint16_t type, uint32_t req_id,
|
||
const void *payload, uint32_t len)
|
||
{
|
||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||
uint32_t magic = MEOW_IPC_MAGIC, rid = req_id, l = len;
|
||
uint16_t ver = MEOW_IPC_VERSION, t = type;
|
||
size_t off = 0;
|
||
|
||
memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2);
|
||
memcpy(hdr + 8, &rid, 4); memcpy(hdr + 12, &l, 4);
|
||
if (write(fd, hdr, sizeof hdr) != (ssize_t)sizeof hdr) return 0;
|
||
off = sizeof hdr;
|
||
if (len && payload && write(fd, payload, len) > 0) off += len;
|
||
return off;
|
||
}
|
||
/* media ops.send 同 fake_send_frame(PLAYCTL 下发校验用) */
|
||
|
||
/* 从 socketpair 读一帧(poll 500ms 防挂)。返回负载字节数,<0=超时/失败。 */
|
||
static int read_frame(int fd, uint16_t *type, void *pay, int paycap)
|
||
{
|
||
struct pollfd pf = { .fd = fd, .events = POLLIN, .revents = 0 };
|
||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||
uint32_t len;
|
||
|
||
if (poll(&pf, 1, 500) <= 0) return -1;
|
||
if (read(fd, hdr, sizeof hdr) != (ssize_t)sizeof hdr) return -1;
|
||
memcpy(type, hdr + 6, 2);
|
||
memcpy(&len, hdr + 12, 4);
|
||
if (len > (uint32_t)paycap) return -1;
|
||
if (len && read(fd, pay, len) != (ssize_t)len) return -1;
|
||
return (int)len;
|
||
}
|
||
|
||
/* fork 一个长眠子进程(300s),测试末统一 SIGKILL 清理。 */
|
||
static pid_t spawn_sleeper(void)
|
||
{
|
||
pid_t pid = fork();
|
||
if (pid == 0) { sleep(300); _exit(0); }
|
||
return pid;
|
||
}
|
||
|
||
/* 泵 tick 直到 cond 或超时(~2s):子进程死亡到 WNOHANG 可见有调度延迟。 */
|
||
static int pump_bg_until(int (*cond)(void *), void *ctx)
|
||
{
|
||
for (int i = 0; i < 100; i++) {
|
||
meow_bg_tick();
|
||
if (cond(ctx)) return 1;
|
||
usleep(20000);
|
||
}
|
||
return 0;
|
||
}
|
||
static int pump_svc_until(int (*cond)(void *), void *ctx)
|
||
{
|
||
for (int i = 0; i < 100; i++) {
|
||
meow_svc_tick();
|
||
if (cond(ctx)) return 1;
|
||
usleep(20000);
|
||
}
|
||
return 0;
|
||
}
|
||
static int cond_bg_dead(void *ctx) { (void)ctx; return g_bg_dead > 0; }
|
||
static int cond_svc_reaped(void *ctx)
|
||
{
|
||
const meow_svc_rt_t *s;
|
||
(void)ctx;
|
||
s = meow_svc_find("dev.test.svc", "daemon");
|
||
return s && s->pid == 0;
|
||
}
|
||
static int cond_svc_gone(void *ctx)
|
||
{
|
||
(void)ctx;
|
||
return meow_svc_find("dev.test.svc", "daemon") == NULL;
|
||
}
|
||
|
||
/* 服务测试用长命可执行:shell 脚本 exec sleep 300(svc_spawn 内部 execve 走 shebang)。
|
||
* 路径取 $TMPDIR(Termux/Android 主机没有 /tmp),缺省回退 /tmp。 */
|
||
static char g_svc_path[256];
|
||
#define TEST_SVC_PATH g_svc_path
|
||
static void write_test_svc(void)
|
||
{
|
||
const char *tmp = getenv("TMPDIR");
|
||
if (!tmp || !tmp[0]) tmp = "/tmp";
|
||
snprintf(g_svc_path, sizeof g_svc_path, "%s/meow_bgplay_test_svc.sh", tmp);
|
||
FILE *f = fopen(TEST_SVC_PATH, "w");
|
||
if (!f) { printf("FAIL: 无法创建测试脚本 %s\n", TEST_SVC_PATH); exit(1); }
|
||
fputs("#!/bin/sh\nexec sleep 300\n", f);
|
||
fclose(f);
|
||
chmod(TEST_SVC_PATH, 0755);
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
meow_bgplay_ops ops;
|
||
meow_media_ops mops;
|
||
|
||
memset(&ops, 0, sizeof ops);
|
||
ops.now_ms = fake_now_ms;
|
||
ops.card_present = fake_card;
|
||
ops.on_bg_dead = fake_bg_dead;
|
||
ops.media_resident = meow_media_app_resident; /* 批①+宽限:媒体活跃判定(宽限回收/槽满LRU) */
|
||
ops.notif_clear_app = fake_notif_clear;
|
||
ops.send_frame = fake_send_frame;
|
||
ops.log_dir = NULL;
|
||
meow_bgplay_init(&ops);
|
||
|
||
memset(&mops, 0, sizeof mops);
|
||
mops.send = fake_send_frame;
|
||
meow_media_init(&mops);
|
||
|
||
/* ---- 1) should_keep 判定矩阵(批①三参:bg_audio, media_resident, keepalive) ---- */
|
||
CHECK(meow_bg_should_keep(0, 0, 0) == 0, "should_keep(未声明,未活跃,非常驻)=0");
|
||
CHECK(meow_bg_should_keep(1, 0, 0) == 0, "should_keep(声明bg_audio,无会话)=0");
|
||
CHECK(meow_bg_should_keep(0, 1, 0) == 0, "should_keep(未声明bg_audio,活跃)=0");
|
||
CHECK(meow_bg_should_keep(1, 1, 0) == 1, "should_keep(bg_audio+活跃)=1");
|
||
CHECK(meow_bg_should_keep(0, 0, 1) == 1, "should_keep(keepalive恒保)=1");
|
||
CHECK(meow_bg_should_keep(0, 1, 1) == 1, "should_keep(keepalive恒保,忽略media)=1");
|
||
CHECK(meow_bg_should_keep(1, 0, 1) == 1, "should_keep(keepalive恒保,无会话)=1");
|
||
CHECK(meow_bg_should_keep(1, 0, 0) == 0, "should_keep(bg_audio+resident=0 真退)=0");
|
||
|
||
/* ---- 2) 后台播放保活全链路 ---- */
|
||
int sp[2];
|
||
CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == 0, "socketpair 建立");
|
||
{
|
||
m_meta_t meta;
|
||
memset(&meta, 0, sizeof meta);
|
||
snprintf(meta.app_id, sizeof meta.app_id, "dev.test.bg1");
|
||
meta.state = MEOW_MEDIA_PLAYING;
|
||
meta.dur_ms = 200000;
|
||
CHECK(meow_media_meta(&meta, sp[0]) == 0, "META 上报 PLAYING 注册媒体会话");
|
||
}
|
||
CHECK(meow_media_app_playing("dev.test.bg1") == 1, "app_playing(bg1)=1");
|
||
CHECK(meow_media_app_resident("dev.test.bg1") == 1, "app_resident(bg1)=1");
|
||
CHECK(meow_bg_should_keep(1, meow_media_app_resident("dev.test.bg1"), 0) == 1,
|
||
"播放中退出 → 应保活");
|
||
|
||
pid_t kid = spawn_sleeper();
|
||
CHECK(kid > 0, "fork 假 App 子进程");
|
||
CHECK(meow_bg_add("dev.test.bg1", kid, sp[0], 0, 0) == 0, "bg_add 转后台保活");
|
||
CHECK(meow_bg_is_backgrounded("dev.test.bg1") == 1, "is_backgrounded=1");
|
||
CHECK(meow_bg_count() == 1, "bg count=1");
|
||
{
|
||
const meow_bg_entry_t *be = meow_bg_at(0);
|
||
CHECK(be && be->bg_since_ms > 0, "bg 槽记 bg_since_ms(转后台时刻)");
|
||
CHECK(be && be->keepalive == 0, "bg_audio 类 keepalive=0");
|
||
}
|
||
CHECK(kill(kid, 0) == 0, "后台化后进程仍存活(未 SIGTERM/SIGSTOP)");
|
||
/* PLAYCTL 经会话 fd 往返(后台化不断连:进程/fd 均保留) */
|
||
CHECK(meow_media_playctl_to("dev.test.bg1", 6, 0) == 0, "PLAYCTL 下发到后台 App 会话");
|
||
{
|
||
uint16_t t = 0;
|
||
m_playctl_t pc;
|
||
int n = read_frame(sp[1], &t, &pc, sizeof pc);
|
||
CHECK(n == (int)sizeof pc && t == MSG_PLAYCTL && pc.cmd == 6,
|
||
"对端收到 MSG_PLAYCTL cmd=6(控制通道后台不断连)");
|
||
}
|
||
{
|
||
int rfd = -1;
|
||
pid_t rpid = 0;
|
||
CHECK(meow_bg_take("dev.test.bg1", &rfd, &rpid) == 0 && rpid == kid && rfd == sp[0],
|
||
"bg_take 回前台(pid/fd 原样归还)");
|
||
}
|
||
CHECK(meow_bg_is_backgrounded("dev.test.bg1") == 0 && meow_bg_count() == 0,
|
||
"take 后清槽");
|
||
meow_media_clear("dev.test.bg1");
|
||
{
|
||
m_meta_t meta;
|
||
memset(&meta, 0, sizeof meta);
|
||
snprintf(meta.app_id, sizeof meta.app_id, "dev.test.bg1");
|
||
meta.state = MEOW_MEDIA_STOPPED;
|
||
meow_media_meta(&meta, sp[0]);
|
||
}
|
||
CHECK(meow_bg_should_keep(1, meow_media_app_resident("dev.test.bg1"), 0) == 0,
|
||
"停播后再退出 → should_keep=0(真退 SIGTERM)");
|
||
kill(kid, SIGKILL); waitpid(kid, NULL, 0);
|
||
close(sp[0]); close(sp[1]);
|
||
meow_media_clear("dev.test.bg1");
|
||
|
||
/* ---- 2b) keepalive 类(background_keepalive)入槽标记 ---- */
|
||
{
|
||
pid_t kk = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.keep", kk, -1, 0, 1) == 0, "bg_add(keepalive=1)");
|
||
const meow_bg_entry_t *bk = meow_bg_at(0);
|
||
CHECK(bk && bk->keepalive == 1, "keepalive 类槽 keepalive=1");
|
||
CHECK(bk && bk->bg_since_ms > 0, "keepalive 类槽 bg_since_ms>0");
|
||
meow_bg_take("dev.test.keep", NULL, NULL);
|
||
kill(kk, SIGKILL); waitpid(kk, NULL, 0);
|
||
CHECK(meow_bg_count() == 0, "keepalive 槽 take 后清槽");
|
||
}
|
||
|
||
/* ---- 3) 后台 App 崩溃照收 ---- */
|
||
g_bg_dead = 0;
|
||
kid = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.crash", kid, -1, 0, 0) == 0, "bg_add(crash)");
|
||
kill(kid, SIGKILL);
|
||
{
|
||
int ok = pump_bg_until(cond_bg_dead, NULL);
|
||
(void)ok;
|
||
}
|
||
CHECK(g_bg_dead == 1 && strcmp(g_bg_dead_app, "dev.test.crash") == 0 &&
|
||
WIFSIGNALED(g_bg_dead_status) && WTERMSIG(g_bg_dead_status) == SIGKILL,
|
||
"崩溃子进程被 tick 回收 + on_bg_dead(SIGKILL) 回调");
|
||
CHECK(meow_bg_count() == 0, "崩溃回收后清槽");
|
||
|
||
/* ---- 4) 卡拔例外:on_card=1 + 卡不在 → SIGTERM 回收 ---- */
|
||
g_bg_dead = 0;
|
||
kid = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.card", kid, -1, 1, 0) == 0, "bg_add(on_card=1)");
|
||
g_card = 0;
|
||
pump_bg_until(cond_bg_dead, NULL);
|
||
CHECK(g_bg_dead == 1 && strcmp(g_bg_dead_app, "dev.test.card") == 0,
|
||
"卡拔 → 卡上后台 App SIGTERM 回收(防 SIGBUS)");
|
||
g_card = 1;
|
||
|
||
/* ---- 4b) meow_bg_terminate:后台管理页「杀」按钮 / `am kill` 共用的终止通道 ---- */
|
||
{
|
||
g_bg_dead = 0;
|
||
pid_t tk = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.term", tk, -1, 0, 0) == 0, "bg_add(term)");
|
||
CHECK(meow_bg_terminate("") == MEOW_ERR_INVAL, "terminate 空 app_id → INVAL");
|
||
CHECK(meow_bg_terminate("dev.test.nothere") == MEOW_ERR_NOENT, "terminate 不在后台表 → NOENT");
|
||
CHECK(meow_bg_terminate("dev.test.term") == 0, "terminate 受理(在后台表且 pid>0)");
|
||
{
|
||
int found = 0;
|
||
for (int i = 0; i < MEOW_BG_MAX; i++) {
|
||
const meow_bg_entry_t *be = meow_bg_at(i);
|
||
if (be && !strcmp(be->app_id, "dev.test.term")) {
|
||
CHECK(be->term_at_ms > 0, "terminate 置 term_at_ms(bgm_enumerate 据此隐藏行)");
|
||
found = 1;
|
||
}
|
||
}
|
||
CHECK(found == 1, "terminate 后槽仍在(等 tick 回收)");
|
||
}
|
||
pump_bg_until(cond_bg_dead, NULL);
|
||
CHECK(g_bg_dead == 1 && strcmp(g_bg_dead_app, "dev.test.term") == 0,
|
||
"terminate → tick SIGTERM 回收 + on_bg_dead 上报 amsd");
|
||
CHECK(meow_bg_count() == 0, "terminate 回收后清槽");
|
||
}
|
||
|
||
/* ---- 5) 表满:LRU 回收最久未激活的未声明槽 / 全常驻满→NOMEM / 重复 / 参数 ---- */
|
||
{
|
||
pid_t k[MEOW_BG_MAX];
|
||
char id[MEOW_APP_ID_MAX];
|
||
int i;
|
||
pid_t gextra = 0;
|
||
for (i = 0; i < MEOW_BG_MAX; i++) {
|
||
k[i] = spawn_sleeper();
|
||
snprintf(id, sizeof id, "dev.test.lru%d", i);
|
||
CHECK(meow_bg_add(id, k[i], -1, 0, 0) == 0, "lru 填表(未声明 keepalive=0)");
|
||
}
|
||
/* 5.1 表满 + 全未声明 → 新增 LRU 回收最旧(lru0,bg_since_ms 最小),新 App 入表成功 */
|
||
{
|
||
pid_t e2 = spawn_sleeper();
|
||
gextra = e2;
|
||
CHECK(meow_bg_add("dev.test.lrunew", e2, -1, 0, 0) == 0,
|
||
"表满全未声明 → LRU 回收最旧槽并成功新增");
|
||
CHECK(meow_bg_count() == MEOW_BG_MAX, "回收后仍满 4 槽");
|
||
CHECK(meow_bg_is_backgrounded("dev.test.lrunew") == 1, "新增 App 在后台表");
|
||
CHECK(meow_bg_is_backgrounded("dev.test.lru0") == 0, "最久未激活未声明 lru0 被回收摘表");
|
||
CHECK(kill(k[0], 0) < 0, "lru0 进程已终止(SIGTERM 真退回收)");
|
||
}
|
||
/* 5.2 重复 add → EXISTS;pid<=0 → INVAL(lru0 已回收,表内为 lrunew/lru1/lru2/lru3) */
|
||
CHECK(meow_bg_add("dev.test.lrunew", 999999, -1, 0, 0) == MEOW_ERR_EXISTS, "重复 app_id → EXISTS");
|
||
CHECK(meow_bg_add("dev.test.lrunew", 0, -1, 0, 0) == MEOW_ERR_INVAL, "pid<=0 → INVAL");
|
||
/* 5.3 清空剩余(take + 杀进程防泄漏) */
|
||
for (i = 1; i < MEOW_BG_MAX; i++) {
|
||
snprintf(id, sizeof id, "dev.test.lru%d", i);
|
||
meow_bg_take(id, NULL, NULL);
|
||
kill(k[i], SIGKILL); waitpid(k[i], NULL, 0);
|
||
}
|
||
meow_bg_take("dev.test.lrunew", NULL, NULL);
|
||
kill(gextra, SIGKILL); waitpid(gextra, NULL, 0);
|
||
CHECK(meow_bg_count() == 0, "清空后 bg 表空(未声明)");
|
||
}
|
||
|
||
/* ---- 5b) 表满 + 全为声明常驻(keepalive=1)→ 拒绝新入 NOMEM(不回收声明常驻,保安全) ---- */
|
||
{
|
||
pid_t d[MEOW_BG_MAX];
|
||
char id[MEOW_APP_ID_MAX];
|
||
int i;
|
||
for (i = 0; i < MEOW_BG_MAX; i++) {
|
||
d[i] = spawn_sleeper();
|
||
snprintf(id, sizeof id, "dev.test.dec%d", i);
|
||
CHECK(meow_bg_add(id, d[i], -1, 0, 1) == 0, "声明常驻填表(keepalive=1)");
|
||
}
|
||
{
|
||
pid_t e2 = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.decnew", e2, -1, 0, 0) == MEOW_ERR_NOMEM,
|
||
"全声明常驻满 → 新增 NOMEM(不回退真退,保安全)");
|
||
kill(e2, SIGKILL); waitpid(e2, NULL, 0); /* 未入表,就地清理 */
|
||
}
|
||
for (i = 0; i < MEOW_BG_MAX; i++) {
|
||
snprintf(id, sizeof id, "dev.test.dec%d", i);
|
||
meow_bg_take(id, NULL, NULL);
|
||
kill(d[i], SIGKILL); waitpid(d[i], NULL, 0);
|
||
}
|
||
CHECK(meow_bg_count() == 0, "声明常驻清空后 bg 表空");
|
||
}
|
||
|
||
/* ================= 批①+宽限:宽限超时回收 / 声明常驻与媒体活跃不清 ================= */
|
||
g_ms = 100000; /* 本组从干净时间基准起(后续 svc 段用相对 g_ms,不受影响) */
|
||
|
||
/* 5c) 未声明 + 媒体不活跃:grace 内保活;恰等 MEOW_BG_GRACE_MS 不回收(严格 >);超界回收 */
|
||
{
|
||
pid_t s1 = spawn_sleeper();
|
||
g_bg_dead = 0;
|
||
CHECK(meow_bg_add("dev.test.grace1", s1, -1, 0, 0) == 0, "grace1 入表(未声明 keepalive=0)");
|
||
g_ms = 100000u + MEOW_BG_GRACE_MS; /* 恰好等于宽限(非严格大于) */
|
||
meow_bg_tick();
|
||
CHECK(meow_bg_is_backgrounded("dev.test.grace1") == 1 && g_bg_dead == 0,
|
||
"grace 边界:恰等 MEOW_BG_GRACE_MS 不回收(严格 > 才回收)");
|
||
g_ms = 100000u + MEOW_BG_GRACE_MS + 1; /* 超界 1ms */
|
||
pump_bg_until(cond_bg_dead, NULL);
|
||
CHECK(meow_bg_is_backgrounded("dev.test.grace1") == 0 && meow_bg_count() == 0,
|
||
"超 grace 未再启动 → 回收摘表");
|
||
CHECK(g_bg_dead == 1, "宽限回收触发 on_bg_dead(通知 amsd)+ 进程经 SIGTERM 退出");
|
||
}
|
||
|
||
/* 5d) 声明常驻(keepalive=1)超 grace 不清(无限期) */
|
||
{
|
||
pid_t k1 = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.gracekeep", k1, -1, 0, 1) == 0, "gracekeep 入表(keepalive=1)");
|
||
g_ms = 100000u + MEOW_BG_GRACE_MS + 100000;
|
||
meow_bg_tick();
|
||
CHECK(meow_bg_is_backgrounded("dev.test.gracekeep") == 1,
|
||
"声明常驻 keepalive=1 超 grace 不清(无限期)");
|
||
meow_bg_take("dev.test.gracekeep", NULL, NULL);
|
||
kill(k1, SIGKILL); waitpid(k1, NULL, 0);
|
||
}
|
||
|
||
/* 5e) 媒体活跃(META PLAYING,keepalive=0)超 grace 不清(resident 保护) */
|
||
{
|
||
int sp2[2];
|
||
pid_t m1;
|
||
CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sp2) == 0, "grace media socketpair");
|
||
{
|
||
m_meta_t meta;
|
||
memset(&meta, 0, sizeof meta);
|
||
snprintf(meta.app_id, sizeof meta.app_id, "dev.test.gracemedia");
|
||
meta.state = MEOW_MEDIA_PLAYING;
|
||
meow_media_meta(&meta, sp2[0]);
|
||
}
|
||
m1 = spawn_sleeper();
|
||
CHECK(meow_bg_add("dev.test.gracemedia", m1, -1, 0, 0) == 0,
|
||
"gracemedia 入表(keepalive=0 但媒体活跃)");
|
||
g_ms = 100000u + MEOW_BG_GRACE_MS + 100000;
|
||
meow_bg_tick();
|
||
CHECK(meow_media_app_resident("dev.test.gracemedia") == 1, "gracemedia media_resident=1");
|
||
CHECK(meow_bg_is_backgrounded("dev.test.gracemedia") == 1,
|
||
"媒体活跃超 grace 不清(resident 保护)");
|
||
meow_bg_take("dev.test.gracemedia", NULL, NULL);
|
||
kill(m1, SIGKILL); waitpid(m1, NULL, 0);
|
||
meow_media_clear("dev.test.gracemedia");
|
||
close(sp2[0]); close(sp2[1]);
|
||
}
|
||
|
||
/* ================= 无头后台服务 ================= */
|
||
write_test_svc();
|
||
|
||
/* ---- 6) 注册校验 ---- */
|
||
CHECK(meow_svc_register("dev.test.svc", "daemon", "/nonexistent/x", 1, 2, 60000, 0, -1)
|
||
== MEOW_ERR_NOENT, "exec 不可执行 → NOENT(不留尸槽)");
|
||
CHECK(meow_svc_find("dev.test.svc", "daemon") == NULL, "NOENT 后无槽残留");
|
||
|
||
int sp_svc[2];
|
||
CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sp_svc) == 0, "svc socketpair 建立");
|
||
int pid1 = meow_svc_register("dev.test.svc", "daemon", TEST_SVC_PATH,
|
||
1, 2, 60000, 0, sp_svc[0]);
|
||
CHECK(pid1 > 0, "register 拉起服务");
|
||
CHECK(meow_svc_register("dev.test.svc", "daemon", TEST_SVC_PATH, 1, 2, 60000, 0, sp_svc[0])
|
||
== MEOW_ERR_EXISTS, "同名服务在跑 → EXISTS");
|
||
|
||
/* ---- 7) 退避 1s/2s + max=2 熔断 ---- */
|
||
const meow_svc_rt_t *sl;
|
||
kill(pid1, SIGKILL);
|
||
pump_svc_until(cond_svc_reaped, NULL);
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid == 0 && sl->restart_count == 1 &&
|
||
sl->next_retry_ms == g_ms + MEOW_SVC_BACKOFF_1,
|
||
"第 1 次死 → 1s 退避排程(restart_count=1)");
|
||
g_ms += MEOW_SVC_BACKOFF_1;
|
||
meow_svc_tick();
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid > 0 && sl->pid != pid1, "到点重启:新 pid");
|
||
|
||
pid_t pid2 = sl->pid;
|
||
kill(pid2, SIGKILL);
|
||
pump_svc_until(cond_svc_reaped, NULL);
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid == 0 && sl->restart_count == 2 &&
|
||
sl->next_retry_ms == g_ms + MEOW_SVC_BACKOFF_2,
|
||
"第 2 次死 → 2s 退避排程(restart_count=2)");
|
||
g_ms += MEOW_SVC_BACKOFF_2;
|
||
meow_svc_tick();
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid > 0, "第 2 次重启拉起");
|
||
|
||
g_notif_cleared = 0;
|
||
kill(sl->pid, SIGKILL);
|
||
pump_svc_until(cond_svc_reaped, NULL);
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid == 0 && sl->circuit == 1 && sl->restart_count == 3,
|
||
"第 3 次死超 max_restart=2 → 熔断 circuit=1");
|
||
CHECK(g_notif_cleared == 1 && strcmp(g_notif_cleared_app, "dev.test.svc") == 0,
|
||
"熔断清该 App 通知(notif_clear_app 回调)");
|
||
{
|
||
uint16_t t = 0;
|
||
m_sys_t m;
|
||
int n = read_frame(sp_svc[1], &t, &m, sizeof m);
|
||
CHECK(n == (int)sizeof m && t == MSG_SYS && m.event == MEOW_SYS_SVC_DEAD &&
|
||
strcmp(m.svc, "daemon") == 0,
|
||
"注册连接收到 MSG_SYS{SVC_DEAD=2} 熔断广播");
|
||
}
|
||
g_ms += 60000;
|
||
meow_svc_tick();
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->pid == 0, "熔断后不再拉起");
|
||
|
||
/* 重注册复位熔断 */
|
||
int pid4 = meow_svc_register("dev.test.svc", "daemon", TEST_SVC_PATH,
|
||
1, 2, 60000, 0, sp_svc[0]);
|
||
CHECK(pid4 > 0, "熔断槽重注册 → 复位重新拉起");
|
||
sl = meow_svc_find("dev.test.svc", "daemon");
|
||
CHECK(sl && sl->circuit == 0 && sl->restart_count == 0, "熔断复位(circuit/count 清零)");
|
||
|
||
/* ---- 8) SERVICE_STOP:不重启 ---- */
|
||
CHECK(meow_svc_stop("dev.test.svc", "daemon") == 0, "svc_stop 受理");
|
||
pump_svc_until(cond_svc_gone, NULL);
|
||
CHECK(meow_svc_find("dev.test.svc", "daemon") == NULL, "stop 后槽释放(不重启)");
|
||
CHECK(meow_svc_stop("dev.test.svc", "daemon") == MEOW_ERR_NOENT, "重复 stop → NOENT");
|
||
close(sp_svc[0]); close(sp_svc[1]);
|
||
|
||
/* ---- 9) kill_all:全回收 + SHUTDOWN 广播 ---- */
|
||
{
|
||
int sp2[2];
|
||
socketpair(AF_UNIX, SOCK_STREAM, 0, sp2);
|
||
int spid = meow_svc_register("dev.test.svc", "daemon", TEST_SVC_PATH,
|
||
1, 3, 60000, 0, sp2[0]);
|
||
pid_t bgk = spawn_sleeper();
|
||
meow_bg_add("dev.test.bg9", bgk, -1, 0, 0);
|
||
CHECK(spid > 0 && meow_bg_count() == 1, "kill_all 前置:1 svc + 1 bg");
|
||
meow_bgplay_kill_all();
|
||
CHECK(meow_bg_count() == 0 && meow_svc_find("dev.test.svc", "daemon") == NULL,
|
||
"kill_all 后 bg/svc 全回收");
|
||
CHECK(kill(spid, 0) < 0 && kill(bgk, 0) < 0, "子进程确已终止");
|
||
uint16_t t = 0;
|
||
m_sys_t m;
|
||
int n = read_frame(sp2[1], &t, &m, sizeof m);
|
||
CHECK(n == (int)sizeof m && t == MSG_SYS && m.event == MEOW_SYS_SHUTDOWN,
|
||
"注册连接收到 MSG_SYS{SHUTDOWN=1}(关机前回收广播)");
|
||
close(sp2[0]); close(sp2[1]);
|
||
}
|
||
|
||
unlink(TEST_SVC_PATH);
|
||
printf(g_fails ? "== FAILURES: %d ==\n" : "== ALL PASS ==\n", g_fails);
|
||
return g_fails ? 1 : 0;
|
||
}
|