feat(bt): 批①宽限期补充 — 全App入后台表(bg_since_ms)+未声明App宽限30min(MEOW_BG_GRACE_MS)超时回收+声明常驻/媒体活跃不清+槽满LRU回收最旧未声明;test_bgplay全过/0warning;devdocs APP_BACKGROUND_KEEPALIVE_P1 §9;M6.34需先重编meow_ui.a(meow_img_pixels)

This commit is contained in:
AskaEth committed 2026-09-04 01:48:31 +08:00
1 parent effce6d0db
commit 45c633868f
4 files changed
+264 -62

No files matched your search

+21 -13
View File
@@ -479,6 +479,7 @@ void meow_bgplay_launcher_init(void)
ops.now_ms = NULL; /* 内部 CLOCK_MONOTONIC */ ops.now_ms = NULL; /* 内部 CLOCK_MONOTONIC */
ops.card_present = sd_device_present; ops.card_present = sd_device_present;
ops.on_bg_dead = bg_on_dead; ops.on_bg_dead = bg_on_dead;
ops.media_resident = meow_media_app_resident; /* 批①+宽限:媒体活跃判定(宽限回收/槽满LRU) */
ops.on_svc_dead = NULL; /* 服务消亡日志 meow_bgplay 内已记 */ ops.on_svc_dead = NULL; /* 服务消亡日志 meow_bgplay 内已记 */
ops.notif_clear_app = bg_notif_clear_app; ops.notif_clear_app = bg_notif_clear_app;
ops.send_frame = bg_send_frame; ops.send_frame = bg_send_frame;
@@ -1964,22 +1965,29 @@ static int launch_app_chain(struct app *a, const char *chain)
break; break;
} }
if (meow_nav_exit_pending()) { if (meow_nav_exit_pending()) {
/* 批①后台保活:声明 background_keepalive → 恒保活;或声明 background_audio 且该 App /* 批①+宽限后台保活:**全 App** nav-exit 入后台保活表(进程保留转后台,UI 冻结走 §10.5
* 媒体会话活跃(PLAYING/PAUSED/BUFFER,见 meow_media_app_resident)→ 保活。进程+音频流 * SDK present 自抑,**非 SIGSTOP**——SIGSTOP 会冻结同进程播放线程断供 PCM,§10.5 实证)。
* 保留转后台(UI 冻结走 §10.5 SDK present 自抑,**非 SIGSTOP**——SIGSTOP 会冻结同进程 * 分两类:
* 播放线程断供 PCM,§10.5 真机实证)。登记失败(槽满/已存在)回落下方真退序列。 * - 声明常驻(background_keepalive=1)或媒体活跃(background_audio+PLAYING/PAUSED/BUFFER,
* 卡拔例外不适用保活(bg 表 tick 照杀)。 */ * 见 meow_media_app_resident)→ **无限期保活**(不清,除非用户手动/一键清);
if (meow_bg_should_keep(a->background_audio == '1', * - 其余(未声明/媒体不活跃)→ **宽限期**(bg_grace_ms,默认30min)内保活,超时未再启动
meow_media_app_resident(a->id), * 由 meow_bg_tick 走真退序列回收。
a->background_keepalive == '1') && * meow_bg_add 内含槽满 LRU 回收(最久未激活的未声明槽);全为声明常驻/媒体活跃 → NOMEM
meow_bg_add(a->id, pid, out_fd, app_on_card, * 回落下方真退序列(保安全)。kill 通道带 !a->framework 门控(批③)。 */
a->background_keepalive == '1') == 0) { int bg_resident = meow_media_app_resident(a->id);
logmsg("bg: app %s nav-exit -> 后台保活(活跃(播放/暂停/缓冲),pid=%d 保留,SDK present 自抑)", int r = meow_bg_add(a->id, pid, out_fd, app_on_card,
a->name, (int)pid); 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; to_background = 1;
break; 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; nav_clean_exit = 1;
kill(pid, SIGTERM); kill(pid, SIGTERM);
usleep(300000); usleep(300000);
+111 -26
View File
@@ -7,6 +7,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <errno.h>
#include <unistd.h> #include <unistd.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <fcntl.h> #include <fcntl.h>
@@ -79,30 +80,100 @@ static meow_bg_entry_t *bg_find(const char *app_id)
return NULL; 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 meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card, int keepalive)
{ {
int i; int i;
if (!app_id || !app_id[0] || pid <= 0) return MEOW_ERR_INVAL; if (!app_id || !app_id[0] || pid <= 0) return MEOW_ERR_INVAL;
if (bg_find(app_id)) return MEOW_ERR_EXISTS; if (bg_find(app_id)) return MEOW_ERR_EXISTS;
for (i = 0; i < MEOW_BG_MAX; i++) { for (i = 0; i < MEOW_BG_MAX; i++)
if (!g_bg[i].used) { if (!g_bg[i].used) { bg_fill(i, app_id, pid, out_fd, on_card, keepalive); return 0; }
meow_bg_entry_t *e = &g_bg[i]; /* 槽满:LRU 回收最久未激活的未声明槽;全为声明常驻/媒体活跃 → NOMEM(调用方回落真退)。 */
if (bg_evict_lru() >= 0)
memset(e, 0, sizeof(*e)); for (i = 0; i < MEOW_BG_MAX; i++)
e->used = 1; if (!g_bg[i].used) { bg_fill(i, app_id, pid, out_fd, on_card, keepalive); return 0; }
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;
}
}
return MEOW_ERR_NOMEM; return MEOW_ERR_NOMEM;
} }
@@ -158,25 +229,39 @@ void meow_bg_tick(void)
if (!e->used) continue; if (!e->used) continue;
/* 卡拔例外:二进制在卡上 → SIGTERM(防 SIGBUS),500ms 未死 SIGKILL */ /* 触发终止(SIGTERM→SIGKILL 真退序列)的两类条件,仅当尚未进入终止序列(term_at_ms==0):
if (e->on_card && !bg_card_present()) { * a) 卡拔例外:on_card 且卡不在(防 SIGBUS);
if (e->term_at_ms == 0) { * b) 批①+宽限 超时回收:未声明(keepalive=0)且媒体不活跃(media_resident=0),转后台
bg_log("[bgplay] 卡拔出,终止卡上后台 App: %s pid=%d", * 超 MEOW_BG_GRACE_MS 未再启动 → 回收。声明常驻(keepalive=1)/媒体活跃**不清**。 */
e->app_id, (int)e->pid); 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); kill(e->pid, SIGTERM);
e->term_at_ms = now; 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); r = waitpid(e->pid, &st, WNOHANG);
if (r == e->pid) { if (r == e->pid) {
char id[MEOW_APP_ID_MAX]; char id[MEOW_APP_ID_MAX];
pid_t pid = e->pid; pid_t pid = e->pid;
int ofd = e->out_fd;
snprintf(id, sizeof(id), "%s", e->app_id); snprintf(id, sizeof(id), "%s", e->app_id);
if (ofd >= 0) close(ofd); /* 回收:后台 App 已死,其 stdout 管道读端一并关(防 fd 泄漏) */
memset(e, 0, sizeof(*e)); memset(e, 0, sizeof(*e));
g_change_seq++; g_change_seq++;
bg_log("[bgplay] 后台 App 退出: %s pid=%d status=0x%x", id, (int)pid, st); bg_log("[bgplay] 后台 App 退出: %s pid=%d status=0x%x", id, (int)pid, st);
+23 -7
View File
@@ -29,6 +29,12 @@
/* 后台保活 App 上限(同时驻留后台的播放中 App) */ /* 后台保活 App 上限(同时驻留后台的播放中 App) */
#define MEOW_BG_MAX 4 #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 对齐) */ /* 无头后台服务运行时槽上限(与 meow_acct.svcs 的 MEOW_MAX_SVCS 对齐) */
#define MEOW_SVC_RT_MAX 8 #define MEOW_SVC_RT_MAX 8
@@ -45,9 +51,10 @@ typedef struct meow_bg_entry {
pid_t pid; pid_t pid;
int out_fd; int out_fd;
int on_card; /* 二进制在 TF 卡上(卡拔需 SIGTERM 防 SIGBUS) */ int on_card; /* 二进制在 TF 卡上(卡拔需 SIGTERM 防 SIGBUS) */
uint64_t term_at_ms; /* 卡拔 SIGTERM 发出时刻(0=未发;SIGKILL 兜底计时) */ uint64_t term_at_ms; /* 卡拔/宽限超时/槽满回收 SIGTERM 发出时刻(0=未发;SIGKILL 兜底计时) */
uint64_t bg_since_ms; /* 批①:转后台时刻(monotonic ms,批③管理页/闲置回收用) */ uint64_t bg_since_ms; /* 批①:转后台时刻(monotonic ms;宽限超时回收/槽满 LRU 掐最久用) */
char keepalive; /* 批①:1=background_keepalive 类(无媒体会话也保活);0=background_audio 类 */ char keepalive; /* 批①+宽限:1=声明常驻(background_keepalive,无限期不清);0=宽限期类
* (未声明/background_audio,媒体不活跃时超 bg_grace_ms 回收) */
} meow_bg_entry_t; } meow_bg_entry_t;
/* 无头后台服务运行时槽(静态声明在 meow_acct.svcs,此处为运行态)。 */ /* 无头后台服务运行时槽(静态声明在 meow_acct.svcs,此处为运行态)。 */
@@ -73,6 +80,8 @@ typedef struct meow_svc_rt {
/* 依赖注入。全 NULL 可用(log 静默、now_ms 内部 CLOCK_MONOTONIC、card_present 恒 1、 /* 依赖注入。全 NULL 可用(log 静默、now_ms 内部 CLOCK_MONOTONIC、card_present 恒 1、
* 通知清理/发帧跳过)。 * 通知清理/发帧跳过)。
* on_bg_dead:后台保活 App 进程退出(status=waitpid 原始状态;正常/异常由回调方判); * on_bg_dead:后台保活 App 进程退出(status=waitpid 原始状态;正常/异常由回调方判);
* media_resident:该 App 媒体会话是否「活跃可驻留」(launcher 注入 meow_media_app_resident)。
* 批①+宽限用:宽限超时回收 / 槽满 LRU 判「媒体活跃不清不回收」;NULL=恒 0(视为不活跃)。
* on_svc_dead:服务槽最终消亡(stopping 走完/不重启死亡;熔断不算消亡,槽保留待复位); * on_svc_dead:服务槽最终消亡(stopping 走完/不重启死亡;熔断不算消亡,槽保留待复位);
* notif_clear_app:熔断时清该 App 全部通知,返回清除条数(<0=失败); * notif_clear_app:熔断时清该 App 全部通知,返回清除条数(<0=失败);
* send_frame:熔断广播 MSG_SYS(launcher 侧须先查连接存活再发); * send_frame:熔断广播 MSG_SYS(launcher 侧须先查连接存活再发);
@@ -82,6 +91,7 @@ typedef struct meow_bgplay_ops {
uint64_t (*now_ms)(void); uint64_t (*now_ms)(void);
int (*card_present)(void); int (*card_present)(void);
void (*on_bg_dead)(const char *app_id, pid_t pid, int status); 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); void (*on_svc_dead)(const char *app_id, const char *svc);
int (*notif_clear_app)(const char *app_id); int (*notif_clear_app)(const char *app_id);
size_t (*send_frame)(int fd, uint16_t type, uint32_t req_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); int meow_bg_should_keep(int bg_audio_declared, int media_resident, int keepalive_declared);
/* ---- 后台保活 App 表 ---- */ /* ---- 后台保活 App 表 ---- */
/* 转后台:登记 app_id/pid/out_fd,keepalive 标记常驻类型(1=background_keepalive 类)。 /* 转后台:登记 app_id/pid/out_fd,keepalive 标记常驻类型(1=background_keepalive 类=无限期;
* 返回 0=成功;MEOW_ERR_EXISTS=已在后台;MEOW_ERR_NOMEM=满。 */ * 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); 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)带回 /* 回前台:按 app_id 摘除槽(进程归还前台会话)。成功返回 0 且 *out_fd(可 NULL)带回
* 管道读端;未在后台返回 -1。 */ * 管道读端;未在后台返回 -1。 */
@@ -116,8 +129,11 @@ const meow_bg_entry_t *meow_bg_at(int i);
/* 表变化计数(add/take/回收 递增);菜单 1s tick 比对刷新「·后台」标注用。 */ /* 表变化计数(add/take/回收 递增);菜单 1s tick 比对刷新「·后台」标注用。 */
uint32_t meow_bgplay_change_seq(void); uint32_t meow_bgplay_change_seq(void);
/* 周期调(主循环 ~10ms 排空点):waitpid WNOHANG 回收死亡后台 App(→on_bg_dead 清槽); /* 周期调(主循环 ~10ms 排空点):waitpid WNOHANG 回收死亡后台 App(→on_bg_dead 清槽)。
* 卡拔例外:on_card 槽在卡不在时 SIGTERM,500ms 未死 SIGKILL。 */ * 终止触发(SIGTERM,TERM_GRACE_MS 后 SIGKILL):
* - 卡拔例外:on_card 槽在卡不在时终止(防 SIGBUS);
* - 批①+宽限 超时回收:未声明(keepalive=0)且媒体不活跃(media_resident=0)的槽,转后台超
* MEOW_BG_GRACE_MS 未再启动 → 回收(走真退序列 + 摘表 + 通知 amsd)。声明常驻/媒体活跃不清。 */
void meow_bg_tick(void); void meow_bg_tick(void);
/* ---- 无头后台服务 ---- */ /* ---- 无头后台服务 ---- */
+109 -16
View File
@@ -7,7 +7,10 @@
* should_keep=0(停播后再退出=真退); * should_keep=0(停播后再退出=真退);
* 3) 后台 App 崩溃照收:子进程被杀 → bg_tick WNOHANG 回收 → on_bg_dead + 清槽; * 3) 后台 App 崩溃照收:子进程被杀 → bg_tick WNOHANG 回收 → on_bg_dead + 清槽;
* 4) 卡拔例外:on_card=1 + card_present 假 0 → tick SIGTERM 回收(防 SIGBUS); * 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; * 6) 服务注册:exec 不可执行 → NOENT;同名在跑 → EXISTS;
* 7) 退避重启 1s/2s + max_restart=2 熔断(假时钟注入):清通知回调 + 注册连接收到 * 7) 退避重启 1s/2s + max_restart=2 熔断(假时钟注入):清通知回调 + 注册连接收到
* MSG_SYS{SVC_DEAD=2} + circuit 后不再拉起;重注册复位熔断重新拉起; * MSG_SYS{SVC_DEAD=2} + circuit 后不再拉起;重注册复位熔断重新拉起;
@@ -154,6 +157,7 @@ int main(void)
ops.now_ms = fake_now_ms; ops.now_ms = fake_now_ms;
ops.card_present = fake_card; ops.card_present = fake_card;
ops.on_bg_dead = fake_bg_dead; ops.on_bg_dead = fake_bg_dead;
ops.media_resident = meow_media_app_resident; /* 批①+宽限:媒体活跃判定(宽限回收/槽满LRU) */
ops.notif_clear_app = fake_notif_clear; ops.notif_clear_app = fake_notif_clear;
ops.send_frame = fake_send_frame; ops.send_frame = fake_send_frame;
ops.log_dir = NULL; ops.log_dir = NULL;
@@ -267,33 +271,122 @@ int main(void)
"卡拔 → 卡上后台 App SIGTERM 回收(防 SIGBUS)"); "卡拔 → 卡上后台 App SIGTERM 回收(防 SIGBUS)");
g_card = 1; g_card = 1;
/* ---- 5) 表满 / 重复 ---- */ /* ---- 5) 表满:LRU 回收最久未激活的未声明槽 / 全常驻满→NOMEM / 重复 / 参数 ---- */
{ {
pid_t k[MEOW_BG_MAX]; pid_t k[MEOW_BG_MAX];
char id[MEOW_APP_ID_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++) { for (i = 0; i < MEOW_BG_MAX; i++) {
k[i] = spawn_sleeper(); k[i] = spawn_sleeper();
snprintf(id, sizeof id, "dev.test.full%d", i); snprintf(id, sizeof id, "dev.test.lru%d", i);
meow_bg_add(id, k[i], -1, 0, 0); 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(); pid_t e2 = spawn_sleeper();
full = (meow_bg_add(id, extra, -1, 0, 0) == MEOW_ERR_NOMEM); gextra = e2;
kill(extra, SIGKILL); waitpid(extra, NULL, 0); /* 未入表,就地清理 */ 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"); /* 5.2 重复 add → EXISTS;pid<=0 → INVAL(lru0 已回收,表内为 lrunew/lru1/lru2/lru3) */
snprintf(id, sizeof id, "dev.test.full%d", 0); CHECK(meow_bg_add("dev.test.lrunew", 999999, -1, 0, 0) == MEOW_ERR_EXISTS, "重复 app_id → EXISTS");
CHECK(meow_bg_add(id, 0, -1, 0, 0) == MEOW_ERR_INVAL, "pid<=0 → INVAL"); CHECK(meow_bg_add("dev.test.lrunew", 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"); /* 5.3 清空剩余(take + 杀进程防泄漏) */
for (i = 0; i < MEOW_BG_MAX; i++) { for (i = 1; i < MEOW_BG_MAX; i++) {
snprintf(id, sizeof id, "dev.test.full%d", i); snprintf(id, sizeof id, "dev.test.lru%d", i);
meow_bg_take(id, NULL, NULL); meow_bg_take(id, NULL, NULL);
kill(k[i], SIGKILL); waitpid(k[i], NULL, 0); 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(); write_test_svc();