diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 1e15d5a..41c54c0 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -479,6 +479,7 @@ void meow_bgplay_launcher_init(void) ops.now_ms = NULL; /* 内部 CLOCK_MONOTONIC */ ops.card_present = sd_device_present; ops.on_bg_dead = bg_on_dead; + ops.media_resident = meow_media_app_resident; /* 批①+宽限:媒体活跃判定(宽限回收/槽满LRU) */ ops.on_svc_dead = NULL; /* 服务消亡日志 meow_bgplay 内已记 */ ops.notif_clear_app = bg_notif_clear_app; ops.send_frame = bg_send_frame; @@ -1964,22 +1965,29 @@ static int launch_app_chain(struct app *a, const char *chain) break; } if (meow_nav_exit_pending()) { - /* 批①后台保活:声明 background_keepalive → 恒保活;或声明 background_audio 且该 App - * 媒体会话活跃(PLAYING/PAUSED/BUFFER,见 meow_media_app_resident)→ 保活。进程+音频流 - * 保留转后台(UI 冻结走 §10.5 SDK present 自抑,**非 SIGSTOP**——SIGSTOP 会冻结同进程 - * 播放线程断供 PCM,§10.5 真机实证)。登记失败(槽满/已存在)回落下方真退序列。 - * 卡拔例外不适用保活(bg 表 tick 照杀)。 */ - if (meow_bg_should_keep(a->background_audio == '1', - meow_media_app_resident(a->id), - a->background_keepalive == '1') && - meow_bg_add(a->id, pid, out_fd, app_on_card, - a->background_keepalive == '1') == 0) { - logmsg("bg: app %s nav-exit -> 后台保活(活跃(播放/暂停/缓冲),pid=%d 保留,SDK present 自抑)", - a->name, (int)pid); + /* 批①+宽限后台保活:**全 App** nav-exit 入后台保活表(进程保留转后台,UI 冻结走 §10.5 + * SDK present 自抑,**非 SIGSTOP**——SIGSTOP 会冻结同进程播放线程断供 PCM,§10.5 实证)。 + * 分两类: + * - 声明常驻(background_keepalive=1)或媒体活跃(background_audio+PLAYING/PAUSED/BUFFER, + * 见 meow_media_app_resident)→ **无限期保活**(不清,除非用户手动/一键清); + * - 其余(未声明/媒体不活跃)→ **宽限期**(bg_grace_ms,默认30min)内保活,超时未再启动 + * 由 meow_bg_tick 走真退序列回收。 + * meow_bg_add 内含槽满 LRU 回收(最久未激活的未声明槽);全为声明常驻/媒体活跃 → NOMEM + * 回落下方真退序列(保安全)。kill 通道带 !a->framework 门控(批③)。 */ + int bg_resident = meow_media_app_resident(a->id); + int r = meow_bg_add(a->id, pid, out_fd, app_on_card, + a->background_keepalive == '1'); + if (r == 0 || r == MEOW_ERR_EXISTS) { /* 0=入表;EXISTS=本就驻留(幂等不重复登记不杀) */ + logmsg("bg: app %s nav-exit -> 后台保活(%s,pid=%d 保留,SDK present 自抑)", + a->name, + ((a->background_keepalive == '1') || bg_resident) ? + "声明常驻/媒体活跃(无限期)" : "宽限期(默认30min)", + (int)pid); to_background = 1; break; } - logmsg("nav: app %s MSG_NAV{EXIT} -> terminate, return to home", a->name); + logmsg("nav: app %s MSG_NAV{EXIT} -> terminate, return to home(后台表满且无未声明槽可回收)", + a->name); nav_clean_exit = 1; kill(pid, SIGTERM); usleep(300000); diff --git a/patch_v2/meow_launcherd/meow_bgplay.c b/patch_v2/meow_launcherd/meow_bgplay.c index ca7c038..2ce5548 100644 --- a/patch_v2/meow_launcherd/meow_bgplay.c +++ b/patch_v2/meow_launcherd/meow_bgplay.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -79,30 +80,100 @@ static meow_bg_entry_t *bg_find(const char *app_id) return NULL; } +/* 填一槽(add 与槽满 LRU 复用)。 */ +static void bg_fill(int i, const char *app_id, pid_t pid, int out_fd, int on_card, int keepalive) +{ + meow_bg_entry_t *e = &g_bg[i]; + + memset(e, 0, sizeof(*e)); + e->used = 1; + snprintf(e->app_id, sizeof(e->app_id), "%s", app_id); + e->pid = pid; + e->out_fd = out_fd; + e->on_card = on_card; + e->bg_since_ms = bg_now_ms(); /* 批①:转后台时刻(宽限/槽满 LRU 掐最久用的锚点) */ + e->keepalive = keepalive ? 1 : 0; /* 批①+宽限:1=声明常驻(无限期);0=宽限期类 */ + g_change_seq++; + bg_log("[bgplay] 转后台保活: %s pid=%d on_card=%d keepalive=%d", + app_id, (int)pid, on_card, e->keepalive); +} + +/* 真实单调毫秒(不随 ops.now_ms 注入的假时钟走)。槽满 LRU 回收需要真实的有界等超时—— + * 注入假时钟的宿主测试若用它算 deadline 则永不超时(假时钟不随 usleep 推进),故独立取真时钟。 */ +static uint64_t bg_real_now_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; +} + +/* 槽满 LRU 回收:找 bg_since_ms 最旧的**未声明且媒体不活跃**槽,走真退序列 SIGTERM→有界等→ + * SIGKILL→reap→on_bg_dead(通知 amsd + 清媒体会话)→ 摘表。返回被回收槽下标;-1=无可回收 + * (全为声明常驻/媒体活跃,按保安全拒绝新入)。 */ +static int bg_evict_lru(void) +{ + uint64_t oldest = UINT64_MAX; + int cand = -1; + int i; + + for (i = 0; i < MEOW_BG_MAX; i++) { + if (!g_bg[i].used) continue; + if (g_bg[i].keepalive) continue; /* 声明常驻不回收 */ + if (g_ops.media_resident && g_ops.media_resident(g_bg[i].app_id)) + continue; /* 媒体活跃不回收 */ + if (g_bg[i].bg_since_ms < oldest) { oldest = g_bg[i].bg_since_ms; cand = i; } + } + if (cand < 0) return -1; + + { + meow_bg_entry_t *e = &g_bg[cand]; + char id[MEOW_APP_ID_MAX]; + pid_t pid = e->pid; + int ofd = e->out_fd; + int st = 0; + uint64_t deadline; + + snprintf(id, sizeof(id), "%s", e->app_id); + bg_log("[bgplay] 后台表满 LRU 回收: %s pid=%d(最久未激活·未声明)", id, (int)pid); + kill(pid, SIGTERM); + deadline = bg_real_now_ms() + TERM_GRACE_MS; + for (;;) { + pid_t r = waitpid(pid, &st, WNOHANG); + if (r == pid) break; + if (r < 0 && errno != EINTR) break; /* 已回收/非子进程:不再等 */ + if (bg_real_now_ms() >= deadline) { + kill(pid, SIGKILL); + deadline = bg_real_now_ms() + 200u; /* SIGKILL 后非阻塞兜底,防 D-state 挂死 */ + while (bg_real_now_ms() < deadline) { + if (waitpid(pid, &st, WNOHANG) == pid) break; + usleep(10000); + } + break; + } + usleep(5000); + } + if (ofd >= 0) close(ofd); /* 被回收 App 已死,其 stdout 读端一并关 */ + if (g_ops.on_bg_dead) g_ops.on_bg_dead(id, pid, st); /* 通知 amsd + 清媒体会话 */ + g_change_seq++; + bg_log("[bgplay] LRU 回收完成: %s 槽释放", id); + memset(e, 0, sizeof(*e)); + } + return cand; +} + int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card, int keepalive) { int i; if (!app_id || !app_id[0] || pid <= 0) return MEOW_ERR_INVAL; if (bg_find(app_id)) return MEOW_ERR_EXISTS; - for (i = 0; i < MEOW_BG_MAX; i++) { - if (!g_bg[i].used) { - meow_bg_entry_t *e = &g_bg[i]; - - memset(e, 0, sizeof(*e)); - e->used = 1; - snprintf(e->app_id, sizeof(e->app_id), "%s", app_id); - e->pid = pid; - e->out_fd = out_fd; - e->on_card = on_card; - e->bg_since_ms = bg_now_ms(); /* 批①:转后台时刻 */ - e->keepalive = keepalive ? 1 : 0; /* 批①:常驻类型标记 */ - g_change_seq++; - bg_log("[bgplay] 转后台保活: %s pid=%d on_card=%d keepalive=%d", - app_id, (int)pid, on_card, e->keepalive); - return 0; - } - } + for (i = 0; i < MEOW_BG_MAX; i++) + if (!g_bg[i].used) { bg_fill(i, app_id, pid, out_fd, on_card, keepalive); return 0; } + /* 槽满:LRU 回收最久未激活的未声明槽;全为声明常驻/媒体活跃 → NOMEM(调用方回落真退)。 */ + if (bg_evict_lru() >= 0) + for (i = 0; i < MEOW_BG_MAX; i++) + if (!g_bg[i].used) { bg_fill(i, app_id, pid, out_fd, on_card, keepalive); return 0; } return MEOW_ERR_NOMEM; } @@ -158,25 +229,39 @@ void meow_bg_tick(void) if (!e->used) continue; - /* 卡拔例外:二进制在卡上 → SIGTERM(防 SIGBUS),500ms 未死 SIGKILL */ - if (e->on_card && !bg_card_present()) { - if (e->term_at_ms == 0) { - bg_log("[bgplay] 卡拔出,终止卡上后台 App: %s pid=%d", - e->app_id, (int)e->pid); + /* 触发终止(SIGTERM→SIGKILL 真退序列)的两类条件,仅当尚未进入终止序列(term_at_ms==0): + * a) 卡拔例外:on_card 且卡不在(防 SIGBUS); + * b) 批①+宽限 超时回收:未声明(keepalive=0)且媒体不活跃(media_resident=0),转后台 + * 超 MEOW_BG_GRACE_MS 未再启动 → 回收。声明常驻(keepalive=1)/媒体活跃**不清**。 */ + if (e->term_at_ms == 0) { + int grace = (e->keepalive == 0 && + !(g_ops.media_resident ? g_ops.media_resident(e->app_id) : 0) && + now - e->bg_since_ms > (uint64_t)MEOW_BG_GRACE_MS); + if ((e->on_card && !bg_card_present()) || grace) { + if (grace) + bg_log("[bgplay] 宽限期超时回收后台App: %s pid=%d(超 %ums 未再启动)", + e->app_id, (int)e->pid, (unsigned)MEOW_BG_GRACE_MS); + else + bg_log("[bgplay] 卡拔出,终止卡上后台 App: %s pid=%d", + e->app_id, (int)e->pid); kill(e->pid, SIGTERM); e->term_at_ms = now; - } else if (now - e->term_at_ms >= TERM_GRACE_MS) { - kill(e->pid, SIGKILL); - e->term_at_ms = now; /* 500ms 节流重复 SIGKILL 直至 reap */ } } + /* SIGTERM 后 SIGKILL 兜底(TERM_GRACE_MS 节流重复发直至 reap;卡拔/宽限/槽满回收共用) */ + if (e->term_at_ms && now - e->term_at_ms >= TERM_GRACE_MS) { + kill(e->pid, SIGKILL); + e->term_at_ms = now; + } r = waitpid(e->pid, &st, WNOHANG); if (r == e->pid) { char id[MEOW_APP_ID_MAX]; pid_t pid = e->pid; + int ofd = e->out_fd; snprintf(id, sizeof(id), "%s", e->app_id); + if (ofd >= 0) close(ofd); /* 回收:后台 App 已死,其 stdout 管道读端一并关(防 fd 泄漏) */ memset(e, 0, sizeof(*e)); g_change_seq++; bg_log("[bgplay] 后台 App 退出: %s pid=%d status=0x%x", id, (int)pid, st); diff --git a/patch_v2/meow_launcherd/meow_bgplay.h b/patch_v2/meow_launcherd/meow_bgplay.h index bce43dc..0446652 100644 --- a/patch_v2/meow_launcherd/meow_bgplay.h +++ b/patch_v2/meow_launcherd/meow_bgplay.h @@ -29,6 +29,12 @@ /* 后台保活 App 上限(同时驻留后台的播放中 App) */ #define MEOW_BG_MAX 4 +/* 批①+宽限:无声明/媒体不活跃 App 入后台的宽限期(默认 30 分钟)。 + * 语义:转后台(bg_since_ms)后超 MEOW_BG_GRACE_MS 未再启动 → meow_bg_tick 走真退序列 + * SIGTERM→SIGKILL 回收(摘表 + 通知 amsd)。**声明常驻(keepalive=1)或媒体活跃 + * (meow_media_app_resident=1)不清**。本批为编译期常量(30*60*1000 = 1800000ms); + * 后续可进设置/按 App 可配(待拍板)。 */ +#define MEOW_BG_GRACE_MS 1800000u /* 无头后台服务运行时槽上限(与 meow_acct.svcs 的 MEOW_MAX_SVCS 对齐) */ #define MEOW_SVC_RT_MAX 8 @@ -45,9 +51,10 @@ typedef struct meow_bg_entry { pid_t pid; int out_fd; int on_card; /* 二进制在 TF 卡上(卡拔需 SIGTERM 防 SIGBUS) */ - uint64_t term_at_ms; /* 卡拔 SIGTERM 发出时刻(0=未发;SIGKILL 兜底计时) */ - uint64_t bg_since_ms; /* 批①:转后台时刻(monotonic ms,批③管理页/闲置回收用) */ - char keepalive; /* 批①:1=background_keepalive 类(无媒体会话也保活);0=background_audio 类 */ + uint64_t term_at_ms; /* 卡拔/宽限超时/槽满回收 SIGTERM 发出时刻(0=未发;SIGKILL 兜底计时) */ + uint64_t bg_since_ms; /* 批①:转后台时刻(monotonic ms;宽限超时回收/槽满 LRU 掐最久用) */ + char keepalive; /* 批①+宽限:1=声明常驻(background_keepalive,无限期不清);0=宽限期类 + * (未声明/background_audio,媒体不活跃时超 bg_grace_ms 回收) */ } meow_bg_entry_t; /* 无头后台服务运行时槽(静态声明在 meow_acct.svcs,此处为运行态)。 */ @@ -73,6 +80,8 @@ typedef struct meow_svc_rt { /* 依赖注入。全 NULL 可用(log 静默、now_ms 内部 CLOCK_MONOTONIC、card_present 恒 1、 * 通知清理/发帧跳过)。 * on_bg_dead:后台保活 App 进程退出(status=waitpid 原始状态;正常/异常由回调方判); + * media_resident:该 App 媒体会话是否「活跃可驻留」(launcher 注入 meow_media_app_resident)。 + * 批①+宽限用:宽限超时回收 / 槽满 LRU 判「媒体活跃不清不回收」;NULL=恒 0(视为不活跃)。 * on_svc_dead:服务槽最终消亡(stopping 走完/不重启死亡;熔断不算消亡,槽保留待复位); * notif_clear_app:熔断时清该 App 全部通知,返回清除条数(<0=失败); * send_frame:熔断广播 MSG_SYS(launcher 侧须先查连接存活再发); @@ -82,6 +91,7 @@ typedef struct meow_bgplay_ops { uint64_t (*now_ms)(void); int (*card_present)(void); void (*on_bg_dead)(const char *app_id, pid_t pid, int status); + int (*media_resident)(const char *app_id); void (*on_svc_dead)(const char *app_id, const char *svc); int (*notif_clear_app)(const char *app_id); size_t (*send_frame)(int fd, uint16_t type, uint32_t req_id, @@ -101,8 +111,11 @@ void meow_bgplay_init(const meow_bgplay_ops *ops); int meow_bg_should_keep(int bg_audio_declared, int media_resident, int keepalive_declared); /* ---- 后台保活 App 表 ---- */ -/* 转后台:登记 app_id/pid/out_fd,keepalive 标记常驻类型(1=background_keepalive 类)。 - * 返回 0=成功;MEOW_ERR_EXISTS=已在后台;MEOW_ERR_NOMEM=满。 */ +/* 转后台:登记 app_id/pid/out_fd,keepalive 标记常驻类型(1=background_keepalive 类=无限期; + * 0=宽限期类=未声明/background_audio,媒体不活跃时超 bg_grace_ms 回收)。批①+宽限:**全 App** + * nav-exit 都经此入表;槽满(MEOW_BG_MAX=4)时 LRU 回收最久未激活(bg_since_ms 最旧)的**未声明** + * 槽(不回收声明常驻/媒体活跃);全为声明常驻/媒体活跃 → MEOW_ERR_NOMEM(调用方回落真退,保安全)。 + * 返回 0=成功;MEOW_ERR_EXISTS=已在后台;MEOW_ERR_NOMEM=满且无可回收。 */ int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card, int keepalive); /* 回前台:按 app_id 摘除槽(进程归还前台会话)。成功返回 0 且 *out_fd(可 NULL)带回 * 管道读端;未在后台返回 -1。 */ @@ -116,8 +129,11 @@ const meow_bg_entry_t *meow_bg_at(int i); /* 表变化计数(add/take/回收 递增);菜单 1s tick 比对刷新「·后台」标注用。 */ uint32_t meow_bgplay_change_seq(void); -/* 周期调(主循环 ~10ms 排空点):waitpid WNOHANG 回收死亡后台 App(→on_bg_dead 清槽); - * 卡拔例外:on_card 槽在卡不在时 SIGTERM,500ms 未死 SIGKILL。 */ +/* 周期调(主循环 ~10ms 排空点):waitpid WNOHANG 回收死亡后台 App(→on_bg_dead 清槽)。 + * 终止触发(SIGTERM,TERM_GRACE_MS 后 SIGKILL): + * - 卡拔例外:on_card 槽在卡不在时终止(防 SIGBUS); + * - 批①+宽限 超时回收:未声明(keepalive=0)且媒体不活跃(media_resident=0)的槽,转后台超 + * MEOW_BG_GRACE_MS 未再启动 → 回收(走真退序列 + 摘表 + 通知 amsd)。声明常驻/媒体活跃不清。 */ void meow_bg_tick(void); /* ---- 无头后台服务 ---- */ diff --git a/patch_v2/meow_launcherd/test_bgplay.c b/patch_v2/meow_launcherd/test_bgplay.c index ccc662b..6ffe659 100644 --- a/patch_v2/meow_launcherd/test_bgplay.c +++ b/patch_v2/meow_launcherd/test_bgplay.c @@ -7,7 +7,10 @@ * should_keep=0(停播后再退出=真退); * 3) 后台 App 崩溃照收:子进程被杀 → bg_tick WNOHANG 回收 → on_bg_dead + 清槽; * 4) 卡拔例外:on_card=1 + card_present 假 0 → tick SIGTERM 回收(防 SIGBUS); - * 5) bg 表满(MEOW_BG_MAX)→ MEOW_ERR_NOMEM;重复 add → MEOW_ERR_EXISTS; + * 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 后不再拉起;重注册复位熔断重新拉起; @@ -154,6 +157,7 @@ int main(void) 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; @@ -267,33 +271,122 @@ int main(void) "卡拔 → 卡上后台 App SIGTERM 回收(防 SIGBUS)"); g_card = 1; - /* ---- 5) 表满 / 重复 ---- */ + /* ---- 5) 表满:LRU 回收最久未激活的未声明槽 / 全常驻满→NOMEM / 重复 / 参数 ---- */ { pid_t k[MEOW_BG_MAX]; char id[MEOW_APP_ID_MAX]; - int i, full = 0; + int i; + pid_t gextra = 0; for (i = 0; i < MEOW_BG_MAX; i++) { k[i] = spawn_sleeper(); - snprintf(id, sizeof id, "dev.test.full%d", i); - meow_bg_add(id, k[i], -1, 0, 0); + snprintf(id, sizeof id, "dev.test.lru%d", i); + CHECK(meow_bg_add(id, k[i], -1, 0, 0) == 0, "lru 填表(未声明 keepalive=0)"); } - snprintf(id, sizeof id, "dev.test.full%d", MEOW_BG_MAX); + /* 5.1 表满 + 全未声明 → 新增 LRU 回收最旧(lru0,bg_since_ms 最小),新 App 入表成功 */ { - pid_t extra = spawn_sleeper(); - full = (meow_bg_add(id, extra, -1, 0, 0) == MEOW_ERR_NOMEM); - kill(extra, SIGKILL); waitpid(extra, NULL, 0); /* 未入表,就地清理 */ + 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 真退回收)"); } - CHECK(full, "bg 表满 → MEOW_ERR_NOMEM"); - snprintf(id, sizeof id, "dev.test.full%d", 0); - CHECK(meow_bg_add(id, 0, -1, 0, 0) == MEOW_ERR_INVAL, "pid<=0 → INVAL"); - CHECK(meow_bg_add(id, k[0], -1, 0, 0) == MEOW_ERR_EXISTS, "重复 app_id → EXISTS"); - for (i = 0; i < MEOW_BG_MAX; i++) { - snprintf(id, sizeof id, "dev.test.full%d", i); + /* 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]); } - CHECK(meow_bg_count() == 0, "take 全部后 bg 表空"); /* ================= 无头后台服务 ================= */ write_test_svc();