feat(launcher): M4 后台服务保活+后台播放(FRAMEWORK_DESIGN §5/§9 落地)
- app.json 新字段 background/background_audio 解析入 struct app/meow_acct 注册表
- meow_ipc.h:MSG_REGISTER(m_register_t)/SERVICE_OK(m_ack_t.status=pid)/SERVICE_STOP/
MSG_SYS(m_sys_t, SHUTDOWN=1/SVC_DEAD=2) 负载结构落地
- 新模块 meow_bgplay.c/h(自包含+ops 注入,仿 meow_media,宿主可单测):
· 后台播放保活——声明 background_audio 且媒体会话 PLAYING 的 App nav-exit 不终止,
进程+音频流保留;UI 冻结走 §10.5 /tmp/meow_notif_active SDK present 自抑(非字面
SIGSTOP:冻结同进程播放线程断供 PCM,§10.5 真机实证);点图标/媒体卡片回前台=
原进程恢复(chain/picker 忽略),停播后再退出=真退 SIGTERM
· 无头后台服务——MSG_REGISTER spawn/stop/reap + 退避重启 1/2/4/10s cap +
max_restart 熔断(清该 App 通知 + 注册连接广播 MSG_SYS{SVC_DEAD},重注册复位)
· 卡拔例外(卡上二进制后台 App/服务仍 SIGTERM 防 SIGBUS)、崩溃照收、
关机前 meow_bgplay_kill_all 有界回收(含 MSG_SYS{SHUTDOWN} 广播)
- util.c:MSG_REGISTER/MSG_SERVICE_STOP 分发(权限/声明校验链)+ ipc_conn_alive 连接
存活查询 + meow_ipc_process 末尾挂 meow_bg_housekeep(drain/reap/退避 tick)
- app_launcher.c:launch_app_chain 入口 resume(bg_take 原进程回前台)+ nav-exit
保活分支 + 转后台出口(杀看门狗/写自抑标记/复位顶条,跳过退出尾)+ 冷启动自动
拉起静态 keepalive(mode=0) 服务(exec 路径约定=App 同目录 <svc> 文件)
- main_menu.c:应用列表「·后台」标注(宫格/列表,1s tick 按 change_seq 刷新)+
两条退出尾 kill_all + unlink NOTIF_ACTIVE
- 宿主单测 test_bgplay.c 50 项全过(真 fork 子进程走状态机 + 假时钟退避熔断);
test_media/test_notif_panel/test_notif_tf/test_vol_ramp_jack 回归全过;
zig-mipsel-cc -Wall -Wextra 0 warning。launcher md5=c07d011c0769fca14d3bfb752d735463
This commit is contained in:
1 parent
7db8197f9c
commit
5aabede0cd
13 files changed
+1461
-40
No files matched your search
@@ -89,20 +89,27 @@ static void ams_report_launch(const struct app *a, const char *chain, pid_t pid)
|
||||
if (l > 0) ams_send(line, l);
|
||||
}
|
||||
|
||||
/* exit 上报:kind(exit|sig)/code/dur_ms。异常退出由 amsd 判 crash + 转发 meow_crash。 */
|
||||
static void ams_report_exit(const struct app *a, int status, long long dur_ms)
|
||||
/* exit 上报:kind(exit|sig)/code/dur_ms。异常退出由 amsd 判 crash + 转发 meow_crash。
|
||||
* _id 变体供 M4 后台保活回收路径用(彼时 struct app 可能已不在手边)。 */
|
||||
static void ams_report_exit_id(const char *app_id, int status, long long dur_ms)
|
||||
{
|
||||
if (!a) return;
|
||||
if (!app_id || !app_id[0]) return;
|
||||
const char *kind;
|
||||
int code;
|
||||
if (WIFSIGNALED(status)) { kind = "sig"; code = WTERMSIG(status); }
|
||||
else { kind = "exit"; code = WIFEXITED(status) ? WEXITSTATUS(status) : 1; }
|
||||
char line[256];
|
||||
int l = snprintf(line, sizeof line, "EV EXIT %s %s %d %lld %ld %ld\n",
|
||||
a->id, kind, code, dur_ms, (long)time(NULL), ams_uptime());
|
||||
app_id, kind, code, dur_ms, (long)time(NULL), ams_uptime());
|
||||
if (l > 0) ams_send(line, l);
|
||||
}
|
||||
|
||||
static void ams_report_exit(const struct app *a, int status, long long dur_ms)
|
||||
{
|
||||
if (!a) return;
|
||||
ams_report_exit_id(a->id, status, dur_ms);
|
||||
}
|
||||
|
||||
/* ---- stdout/stderr 管道捕获(AMSD_DESIGN §5):前台 waitpid 循环排空 + 按行转发 ---- */
|
||||
static char g_ams_out_line[1024]; /* 未完结行缓冲(无 \n) */
|
||||
static int g_ams_out_n = 0;
|
||||
@@ -119,17 +126,17 @@ static void ams_out_flush(void)
|
||||
}
|
||||
|
||||
/* 追加一行(line 为无 \n 的 NUL 串)到 flush 缓冲;满了先 flush。 */
|
||||
static void ams_out_line(const struct app *a, pid_t pid, const char *line)
|
||||
static void ams_out_line(const char *app_id, pid_t pid, const char *line)
|
||||
{
|
||||
if (!a || !line) return;
|
||||
if (!app_id || !line) return;
|
||||
int need = snprintf(NULL, 0, "EV OUT %s %d %ld %ld %s\n",
|
||||
a->id, (int)pid, (long)time(NULL), ams_uptime(), line);
|
||||
app_id, (int)pid, (long)time(NULL), ams_uptime(), line);
|
||||
if (g_ams_out_flush_n + need + 1 > (int)sizeof g_ams_out_flush) ams_out_flush();
|
||||
if (need < (int)sizeof g_ams_out_flush - 1 - g_ams_out_flush_n)
|
||||
g_ams_out_flush_n += snprintf(g_ams_out_flush + g_ams_out_flush_n,
|
||||
sizeof g_ams_out_flush - g_ams_out_flush_n,
|
||||
"EV OUT %s %d %ld %ld %s\n",
|
||||
a->id, (int)pid, (long)time(NULL), ams_uptime(), line);
|
||||
app_id, (int)pid, (long)time(NULL), ams_uptime(), line);
|
||||
}
|
||||
|
||||
/* 单次排空上限:App 被 OOM/信号杀后,其子孙进程可能仍继承持有 out_pipe 写端
|
||||
@@ -143,8 +150,11 @@ static void ams_out_line(const struct app *a, pid_t pid, const char *line)
|
||||
#define EXEC_CONFIRM_MS 2000
|
||||
|
||||
/* 排空读端尽可能多的字节,按行切分转发;超长行截断标记。有界:单次调用最多
|
||||
* 读 AMS_DRAIN_MAX 字节(见上)。读端已 O_NONBLOCK;App 死且写端全关 → read 返 0=EOF。 */
|
||||
static void ams_drain_stdout(const struct app *a, int fd, pid_t pid)
|
||||
* 读 AMS_DRAIN_MAX 字节(见上)。读端已 O_NONBLOCK;App 死且写端全关 → read 返 0=EOF。
|
||||
* linebuf/line_n:未完结行缓冲外提——前台会话用全局 g_ams_out_line/g_ams_out_n;
|
||||
* M4 后台保活 drain(meow_bg_housekeep)用独立缓冲,防与前台未完结行串扰。 */
|
||||
static void ams_drain_stdout(const char *app_id, int fd, pid_t pid,
|
||||
char *linebuf, int *line_n)
|
||||
{
|
||||
char tmp[2048];
|
||||
ssize_t n;
|
||||
@@ -155,19 +165,19 @@ static void ams_drain_stdout(const struct app *a, int fd, pid_t pid)
|
||||
for (ssize_t i = 0; i < n; i++) {
|
||||
char c = tmp[i];
|
||||
if (c == '\n') {
|
||||
g_ams_out_line[g_ams_out_n] = '\0';
|
||||
ams_out_line(a, pid, g_ams_out_line);
|
||||
g_ams_out_n = 0;
|
||||
linebuf[*line_n] = '\0';
|
||||
ams_out_line(app_id, pid, linebuf);
|
||||
*line_n = 0;
|
||||
} else if (c == '\r') {
|
||||
/* 去尾 \r */
|
||||
} else if (g_ams_out_n < (int)sizeof g_ams_out_line - 1) {
|
||||
g_ams_out_line[g_ams_out_n++] = c;
|
||||
} else if (*line_n < 1024 - 1) {
|
||||
linebuf[(*line_n)++] = c;
|
||||
} else {
|
||||
/* 超长行:截断标记 + 重置(当前字符丢弃) */
|
||||
g_ams_out_line[ (int)sizeof g_ams_out_line - 2 ] = '\0';
|
||||
ams_out_line(a, pid, g_ams_out_line);
|
||||
ams_out_line(a, pid, "[...trunc]");
|
||||
g_ams_out_n = 0;
|
||||
linebuf[1024 - 2] = '\0';
|
||||
ams_out_line(app_id, pid, linebuf);
|
||||
ams_out_line(app_id, pid, "[...trunc]");
|
||||
*line_n = 0;
|
||||
}
|
||||
}
|
||||
if (drained >= AMS_DRAIN_MAX) break; /* 有界:孤儿持续写也不卡死主循环 */
|
||||
@@ -311,6 +321,8 @@ static void meow_acct_sync(const char *app_id, const struct app *ap)
|
||||
struct meow_acct *a = meow_acct_upsert(app_id);
|
||||
if (!a) return;
|
||||
a->framework = ap->framework;
|
||||
a->background = ap->background; /* M4 */
|
||||
a->background_audio = ap->background_audio;
|
||||
strncpy(a->icon, ap->icon, 128);
|
||||
strncpy(a->name, ap->name, 16);
|
||||
strncpy(a->exec, ap->exec, 128);
|
||||
@@ -361,6 +373,119 @@ void meow_acct_declare(const char *app_id, const char *entries, int count)
|
||||
}
|
||||
|
||||
|
||||
/* ---- M4 后台保活 / 无头后台服务胶水(meow_bgplay ops 注入实现)----
|
||||
* 挂载:meow_bgplay_launcher_init() 由 util.c meow_ipc_init 末尾调一次;
|
||||
* meow_bg_housekeep() 挂 meow_ipc_process 末尾(主循环单写者排空点,四处循环都经它)。 */
|
||||
static void notify_app_crash(const char *pkg, int status); /* 定义见下(MEOW_CRASH_SOCK) */
|
||||
|
||||
/* 查静态声明的 service.<svc>(app.json 解析进 meow_acct.svcs);无返回 NULL。 */
|
||||
const struct meow_svc *meow_acct_svc_find(const struct meow_acct *ac, const char *svc)
|
||||
{
|
||||
if (!ac || !svc || !svc[0]) return NULL;
|
||||
for (int i = 0; i < ac->nsvc; i++)
|
||||
if (!strcmp(ac->svcs[i].name, svc)) return &ac->svcs[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 后台权限位:bit0=background(可注册无头服务)bit1=background_audio(后台音频)。 */
|
||||
int meow_acct_background_perm(const struct meow_acct *ac)
|
||||
{
|
||||
if (!ac) return 0;
|
||||
return (ac->background == '1' ? 1 : 0) | (ac->background_audio == '1' ? 2 : 0);
|
||||
}
|
||||
|
||||
/* M4 服务可执行路径约定:App 二进制同目录下的 <svc> 名文件
|
||||
* (app.json `service.<svc>=...` 只声明策略,二进制位置由此约定派生)。 */
|
||||
void meow_svc_exec_path(const char *app_exec, const char *svc, char *out, int olen)
|
||||
{
|
||||
char dir[144];
|
||||
char *sl;
|
||||
|
||||
snprintf(dir, sizeof(dir), "%s", app_exec ? app_exec : "");
|
||||
sl = strrchr(dir, '/');
|
||||
if (sl) sl[1] = 0;
|
||||
else dir[0] = 0;
|
||||
snprintf(out, (size_t)olen, "%s%s", dir, svc ? svc : "");
|
||||
}
|
||||
|
||||
/* 后台保活 drain 的独立行缓冲(防与前台会话的 g_ams_out_line 未完结行串扰,一 App 一组) */
|
||||
static char g_bg_out_line[MEOW_BG_MAX][1024];
|
||||
static int g_bg_out_n[MEOW_BG_MAX];
|
||||
|
||||
/* meow_bgplay on_bg_dead:后台保活 App 进程退出(崩溃照收/停播后自退/卡拔 SIGTERM 走完)。 */
|
||||
static void bg_on_dead(const char *app_id, pid_t pid, int status)
|
||||
{
|
||||
(void)pid;
|
||||
ams_report_exit_id(app_id, status, 0); /* dur 未知(保活起点未记),记 0 */
|
||||
if (WIFSIGNALED(status) ||
|
||||
(WIFEXITED(status) && WEXITSTATUS(status) != 0))
|
||||
notify_app_crash(app_id, status); /* 崩溃照收(SIGTERM=卡拔/关机回收也算信号,
|
||||
* meow_crash 侧自行判重;与前台同口径) */
|
||||
meow_media_clear(app_id); /* 媒体会话随源死清槽 */
|
||||
/* §10.5 自抑标记:仅当「再无后台保活 App 且无通知中心叠加会话」才清——
|
||||
* 多 bg App 共存时标记粒度为全局(v1 定案:叠加会话与后台保活共用同一标记)。 */
|
||||
if (meow_bg_count() == 0 && g_notif_ctrl_pid <= 0)
|
||||
unlink(NOTIF_ACTIVE);
|
||||
}
|
||||
|
||||
/* meow_bgplay notif_clear_app:熔断时清该 App 全部活跃通知(先收集 id 再 dismiss,
|
||||
* 防遍历时改表)。返回清除条数。 */
|
||||
struct bg_clear_ctx { uint32_t ids[16]; int n; };
|
||||
static int bg_notif_collect_cb(const meow_notif_t *nt, void *ctx)
|
||||
{
|
||||
struct bg_clear_ctx *c = ctx;
|
||||
if (c->n < 16) c->ids[c->n++] = nt->id;
|
||||
return c->n >= 16; /* 集满即中止遍历 */
|
||||
}
|
||||
static int bg_notif_clear_app(const char *app_id)
|
||||
{
|
||||
struct bg_clear_ctx c;
|
||||
|
||||
memset(&c, 0, sizeof(c));
|
||||
meow_notif_list(app_id, bg_notif_collect_cb, &c);
|
||||
for (int i = 0; i < c.n; i++) meow_notif_dismiss(c.ids[i]);
|
||||
return c.n;
|
||||
}
|
||||
|
||||
/* meow_bgplay send_frame:熔断广播投递前判 IPC 连接存活(注册方可能已断开)。 */
|
||||
static size_t bg_send_frame(int fd, uint16_t type, uint32_t req_id,
|
||||
const void *payload, uint32_t len)
|
||||
{
|
||||
if (!meow_ipc_conn_alive(fd)) return 0;
|
||||
return meow_ipc_send_pub(fd, type, req_id, payload, len);
|
||||
}
|
||||
|
||||
void meow_bgplay_launcher_init(void)
|
||||
{
|
||||
meow_bgplay_ops ops;
|
||||
|
||||
memset(&ops, 0, sizeof(ops));
|
||||
ops.log = logmsg;
|
||||
ops.now_ms = NULL; /* 内部 CLOCK_MONOTONIC */
|
||||
ops.card_present = sd_device_present;
|
||||
ops.on_bg_dead = bg_on_dead;
|
||||
ops.on_svc_dead = NULL; /* 服务消亡日志 meow_bgplay 内已记 */
|
||||
ops.notif_clear_app = bg_notif_clear_app;
|
||||
ops.send_frame = bg_send_frame;
|
||||
ops.log_dir = log_dir[0] ? log_dir : NULL;
|
||||
meow_bgplay_init(&ops);
|
||||
}
|
||||
|
||||
/* 后台表周期维护(meow_ipc_process 末尾调):drain 各 bg App stdout(防写满管道阻塞
|
||||
* 播放线程)+ 回收/卡拔 tick + 服务退避重启 tick。 */
|
||||
void meow_bg_housekeep(void)
|
||||
{
|
||||
for (int i = 0; i < MEOW_BG_MAX; i++) {
|
||||
const meow_bg_entry_t *e = meow_bg_at(i);
|
||||
if (e && e->out_fd >= 0)
|
||||
ams_drain_stdout(e->app_id, e->out_fd, e->pid,
|
||||
g_bg_out_line[i], &g_bg_out_n[i]);
|
||||
}
|
||||
meow_bg_tick();
|
||||
meow_svc_tick();
|
||||
}
|
||||
|
||||
|
||||
/* 链路深度 = ';' 分隔段数(根屏→目标屏的逻辑链长)。 */
|
||||
int chain_depth(const char *c)
|
||||
{
|
||||
@@ -525,6 +650,8 @@ void meow_parse_app_json(FILE *f, struct app *a)
|
||||
int fram = 0, immersive = 0;
|
||||
int fullscreen = 0; /* app.json fullscreen=<true|1>(注入 MEOW_FULLSCREEN env,SDK 预先设全屏) */
|
||||
int fonts = 0, fonts_set = 0; /* app.json "fonts":<档>(0=默认4K常用 1=大字库GBK 2=Full;缺省 0) */
|
||||
int background = 0; /* M4:app.json background=true(可注册无头后台服务) */
|
||||
int bg_audio = 0; /* M4:app.json background_audio=true(播放中退出转后台保活) */
|
||||
struct app tmp;
|
||||
memset(&tmp, 0, sizeof tmp);
|
||||
while (fgets(buf, sizeof buf, f)) {
|
||||
@@ -638,6 +765,24 @@ void meow_parse_app_json(FILE *f, struct app *a)
|
||||
fonts_set = 1;
|
||||
}
|
||||
}
|
||||
/* "background":true(M4:可注册无头后台服务,MSG_REGISTER 权限位0)。
|
||||
* 注意 "background" 是 "background_audio" 的前缀:反向误命中(本 key 匹配到
|
||||
* background_audio 行)时 json_str 读出 "_audio…" ≠ true,判定自然为 0,无害。 */
|
||||
if (!background && strstr(buf, "background")) {
|
||||
char v[8] = "";
|
||||
if (json_str(buf, "background", v, sizeof v) > 0) {
|
||||
if (!strcmp(v, "true") || !strcmp(v, "1") || !strcmp(v, "yes"))
|
||||
background = 1;
|
||||
}
|
||||
}
|
||||
/* "background_audio":true(M4:播放中退出转后台保活,进程+音频流保留,§9) */
|
||||
if (!bg_audio && strstr(buf, "background_audio")) {
|
||||
char v[8] = "";
|
||||
if (json_str(buf, "background_audio", v, sizeof v) > 0) {
|
||||
if (!strcmp(v, "true") || !strcmp(v, "1") || !strcmp(v, "yes"))
|
||||
bg_audio = 1;
|
||||
}
|
||||
}
|
||||
/* service.<svc>=mode:restart:max:window_ms(M1 解析存储,保活 M4) */
|
||||
char *sp = strstr(buf, "service.");
|
||||
if (sp && tmp.nsvc < MEOW_MAX_SVCS) {
|
||||
@@ -670,6 +815,8 @@ void meow_parse_app_json(FILE *f, struct app *a)
|
||||
a->immersive = immersive ? '1' : 0;
|
||||
a->fullscreen = fullscreen ? '1' : 0; /* 注入 MEOW_FULLSCREEN env */
|
||||
a->fonts = (char)(fonts < 0 ? 0 : (fonts > 2 ? 2 : fonts)); /* 注入 MEOW_FONT_TIER env */
|
||||
a->background = background ? '1' : 0; /* M4:后台服务注册权限 */
|
||||
a->background_audio = bg_audio ? '1' : 0; /* M4:后台播放保活 */
|
||||
strncpy(a->icon, icon, 128);
|
||||
a->nroute = tmp.nroute;
|
||||
if (tmp.nroute) memcpy(a->routes, tmp.routes, sizeof(a->routes[0]) * (size_t)tmp.nroute);
|
||||
@@ -1295,6 +1442,25 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
g_ams_pick_armed = 0;
|
||||
}
|
||||
if (!a->ok) { logmsg("launch blocked: exec not ok"); return -1; }
|
||||
|
||||
/* M4 后台保活恢复:该 App 驻留后台(播放中经 nav-exit 转过)→ 原进程回前台,
|
||||
* 不 fork/exec(进程仍存无法注入新 env:chain/picker 上下文忽略,记日志)。
|
||||
* pid/out_fd 外提:冷启动与恢复两路填充,其后 watchdog/waitpid 循环共用。 */
|
||||
pid_t pid = 0;
|
||||
int out_fd = -1;
|
||||
int resumed = 0;
|
||||
if (meow_bg_take(a->id, &out_fd, &pid) == 0) {
|
||||
resumed = 1;
|
||||
logmsg("launch: %s resume from background (pid=%d)", a->name, (int)pid);
|
||||
if (chain && chain[0]) logmsg("launch: resume ignores chain=\"%s\"", chain);
|
||||
if (pick_action[0]) logmsg("launch: resume ignores picker action=%s", pick_action);
|
||||
/* 回前台:清 §10.5 SDK present 自抑标记(无其他 bg App 且无叠加会话时) */
|
||||
if (meow_bg_count() == 0 && g_notif_ctrl_pid <= 0)
|
||||
unlink(NOTIF_ACTIVE);
|
||||
drain_input();
|
||||
meow_nav_begin(a->id, NULL); /* 转后台时导航栈已清;回前台从裸态重建 */
|
||||
}
|
||||
if (!resumed) {
|
||||
meow_nav_begin(a->id, chain); /* M3:冷启动/裸启动都以当前链深初始化导航栈(chain 空→depth 0) */
|
||||
|
||||
/* M2.12:启动前准备 MeowData/<pkg>/{data,cache}(幂等;卡不在/失败只 log 不崩)。
|
||||
@@ -1331,7 +1497,7 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
fcntl(out_pipe[1], F_SETFD, FD_CLOEXEC);
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
close(execpipe[0]); close(execpipe[1]);
|
||||
if (out_pipe[0] >= 0) close(out_pipe[0]);
|
||||
@@ -1494,7 +1660,7 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
}
|
||||
close(execpipe[1]);
|
||||
if (out_pipe[1] >= 0) close(out_pipe[1]); /* parent: 关闭写端 */
|
||||
int out_fd = out_pipe[0]; /* parent 保留读端,前台循环排空 */
|
||||
out_fd = out_pipe[0]; /* parent 保留读端,前台循环排空 */
|
||||
if (out_fd >= 0) {
|
||||
int fl = fcntl(out_fd, F_GETFL, 0);
|
||||
if (fl >= 0) fcntl(out_fd, F_SETFL, fl | O_NONBLOCK);
|
||||
@@ -1527,13 +1693,35 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
if (out_fd >= 0) close(out_fd);
|
||||
return -1;
|
||||
}
|
||||
} /* end if (!resumed):冷启动分支(prepare/fork/exec/确认) */
|
||||
|
||||
/* AMSD_DESIGN §4.1:启动上报(pkg/activity/chain/pid),amsd 建表项 + 开 stdout 捕获会话。 */
|
||||
ams_report_launch(a, chain, pid);
|
||||
/* AMSD_DESIGN §4.1:启动上报(pkg/activity/chain/pid),amsd 建表项 + 开 stdout 捕获会话。
|
||||
* M4:两路都报(恢复路径诚实记为新一次 LAUNCH;chain 被忽略故报 NULL)。 */
|
||||
ams_report_launch(a, resumed ? NULL : chain, pid);
|
||||
|
||||
/* GESTURE_BACK §2.4:应用启动即写 immersive 状态,watchdog 依此隐藏/保留虚拟返回键 */
|
||||
meow_write_app_state(a->immersive == '1' ? 1 : 0);
|
||||
|
||||
/* M4:冷启动成功且声明 background=true → 自动拉起其静态声明的 keepalive(mode=0)
|
||||
* 无头服务(reg_fd=-1=静态拉起无注册连接;同名在跑/退避排程中 → EXISTS 静默跳过)。
|
||||
* 服务 exec 路径约定 = App 二进制同目录下的 <svc> 名文件(见 meow_svc_exec_path)。 */
|
||||
if (!resumed && a->background == '1') {
|
||||
for (int i = 0; i < a->nsvc; i++) {
|
||||
char sexec[160];
|
||||
int rc;
|
||||
if (a->svcs[i].mode != 0) continue;
|
||||
meow_svc_exec_path(a->exec, a->svcs[i].name, sexec, sizeof sexec);
|
||||
rc = meow_svc_register(a->id, a->svcs[i].name, sexec,
|
||||
a->svcs[i].restart, a->svcs[i].max,
|
||||
a->svcs[i].window_ms,
|
||||
strncmp(sexec, MEOWAPPS, strlen(MEOWAPPS)) == 0,
|
||||
-1);
|
||||
if (rc == MEOW_ERR_EXISTS) continue; /* 在跑/排程中:静默跳过 */
|
||||
logmsg("launch: auto-svc %s/%s -> %s", a->id, a->svcs[i].name,
|
||||
rc > 0 ? "spawned" : "FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
/* watchdog for virtual back key / gestures */
|
||||
char wd1[192], pidstr[16];
|
||||
pid_t wpid = 0;
|
||||
@@ -1580,6 +1768,7 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
unsigned long long ams_t0 = now_ms(); /* AMSD_DESIGN §4.1:运行时长起点 */
|
||||
int ams_status_valid = 0; /* 仅真实 waitpid 回收才置 1(规避 sigterm/重启路径 status=0 假干净退出) */
|
||||
int nav_clean_exit = 0; /* M3:MSG_NAV{EXIT} 主动退出 → 不按崩溃上报 */
|
||||
int to_background = 0; /* M4:nav-exit 转后台保活(进程保留,跳过退出尾) */
|
||||
int app_prev_vold = 0;
|
||||
int app_seq_hits = 0;
|
||||
unsigned long long app_seq_last = 0;
|
||||
@@ -1621,7 +1810,7 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
if (sigterm_flag) { logmsg("sigterm during app"); kill(pid, SIGTERM); break; }
|
||||
/* AMSD_DESIGN §5.1:排空 App stdout/stderr 管道(非阻塞),按行转发 amsd;App 写满不阻塞。 */
|
||||
g_ams_out_had = 0;
|
||||
if (out_fd >= 0) ams_drain_stdout(a, out_fd, pid);
|
||||
if (out_fd >= 0) ams_drain_stdout(a->id, out_fd, pid, g_ams_out_line, &g_ams_out_n);
|
||||
/* M3 导航栈:前台会话排空 IPC(App 经 MSG_NAV REPORT/EXIT 上报导航/请求退出)。
|
||||
* REPORT 更新 nav_stack(meow_ipc_process 内单写者);EXIT → 终止应用回主屏。 */
|
||||
meow_ipc_process();
|
||||
@@ -1635,6 +1824,18 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
break;
|
||||
}
|
||||
if (meow_nav_exit_pending()) {
|
||||
/* M4 后台播放保活:声明 background_audio 且该 App 媒体会话 PLAYING → 不终止,
|
||||
* 进程+音频流保留转后台(UI 冻结走 §10.5 SDK present 自抑,**非 SIGSTOP**——
|
||||
* SIGSTOP 会冻结同进程播放线程断供 PCM,§10.5 真机实证)。登记失败
|
||||
*(槽满/已存在)回落下方真退序列。卡拔例外不适用保活(bg 表 tick 照杀)。 */
|
||||
if (meow_bg_should_keep(a->background_audio == '1',
|
||||
meow_media_app_playing(a->id)) &&
|
||||
meow_bg_add(a->id, pid, out_fd, app_on_card) == 0) {
|
||||
logmsg("bg: app %s nav-exit -> 后台保活(播放中,pid=%d 保留,SDK present 自抑)",
|
||||
a->name, (int)pid);
|
||||
to_background = 1;
|
||||
break;
|
||||
}
|
||||
logmsg("nav: app %s MSG_NAV{EXIT} -> terminate, return to home", a->name);
|
||||
nav_clean_exit = 1;
|
||||
kill(pid, SIGTERM);
|
||||
@@ -1791,6 +1992,36 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
* AMSD_DESIGN §5.1:本轮排空到 stdout 数据 → 也睡短些,防止 App 写满管道被阻塞。 */
|
||||
usleep((meow_ui_statusbar_animating() || g_ams_out_had) ? 20000 : 100000);
|
||||
}
|
||||
/* M4:转后台保活出口 —— 跳过退出尾(不 ams EXIT/不 crash 上报/不清媒体会话/
|
||||
* 不关 out_fd:进程仍存活,out_fd 归 bg 表由 meow_bg_housekeep 继续 drain)。
|
||||
* 停播后再退出走上方正常退出尾 = 真退。 */
|
||||
if (to_background) {
|
||||
g_app_session_active = 0; /* 前台会话结束(ipc_dispatch 不再受理 NOTIF_OPEN) */
|
||||
meow_notif_open_clear();
|
||||
if (wpid > 0) {
|
||||
/* 看门狗随前台会话结束回收(后台无手势监听;有界等防挂死) */
|
||||
kill(wpid, SIGTERM);
|
||||
unsigned long long wstart = now_ms();
|
||||
while (now_ms() - wstart < 2000) {
|
||||
if (waitpid(wpid, NULL, WNOHANG) == wpid) break;
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
/* 写 §10.5 SDK 自抑标记:App 进程见 /tmp/meow_notif_active → present 画进
|
||||
* back_buf 不上屏 + 输入线程排空(=UI 冻结);音频经 audiod socket 天然续播。 */
|
||||
int nfd = open(NOTIF_ACTIVE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (nfd >= 0) { write(nfd, "1", 1); close(nfd); }
|
||||
meow_write_app_state(-1);
|
||||
unlink(MEOW_SBAR_MODE_FILE); /* 顶条模式文件由 App SDK 写;转后台清理复位菜单整条 */
|
||||
g_sb_mode_last = -1;
|
||||
meow_ui_statusbar_set_collapsed(0);
|
||||
meow_ui_statusbar_animate(0);
|
||||
meow_ui_set_fullscreen(0);
|
||||
meow_dirty_add(0, SB_BAR_Y, fb_xres, SB_BAR_H);
|
||||
settings_load();
|
||||
meow_nav_exit(); /* 前台会话导航栈清栈(App 本体栈内存活;回前台时裸态重建) */
|
||||
return 0;
|
||||
}
|
||||
/* §10.5:App 前台会话结束,清会话标记(ipc_dispatch 不再受理 MSG_NOTIF_OPEN) */
|
||||
g_app_session_active = 0;
|
||||
meow_notif_open_clear();
|
||||
@@ -1798,10 +2029,10 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
* 先转发 amsd,再报 EXIT / crash。仅当有读端(管道建立成功)才 drain。 */
|
||||
if (out_fd >= 0) {
|
||||
g_ams_out_had = 0;
|
||||
ams_drain_stdout(a, out_fd, pid); /* 排空全部剩余(App 死 → read 返回 0=EOF) */
|
||||
ams_drain_stdout(a->id, out_fd, pid, g_ams_out_line, &g_ams_out_n); /* 排空全部剩余(App 死 → read 返回 0=EOF) */
|
||||
if (g_ams_out_n > 0) { /* 未完结尾行:强制提交 */
|
||||
g_ams_out_line[g_ams_out_n] = '\0';
|
||||
ams_out_line(a, pid, g_ams_out_line);
|
||||
ams_out_line(a->id, pid, g_ams_out_line);
|
||||
g_ams_out_n = 0;
|
||||
}
|
||||
ams_out_flush();
|
||||
|
||||
@@ -27,7 +27,7 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
|
||||
notif_center.c app_launcher.c audio.c backlight.c net.c about.c \
|
||||
subpage.c wifi_page.c bt_page.c now_playing.c \
|
||||
font_theme.c keys.c util.c \
|
||||
meow_touch.c meow_settings.c meow_notif.c meow_media.c \
|
||||
meow_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \
|
||||
"../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o meow_launcherd
|
||||
echo "== meow_launcherd =="
|
||||
ls -la meow_launcherd
|
||||
|
||||
@@ -467,6 +467,11 @@ static void menu_draw_grid_cell(const struct app *a, meow_img *icon, int x, int
|
||||
if (!a->ok) {
|
||||
int ew = ui_width("[缺失]", 14);
|
||||
ui_draw(x + (w - ew) / 2, name_y + meow_font_line_height(18) + 2, "[缺失]", th->error, 14);
|
||||
} else if (meow_bg_is_backgrounded(a->id)) {
|
||||
/* M4:后台保活中标注(播放中转后台的 App;run_menu 1s tick 按 change_seq 刷新) */
|
||||
int bw = ui_width("·后台", 14);
|
||||
ui_draw(x + (w - bw) / 2, name_y + meow_font_line_height(18) + 2, "·后台",
|
||||
th->on_surface_variant, 14);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,9 +501,11 @@ static void menu_draw_list_cell(const struct app *a, meow_img *icon, int x, int
|
||||
sub_y = y + 30;
|
||||
}
|
||||
ui_draw(tx, name_y, nm, a->ok ? th->on_surface : th->error, 20);
|
||||
/* 副标题:package(无则 id),on_surface_variant */
|
||||
char sub[40];
|
||||
snprintf(sub, sizeof sub, "%s", a->package[0] ? a->package : (a->id[0] ? a->id : ""));
|
||||
/* 副标题:package(无则 id),on_surface_variant;M4 后台保活中前缀「·后台 」 */
|
||||
char sub[48];
|
||||
snprintf(sub, sizeof sub, "%s%s",
|
||||
meow_bg_is_backgrounded(a->id) ? "·后台 " : "",
|
||||
a->package[0] ? a->package : (a->id[0] ? a->id : ""));
|
||||
if (sub[0]) {
|
||||
char subf[44];
|
||||
menu_name_fit(sub, w - (tx - x) - 12, 14, subf, sizeof subf);
|
||||
@@ -1621,6 +1628,12 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
|
||||
draw_clock(); /* Stage-One:喂 SDK 全局状态栏 + 标脏顶条;SDK present() 重绘 */
|
||||
fb_norm_buf0();
|
||||
meow_ui_flush(); /* blit 状态栏整条,不动列表 */
|
||||
/* M4:后台保活表变化(转后台/回前台/回收)→ 重绘列表刷新「·后台」标注 */
|
||||
{
|
||||
static uint32_t bg_seq_last = 0;
|
||||
uint32_t seq = meow_bgplay_change_seq();
|
||||
if (seq != bg_seq_last) { bg_seq_last = seq; dirty = 1; }
|
||||
}
|
||||
}
|
||||
|
||||
usleep(POLL_MS * 1000);
|
||||
@@ -1964,6 +1977,10 @@ int main(int argc, char **argv)
|
||||
"(state=%d napps=%d), drain-to-home immediately", state, napps);
|
||||
run_menu(apps, napps, state, sim_tap, sim_exit);
|
||||
logmsg("=== meow_launcherd exit (MeowOS mode) ===");
|
||||
/* M4:关机/退出前回收后台保活 App + 无头服务(有界 SIGTERM→SIGKILL→reap);
|
||||
* 清 §10.5 自抑标记,防 launcher 重启后残留标记抑制后续 App present。 */
|
||||
meow_bgplay_kill_all();
|
||||
unlink(NOTIF_ACTIVE);
|
||||
if (log_fd >= 0) { close(log_fd); log_fd = -1; }
|
||||
ipc_shutdown = 1; unlink(ipc_sock_path);
|
||||
unlink(PID_PATH);
|
||||
@@ -2168,6 +2185,8 @@ int main(int argc, char **argv)
|
||||
if (player_stopped) { fb_restore_player(); restore_backlight(); }
|
||||
cont_player();
|
||||
logmsg("=== meow_launcherd exit ===");
|
||||
meow_bgplay_kill_all(); /* M4:退出前回收后台保活 App + 无头服务(有界) */
|
||||
unlink(NOTIF_ACTIVE); /* M4:清 §10.5 自抑标记,防残留抑制后续 App present */
|
||||
if (log_fd >= 0) { close(log_fd); log_fd = -1; }
|
||||
ipc_shutdown = 1; unlink(ipc_sock_path);
|
||||
unlink(PID_PATH);
|
||||
|
||||
@@ -0,0 +1,509 @@
|
||||
/* meow_bgplay.c — M4 后台播放保活 + 无头后台服务运行时表(见 meow_bgplay.h 头注释)。 */
|
||||
#include "meow_bgplay.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
static meow_bgplay_ops g_ops;
|
||||
static meow_bg_entry_t g_bg[MEOW_BG_MAX];
|
||||
static meow_svc_rt_t g_svc[MEOW_SVC_RT_MAX];
|
||||
static uint32_t g_change_seq;
|
||||
|
||||
static const uint32_t k_backoff[] = {
|
||||
MEOW_SVC_BACKOFF_1, MEOW_SVC_BACKOFF_2, MEOW_SVC_BACKOFF_4, MEOW_SVC_BACKOFF_10
|
||||
};
|
||||
#define BACKOFF_STEPS ((int)(sizeof(k_backoff) / sizeof(k_backoff[0])))
|
||||
|
||||
/* SIGTERM 后 SIGKILL 兜底宽限(ms) */
|
||||
#define TERM_GRACE_MS 500u
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void bg_log(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char buf[384];
|
||||
|
||||
if (!g_ops.log) return;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
g_ops.log("%s", buf);
|
||||
}
|
||||
|
||||
static uint64_t bg_now_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
if (g_ops.now_ms) return g_ops.now_ms();
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u;
|
||||
}
|
||||
|
||||
static int bg_card_present(void)
|
||||
{
|
||||
return g_ops.card_present ? g_ops.card_present() : 1;
|
||||
}
|
||||
|
||||
void meow_bgplay_init(const meow_bgplay_ops *ops)
|
||||
{
|
||||
memset(&g_ops, 0, sizeof(g_ops));
|
||||
memset(g_bg, 0, sizeof(g_bg));
|
||||
memset(g_svc, 0, sizeof(g_svc));
|
||||
g_change_seq = 0;
|
||||
if (ops) g_ops = *ops;
|
||||
}
|
||||
|
||||
/* ================= 后台播放保活 ================= */
|
||||
|
||||
int meow_bg_should_keep(int bg_audio_declared, int media_playing)
|
||||
{
|
||||
return bg_audio_declared && media_playing;
|
||||
}
|
||||
|
||||
static meow_bg_entry_t *bg_find(const char *app_id)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MEOW_BG_MAX; i++)
|
||||
if (g_bg[i].used && strcmp(g_bg[i].app_id, app_id) == 0)
|
||||
return &g_bg[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card)
|
||||
{
|
||||
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;
|
||||
g_change_seq++;
|
||||
bg_log("[bgplay] 转后台保活: %s pid=%d on_card=%d", app_id, (int)pid, on_card);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return MEOW_ERR_NOMEM;
|
||||
}
|
||||
|
||||
int meow_bg_take(const char *app_id, int *out_fd, pid_t *pid)
|
||||
{
|
||||
meow_bg_entry_t *e;
|
||||
|
||||
if (!app_id || !app_id[0]) return -1;
|
||||
e = bg_find(app_id);
|
||||
if (!e) return -1;
|
||||
if (out_fd) *out_fd = e->out_fd;
|
||||
if (pid) *pid = e->pid;
|
||||
bg_log("[bgplay] 回前台: %s pid=%d", app_id, (int)e->pid);
|
||||
memset(e, 0, sizeof(*e));
|
||||
g_change_seq++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_bg_is_backgrounded(const char *app_id)
|
||||
{
|
||||
return app_id && app_id[0] && bg_find(app_id) != NULL;
|
||||
}
|
||||
|
||||
int meow_bg_count(void)
|
||||
{
|
||||
int i, n = 0;
|
||||
|
||||
for (i = 0; i < MEOW_BG_MAX; i++)
|
||||
if (g_bg[i].used) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
const meow_bg_entry_t *meow_bg_at(int i)
|
||||
{
|
||||
if (i < 0 || i >= MEOW_BG_MAX || !g_bg[i].used) return NULL;
|
||||
return &g_bg[i];
|
||||
}
|
||||
|
||||
uint32_t meow_bgplay_change_seq(void)
|
||||
{
|
||||
return g_change_seq;
|
||||
}
|
||||
|
||||
void meow_bg_tick(void)
|
||||
{
|
||||
uint64_t now = bg_now_ms();
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MEOW_BG_MAX; i++) {
|
||||
meow_bg_entry_t *e = &g_bg[i];
|
||||
int st;
|
||||
pid_t r;
|
||||
|
||||
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);
|
||||
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 */
|
||||
}
|
||||
}
|
||||
|
||||
r = waitpid(e->pid, &st, WNOHANG);
|
||||
if (r == e->pid) {
|
||||
char id[MEOW_APP_ID_MAX];
|
||||
pid_t pid = e->pid;
|
||||
|
||||
snprintf(id, sizeof(id), "%s", e->app_id);
|
||||
memset(e, 0, sizeof(*e));
|
||||
g_change_seq++;
|
||||
bg_log("[bgplay] 后台 App 退出: %s pid=%d status=0x%x", id, (int)pid, st);
|
||||
if (g_ops.on_bg_dead) g_ops.on_bg_dead(id, pid, st);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= 无头后台服务 ================= */
|
||||
|
||||
static meow_svc_rt_t *svc_find_mut(const char *app_id, const char *svc)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
|
||||
if (g_svc[i].used && strcmp(g_svc[i].app_id, app_id) == 0 &&
|
||||
strcmp(g_svc[i].svc, svc) == 0)
|
||||
return &g_svc[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const meow_svc_rt_t *meow_svc_find(const char *app_id, const char *svc)
|
||||
{
|
||||
if (!app_id || !svc) return NULL;
|
||||
return svc_find_mut(app_id, svc);
|
||||
}
|
||||
|
||||
/* spawn 服务子进程:stdio→/dev/null,env={PATH,HOME,MEOW_SERVICE[,MEOW_LOG_DIR/MEOW_LOG_APP]}。
|
||||
* 父进程返回 pid;子进程不返回(execve 或 _exit(127))。 */
|
||||
static pid_t svc_spawn(meow_svc_rt_t *s)
|
||||
{
|
||||
char env_service[32];
|
||||
char env_logdir[160];
|
||||
char env_logapp[64];
|
||||
char *envp[6];
|
||||
int n = 0;
|
||||
pid_t pid;
|
||||
|
||||
envp[n++] = (char *)"PATH=/usr/bin:/bin:/usr/sbin:/sbin";
|
||||
envp[n++] = (char *)"HOME=/root";
|
||||
snprintf(env_service, sizeof(env_service), "MEOW_SERVICE=%s", s->svc);
|
||||
envp[n++] = env_service;
|
||||
if (g_ops.log_dir && g_ops.log_dir[0]) {
|
||||
snprintf(env_logdir, sizeof(env_logdir), "MEOW_LOG_DIR=%s", g_ops.log_dir);
|
||||
envp[n++] = env_logdir;
|
||||
snprintf(env_logapp, sizeof(env_logapp), "MEOW_LOG_APP=%s", s->app_id);
|
||||
envp[n++] = env_logapp;
|
||||
}
|
||||
envp[n] = NULL;
|
||||
|
||||
pid = fork();
|
||||
if (pid != 0) return pid; /* 父:pid 或 -1 */
|
||||
|
||||
/* 子进程:脱离终端会话,stdio 全部重定向 /dev/null(无头服务) */
|
||||
setsid();
|
||||
{
|
||||
int dn = open("/dev/null", O_RDWR);
|
||||
if (dn >= 0) {
|
||||
dup2(dn, STDIN_FILENO);
|
||||
dup2(dn, STDOUT_FILENO);
|
||||
dup2(dn, STDERR_FILENO);
|
||||
if (dn > STDERR_FILENO) close(dn);
|
||||
}
|
||||
}
|
||||
{
|
||||
char *argv[2];
|
||||
|
||||
argv[0] = s->exec;
|
||||
argv[1] = NULL;
|
||||
execve(s->exec, argv, envp);
|
||||
}
|
||||
_exit(127); /* exec 失败:父进程 tick 回收后按退避/熔断处理 */
|
||||
}
|
||||
|
||||
int meow_svc_register(const char *app_id, const char *svc, const char *exec,
|
||||
int restart, int max_restart, int window_ms,
|
||||
int on_card, int reg_fd)
|
||||
{
|
||||
meow_svc_rt_t *s;
|
||||
pid_t pid;
|
||||
int i;
|
||||
|
||||
if (!app_id || !app_id[0] || !svc || !svc[0] || !exec || !exec[0])
|
||||
return MEOW_ERR_INVAL;
|
||||
if (access(exec, X_OK) != 0) return MEOW_ERR_NOENT;
|
||||
|
||||
s = svc_find_mut(app_id, svc);
|
||||
if (s) {
|
||||
if (s->pid > 0) return MEOW_ERR_EXISTS;
|
||||
if (!s->circuit) return MEOW_ERR_EXISTS; /* 退避排程中也视为在册 */
|
||||
/* 熔断槽重注册:复位熔断,重新武装 */
|
||||
bg_log("[bgplay] 服务熔断复位重注册: %s/%s", app_id, svc);
|
||||
s->circuit = 0;
|
||||
s->stopping = 0;
|
||||
s->restart_count = 0;
|
||||
s->window_start_ms = bg_now_ms();
|
||||
s->next_retry_ms = 0;
|
||||
s->reg_fd = reg_fd;
|
||||
s->on_card = on_card;
|
||||
} else {
|
||||
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
|
||||
if (!g_svc[i].used) break;
|
||||
if (i >= MEOW_SVC_RT_MAX) return MEOW_ERR_NOMEM;
|
||||
s = &g_svc[i];
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->used = 1;
|
||||
snprintf(s->app_id, sizeof(s->app_id), "%s", app_id);
|
||||
snprintf(s->svc, sizeof(s->svc), "%s", svc);
|
||||
snprintf(s->exec, sizeof(s->exec), "%s", exec);
|
||||
s->restart = restart;
|
||||
s->max_restart = max_restart;
|
||||
s->window_ms = window_ms;
|
||||
s->on_card = on_card;
|
||||
s->reg_fd = reg_fd;
|
||||
s->window_start_ms = bg_now_ms();
|
||||
}
|
||||
|
||||
pid = svc_spawn(s);
|
||||
if (pid < 0) {
|
||||
if (s->pid == 0 && s->restart_count == 0 && !s->circuit)
|
||||
memset(s, 0, sizeof(*s)); /* 新槽首次 fork 即败:不留尸槽 */
|
||||
return MEOW_ERR_IO;
|
||||
}
|
||||
s->pid = pid;
|
||||
s->term_at_ms = 0;
|
||||
bg_log("[bgplay] 服务拉起: %s/%s pid=%d restart=%d max=%d",
|
||||
app_id, svc, (int)pid, restart, max_restart);
|
||||
return (int)pid;
|
||||
}
|
||||
|
||||
int meow_svc_stop(const char *app_id, const char *svc)
|
||||
{
|
||||
meow_svc_rt_t *s;
|
||||
|
||||
if (!app_id || !svc) return MEOW_ERR_INVAL;
|
||||
s = svc_find_mut(app_id, svc);
|
||||
if (!s) return MEOW_ERR_NOENT;
|
||||
bg_log("[bgplay] 服务停止: %s/%s pid=%d", app_id, svc, (int)s->pid);
|
||||
if (s->pid > 0) {
|
||||
s->stopping = 1;
|
||||
kill(s->pid, SIGTERM);
|
||||
s->term_at_ms = bg_now_ms();
|
||||
} else {
|
||||
/* 未在跑(退避中/熔断):直接清槽 */
|
||||
if (g_ops.on_svc_dead) g_ops.on_svc_dead(s->app_id, s->svc);
|
||||
memset(s, 0, sizeof(*s));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 服务死亡后的退避/熔断判定。返回 1=已排程重启;0=槽已清或熔断。 */
|
||||
static int svc_on_exit(meow_svc_rt_t *s, int st)
|
||||
{
|
||||
uint64_t now = bg_now_ms();
|
||||
|
||||
bg_log("[bgplay] 服务退出: %s/%s pid=%d status=0x%x stopping=%d",
|
||||
s->app_id, s->svc, (int)s->pid, st, s->stopping);
|
||||
s->pid = 0;
|
||||
|
||||
if (s->stopping || !s->restart) {
|
||||
if (g_ops.on_svc_dead) g_ops.on_svc_dead(s->app_id, s->svc);
|
||||
memset(s, 0, sizeof(*s));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 滑窗:超窗复位计数 */
|
||||
if (s->window_ms > 0 && now - s->window_start_ms >= (uint64_t)s->window_ms) {
|
||||
s->restart_count = 0;
|
||||
s->window_start_ms = now;
|
||||
}
|
||||
s->restart_count++;
|
||||
|
||||
if (s->max_restart > 0 && s->restart_count > s->max_restart) {
|
||||
/* 熔断:清通知 + 广播 MSG_SYS{SVC_DEAD},槽保留待重注册复位 */
|
||||
m_sys_t m;
|
||||
|
||||
s->circuit = 1;
|
||||
s->next_retry_ms = 0;
|
||||
bg_log("[bgplay] 服务熔断: %s/%s 窗口内重启 %d 次超 max=%d",
|
||||
s->app_id, s->svc, s->restart_count, s->max_restart);
|
||||
if (g_ops.notif_clear_app)
|
||||
g_ops.notif_clear_app(s->app_id);
|
||||
if (g_ops.send_frame && s->reg_fd >= 0) {
|
||||
memset(&m, 0, sizeof(m));
|
||||
m.event = MEOW_SYS_SVC_DEAD;
|
||||
snprintf(m.app_id, sizeof(m.app_id), "%s", s->app_id);
|
||||
snprintf(m.svc, sizeof(m.svc), "%s", s->svc);
|
||||
g_ops.send_frame(s->reg_fd, MSG_SYS, 0, &m, sizeof(m));
|
||||
}
|
||||
if (g_ops.on_svc_dead) g_ops.on_svc_dead(s->app_id, s->svc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
int step = s->restart_count - 1;
|
||||
uint32_t dly;
|
||||
|
||||
if (step >= BACKOFF_STEPS) step = BACKOFF_STEPS - 1;
|
||||
dly = k_backoff[step];
|
||||
s->next_retry_ms = now + dly;
|
||||
bg_log("[bgplay] 服务退避重启: %s/%s 第 %d 次,%ums 后拉起",
|
||||
s->app_id, s->svc, s->restart_count, (unsigned)dly);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void meow_svc_tick(void)
|
||||
{
|
||||
uint64_t now = bg_now_ms();
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MEOW_SVC_RT_MAX; i++) {
|
||||
meow_svc_rt_t *s = &g_svc[i];
|
||||
|
||||
if (!s->used) continue;
|
||||
|
||||
/* 卡拔例外:exec 在卡上 → SIGTERM + stopping=1(不重启防空转死循环) */
|
||||
if (s->on_card && s->pid > 0 && !bg_card_present() && !s->stopping) {
|
||||
bg_log("[bgplay] 卡拔出,终止卡上服务: %s/%s pid=%d",
|
||||
s->app_id, s->svc, (int)s->pid);
|
||||
s->stopping = 1;
|
||||
kill(s->pid, SIGTERM);
|
||||
s->term_at_ms = now;
|
||||
}
|
||||
/* SIGTERM 宽限超时 → SIGKILL */
|
||||
if (s->stopping && s->pid > 0 && s->term_at_ms &&
|
||||
now - s->term_at_ms >= TERM_GRACE_MS) {
|
||||
kill(s->pid, SIGKILL);
|
||||
s->term_at_ms = now;
|
||||
}
|
||||
|
||||
if (s->pid > 0) {
|
||||
int st;
|
||||
pid_t r = waitpid(s->pid, &st, WNOHANG);
|
||||
|
||||
if (r == s->pid) svc_on_exit(s, st);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 退避到点 → 重新拉起 */
|
||||
if (!s->circuit && !s->stopping && s->restart &&
|
||||
s->next_retry_ms && now >= s->next_retry_ms) {
|
||||
pid_t pid = svc_spawn(s);
|
||||
|
||||
s->next_retry_ms = 0;
|
||||
if (pid > 0) {
|
||||
s->pid = pid;
|
||||
bg_log("[bgplay] 服务重启: %s/%s pid=%d(第 %d 次)",
|
||||
s->app_id, s->svc, (int)pid, s->restart_count);
|
||||
} else {
|
||||
/* fork 失败:压到下一档重试 */
|
||||
s->next_retry_ms = now + k_backoff[BACKOFF_STEPS - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= 关机前回收 ================= */
|
||||
|
||||
void meow_bgplay_kill_all(void)
|
||||
{
|
||||
uint64_t deadline;
|
||||
int i;
|
||||
|
||||
/* best-effort 广播 SHUTDOWN */
|
||||
if (g_ops.send_frame) {
|
||||
m_sys_t m;
|
||||
|
||||
memset(&m, 0, sizeof(m));
|
||||
m.event = MEOW_SYS_SHUTDOWN;
|
||||
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
|
||||
if (g_svc[i].used && g_svc[i].reg_fd >= 0)
|
||||
g_ops.send_frame(g_svc[i].reg_fd, MSG_SYS, 0, &m, sizeof(m));
|
||||
}
|
||||
|
||||
for (i = 0; i < MEOW_BG_MAX; i++)
|
||||
if (g_bg[i].used && g_bg[i].pid > 0) kill(g_bg[i].pid, SIGTERM);
|
||||
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
|
||||
if (g_svc[i].used && g_svc[i].pid > 0) kill(g_svc[i].pid, SIGTERM);
|
||||
|
||||
/* 有界等 1s(轮询 reap,期间 tick 语义不在——直接 waitpid) */
|
||||
deadline = bg_now_ms() + 1000u;
|
||||
while (bg_now_ms() < deadline) {
|
||||
int alive = 0;
|
||||
|
||||
for (i = 0; i < MEOW_BG_MAX; i++) {
|
||||
if (!g_bg[i].used) continue;
|
||||
if (waitpid(g_bg[i].pid, NULL, WNOHANG) == g_bg[i].pid)
|
||||
memset(&g_bg[i], 0, sizeof(g_bg[i]));
|
||||
else
|
||||
alive++;
|
||||
}
|
||||
for (i = 0; i < MEOW_SVC_RT_MAX; i++) {
|
||||
if (!g_svc[i].used) continue;
|
||||
if (waitpid(g_svc[i].pid, NULL, WNOHANG) == g_svc[i].pid)
|
||||
memset(&g_svc[i], 0, sizeof(g_svc[i]));
|
||||
else
|
||||
alive++;
|
||||
}
|
||||
if (!alive) break;
|
||||
usleep(50 * 1000);
|
||||
}
|
||||
|
||||
/* 兜底 SIGKILL + 有界 reap(waitpid 阻塞上限由 SIGKILL 必死保证) */
|
||||
for (i = 0; i < MEOW_BG_MAX; i++)
|
||||
if (g_bg[i].used) {
|
||||
kill(g_bg[i].pid, SIGKILL);
|
||||
waitpid(g_bg[i].pid, NULL, 0);
|
||||
memset(&g_bg[i], 0, sizeof(g_bg[i]));
|
||||
}
|
||||
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
|
||||
if (g_svc[i].used) {
|
||||
kill(g_svc[i].pid, SIGKILL);
|
||||
waitpid(g_svc[i].pid, NULL, 0);
|
||||
memset(&g_svc[i], 0, sizeof(g_svc[i]));
|
||||
}
|
||||
g_change_seq++;
|
||||
bg_log("[bgplay] kill_all 完成(关机/退出回收)");
|
||||
}
|
||||
|
||||
void meow_bgplay_dump(const char *prefix)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MEOW_BG_MAX; i++)
|
||||
if (g_bg[i].used)
|
||||
bg_log("%s bg[%d] %s pid=%d on_card=%d", prefix, i,
|
||||
g_bg[i].app_id, (int)g_bg[i].pid, g_bg[i].on_card);
|
||||
for (i = 0; i < MEOW_SVC_RT_MAX; i++)
|
||||
if (g_svc[i].used)
|
||||
bg_log("%s svc[%d] %s/%s pid=%d restart=%d count=%d circuit=%d",
|
||||
prefix, i, g_svc[i].app_id, g_svc[i].svc, (int)g_svc[i].pid,
|
||||
g_svc[i].restart, g_svc[i].restart_count, g_svc[i].circuit);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/* meow_bgplay.h — MeowOS M4:后台播放保活 + 无头后台服务运行时表(FRAMEWORK_DESIGN §9)。
|
||||
*
|
||||
* 定位:
|
||||
* ① 后台播放保活——退出播放中(声明 background_audio=true 且媒体会话 PLAYING)的 App
|
||||
* 不 SIGTERM:进程保留(音频经 audiod socket 天然续播),UI 冻结走 §10.5 的
|
||||
* /tmp/meow_notif_active SDK 自抑机制(present 画进 back_buf 不上屏 + 输入排空)。
|
||||
* **不做字面 SIGSTOP**:SIGSTOP 会冻结同进程播放线程、断供 PCM(§10.5 真机实证的坑)。
|
||||
* 点图标/媒体卡片回前台=take 恢复原进程;停播后再退出=真退(走原 SIGTERM 序列)。
|
||||
* ② 无头后台服务——MSG_REGISTER 注册 app.json `service.<svc>` 声明的服务:launcher
|
||||
* spawn 子进程(无 UI,stdio→/dev/null),死亡按 1/2/4/10s 退避重启,超 max_restart
|
||||
* 熔断(清该 App 通知 + 经注册连接广播 MSG_SYS{SVC_DEAD}),重注册可复位熔断。
|
||||
*
|
||||
* 边界:
|
||||
* - 卡拔出例外:二进制在 TF 卡上的后台 App/服务仍 SIGTERM(防 SIGBUS),不适用保活。
|
||||
* - 崩溃照收:waitpid WNOHANG 回收,异常退出经 on_bg_dead 回调走 crash 上报。
|
||||
* - 关机前回收:meow_bgplay_kill_all() 有界 SIGTERM→SIGKILL→reap。
|
||||
*
|
||||
* 自包含:不依赖 launcher static 符号;日志/时钟/卡检测/通知清理/发帧全经
|
||||
* meow_bgplay_ops 注入(launcher 用 logmsg/sd_device_present/ipc_send;宿主单测用假件)。
|
||||
* 单一写者=launcher 主循环(meow_ipc_process 排空点挂 tick),无锁。可独立单测(test_bgplay.c)。
|
||||
*/
|
||||
#ifndef MEOW_BGPLAY_H
|
||||
#define MEOW_BGPLAY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
#include "meow_ipc.h"
|
||||
|
||||
/* 后台保活 App 上限(同时驻留后台的播放中 App) */
|
||||
#define MEOW_BG_MAX 4
|
||||
/* 无头后台服务运行时槽上限(与 meow_acct.svcs 的 MEOW_MAX_SVCS 对齐) */
|
||||
#define MEOW_SVC_RT_MAX 8
|
||||
|
||||
/* 退避重启档位(ms):1s/2s/4s/10s 封顶 */
|
||||
#define MEOW_SVC_BACKOFF_1 1000u
|
||||
#define MEOW_SVC_BACKOFF_2 2000u
|
||||
#define MEOW_SVC_BACKOFF_4 4000u
|
||||
#define MEOW_SVC_BACKOFF_10 10000u
|
||||
|
||||
/* 后台保活 App 槽。out_fd=其 stdout 管道读端(保活期继续 drain,防 App 写满管道阻塞)。 */
|
||||
typedef struct meow_bg_entry {
|
||||
int used;
|
||||
char app_id[MEOW_APP_ID_MAX];
|
||||
pid_t pid;
|
||||
int out_fd;
|
||||
int on_card; /* 二进制在 TF 卡上(卡拔需 SIGTERM 防 SIGBUS) */
|
||||
uint64_t term_at_ms; /* 卡拔 SIGTERM 发出时刻(0=未发;SIGKILL 兜底计时) */
|
||||
} meow_bg_entry_t;
|
||||
|
||||
/* 无头后台服务运行时槽(静态声明在 meow_acct.svcs,此处为运行态)。 */
|
||||
typedef struct meow_svc_rt {
|
||||
int used;
|
||||
char app_id[MEOW_APP_ID_MAX];
|
||||
char svc[16];
|
||||
char exec[129];
|
||||
pid_t pid; /* >0 在跑;0=等待退避重启/已熔断 */
|
||||
int reg_fd; /* 注册方 IPC 连接 fd(熔断广播投递用;-1=静态自动拉起) */
|
||||
int restart; /* 1=死亡后退避重启 */
|
||||
int max_restart; /* 熔断阈值(窗口内重启次数上限;0=不熔断) */
|
||||
int window_ms; /* 重启窗口(0=累计不滑窗) */
|
||||
int restart_count; /* 当前窗口内已重启次数 */
|
||||
int circuit; /* 1=已熔断(清通知+广播过,不再拉起;重注册复位) */
|
||||
int stopping; /* 1=主动停止/卡拔终止中(SIGTERM 已发,不重启) */
|
||||
int on_card; /* exec 在 TF 卡上 */
|
||||
uint64_t window_start_ms; /* 滑窗起点 */
|
||||
uint64_t next_retry_ms; /* 退避下次拉起时刻(0=无排程) */
|
||||
uint64_t term_at_ms; /* SIGTERM 发出时刻(0=未发;500ms 未死 SIGKILL) */
|
||||
} meow_svc_rt_t;
|
||||
|
||||
/* 依赖注入。全 NULL 可用(log 静默、now_ms 内部 CLOCK_MONOTONIC、card_present 恒 1、
|
||||
* 通知清理/发帧跳过)。
|
||||
* on_bg_dead:后台保活 App 进程退出(status=waitpid 原始状态;正常/异常由回调方判);
|
||||
* on_svc_dead:服务槽最终消亡(stopping 走完/不重启死亡;熔断不算消亡,槽保留待复位);
|
||||
* notif_clear_app:熔断时清该 App 全部通知,返回清除条数(<0=失败);
|
||||
* send_frame:熔断广播 MSG_SYS(launcher 侧须先查连接存活再发);
|
||||
* log_dir:svc 子进程 MEOW_LOG_DIR 注入(NULL 不注入)。 */
|
||||
typedef struct meow_bgplay_ops {
|
||||
void (*log)(const char *fmt, ...);
|
||||
uint64_t (*now_ms)(void);
|
||||
int (*card_present)(void);
|
||||
void (*on_bg_dead)(const char *app_id, pid_t pid, int status);
|
||||
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,
|
||||
const void *payload, uint32_t len);
|
||||
const char *log_dir;
|
||||
} meow_bgplay_ops;
|
||||
|
||||
/* 注入 ops(拷贝);NULL=全清空复位(含两张表)。launcher 在 meow_ipc_init 末尾调一次。 */
|
||||
void meow_bgplay_init(const meow_bgplay_ops *ops);
|
||||
|
||||
/* ---- 后台播放保活判定(纯函数,无副作用)---- */
|
||||
/* 退出时是否转后台保活:声明 background_audio 且该 App 媒体会话正在播放。 */
|
||||
int meow_bg_should_keep(int bg_audio_declared, int media_playing);
|
||||
|
||||
/* ---- 后台保活 App 表 ---- */
|
||||
/* 转后台:登记 app_id/pid/out_fd。返回 0=成功;MEOW_ERR_EXISTS=已在后台;MEOW_ERR_NOMEM=满。 */
|
||||
int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card);
|
||||
/* 回前台:按 app_id 摘除槽(进程归还前台会话)。成功返回 0 且 *out_fd(可 NULL)带回
|
||||
* 管道读端;未在后台返回 -1。 */
|
||||
int meow_bg_take(const char *app_id, int *out_fd, pid_t *pid);
|
||||
/* 该 App 是否在后台保活。 */
|
||||
int meow_bg_is_backgrounded(const char *app_id);
|
||||
/* 当前后台保活数。 */
|
||||
int meow_bg_count(void);
|
||||
/* 取第 i 槽(0..MEOW_BG_MAX-1)只读指针;空槽/越界返回 NULL。供 drain out_fd。 */
|
||||
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。 */
|
||||
void meow_bg_tick(void);
|
||||
|
||||
/* ---- 无头后台服务 ---- */
|
||||
/* 注册/拉起服务。exec 须可执行(先 access(X_OK),拒绝返回 MEOW_ERR_NOENT 不污染槽)。
|
||||
* 返回 pid(>0)=已拉起;MEOW_ERR_EXISTS=同名服务在跑;MEOW_ERR_NOENT=exec 不可执行;
|
||||
* MEOW_ERR_NOMEM=槽满;MEOW_ERR_IO=fork 失败。同 svc 已熔断槽重注册=复位熔断重新拉起。 */
|
||||
int meow_svc_register(const char *app_id, const char *svc, const char *exec,
|
||||
int restart, int max_restart, int window_ms,
|
||||
int on_card, int reg_fd);
|
||||
/* 请求停止服务:SIGTERM,标记 stopping(不触发重启)。返回 0=已受理;MEOW_ERR_NOENT=无此服务。 */
|
||||
int meow_svc_stop(const char *app_id, const char *svc);
|
||||
/* 查服务槽(只读);无返回 NULL。 */
|
||||
const meow_svc_rt_t *meow_svc_find(const char *app_id, const char *svc);
|
||||
/* 周期调:回收死亡子进程;按 restart/退避档 1/2/4/10s 重启;滑窗复位计数;超
|
||||
* max_restart 熔断(notif_clear_app + send_frame MSG_SYS{SVC_DEAD} + circuit=1);
|
||||
* 卡拔:on_card 槽 SIGTERM + stopping=1(exec 随卡消失,不重启防空转)。 */
|
||||
void meow_svc_tick(void);
|
||||
|
||||
/* 关机前回收:向各服务注册连接广播 MSG_SYS{SHUTDOWN}(best-effort),随后全部
|
||||
* bg/svc 子进程 SIGTERM→有界等 1s→SIGKILL→有界 reap。调用方负责 unlink NOTIF_ACTIVE。 */
|
||||
void meow_bgplay_kill_all(void);
|
||||
|
||||
/* 调试打印(经 ops->log,每槽一行)。 */
|
||||
void meow_bgplay_dump(const char *prefix);
|
||||
|
||||
#endif /* MEOW_BGPLAY_H */
|
||||
@@ -13,10 +13,11 @@
|
||||
* uint32_t len; = payload 字节数
|
||||
* };
|
||||
*
|
||||
* 本阶段(D · M0+M1+M2+M3+M4 前置)通讯子集:MSG_HELLO/ACK/ERR + MSG_ACT_DECLARE/QUERY/RESULT +
|
||||
* 本阶段(D · M0+M1+M2+M3+M4)通讯子集:MSG_HELLO/ACK/ERR + MSG_ACT_DECLARE/QUERY/RESULT +
|
||||
* MSG_ACT_FOCUS/BACK + MSG_NOTIFY/PROGRESS/QUERY/DISMISS* + MSG_NAV(导航栈上报/退出)
|
||||
* + MSG_META/MSG_PLAYCTL(媒体会话,阶段 D 附录 B)。
|
||||
* picker(0x0070/0x0071)仅预留协议位,本批不实现。
|
||||
* + MSG_META/MSG_PLAYCTL(媒体会话,阶段 D 附录 B)
|
||||
* + MSG_REGISTER/SERVICE_OK/SERVICE_STOP/MSG_SYS(后台服务注册/停止/熔断事件,M4)。
|
||||
* picker(0x0070/0x0071)已落地(二期)。
|
||||
*/
|
||||
#ifndef MEOW_IPC_H
|
||||
#define MEOW_IPC_H
|
||||
@@ -157,6 +158,38 @@ typedef struct m_err {
|
||||
char msg[64];
|
||||
} m_err_t;
|
||||
|
||||
/* ================= 后台服务注册(M4,FRAMEWORK_DESIGN §9) = */
|
||||
|
||||
/* MSG_REGISTER ↑:无头后台服务注册。调用方(App 前台进程)须:
|
||||
* ① app.json 声明 `background=true` 或 HELLO flags 含 MF_BACKGROUND(否则 ERR_PERM);
|
||||
* ② app.json `service.<svc>=mode:restart:max:window_ms` 静态声明该服务(否则 ERR_NOENT);
|
||||
* ③ 注册后保持 IPC 连接不断(launcher 以连接存活做熔断广播的投递判定)。
|
||||
* svc=NUL 结尾服务名(≤15 字符);flags 按位:MF_BACKGROUND(申请后台常驻)。 */
|
||||
typedef struct m_register {
|
||||
char app_id[MEOW_APP_ID_MAX]; /* 32 */
|
||||
char svc[16]; /* 服务名(app.json service.<svc>) */
|
||||
uint32_t flags; /* MF_BACKGROUND 等 */
|
||||
} m_register_t; /* 52 B */
|
||||
|
||||
/* MSG_SERVICE_OK ↓:用 m_ack_t(code=0;status=服务子进程 pid)。 */
|
||||
|
||||
/* MSG_SERVICE_STOP ↑:请求停止自己已注册的服务(SIGTERM,不触发重启)。 */
|
||||
typedef struct m_service_stop {
|
||||
char app_id[MEOW_APP_ID_MAX]; /* 32 */
|
||||
char svc[16];
|
||||
} m_service_stop_t; /* 48 B */
|
||||
|
||||
/* MSG_SYS ↓:系统事件广播(守护关闭/服务死亡/播放器状态)。
|
||||
* event:MEOW_SYS_*;app_id/svc 为事件主体(SHUTDOWN 时可空)。 */
|
||||
enum { MEOW_SYS_SHUTDOWN = 1, /* 守护进程即将关闭(关机/重启前回收) */
|
||||
MEOW_SYS_SVC_DEAD = 2, /* 后台服务熔断(超 max_restart 不再拉起) */
|
||||
MEOW_SYS_PLAYER_STATE = 3 }; /* 播放器状态变更(预留) */
|
||||
typedef struct m_sys {
|
||||
uint32_t event; /* MEOW_SYS_* */
|
||||
char app_id[MEOW_APP_ID_MAX]; /* 32 */
|
||||
char svc[16]; /* SVC_DEAD 时为熔断服务名 */
|
||||
} m_sys_t; /* 52 B */
|
||||
|
||||
/* MSG_ACT_QUERY ↑:解析某 app 的 activity 链路 */
|
||||
typedef struct m_act_query {
|
||||
char app_id[MEOW_APP_ID_MAX];
|
||||
|
||||
@@ -37,6 +37,7 @@ extern "C" {
|
||||
#include "meow_settings.h" /* B2a:统一系统设置后端(SYSTEM_SETTINGS.md §3/MEOWOS_API §2) */
|
||||
#include "meow_notif.h" /* M2a:通知存储 + 分发(NOTIFICATION_SYSTEM.md §4/§7/§8) */
|
||||
#include "meow_media.h" /* 阶段D附录B:媒体会话注册表(MSG_META/MSG_PLAYCTL/最近活跃源) */
|
||||
#include "meow_bgplay.h" /* M4:后台播放保活 + 无头后台服务运行时表(§9) */
|
||||
#include "meow_ui.h" /* A-1/A-2:launcher 用 SDK 渲染栈(meow_ui_fill_rect/text/flush/theme) */
|
||||
#include "meow_ui_internal.h" /* A-1/A-2:launcher 与 SDK 共用 back_buf + 手动脏模型桥接(mark_full/back_buf) */
|
||||
#include "meow_notif_panel.h" /* 阶段B:通知中心面板纯逻辑(header-only,可单测) */
|
||||
@@ -305,6 +306,8 @@ struct app {
|
||||
char immersive; /* '1'=immersive=true(隐藏虚拟返回键,返回手势归 SDK) */
|
||||
char fullscreen; /* '1'=app.json fullscreen=true(注入 MEOW_FULLSCREEN=1,SDK 预先设全屏) */
|
||||
char fonts; /* 字库档:0=默认4K常用 1=大字库GBK 2=Full(app.json "fonts";注入 MEOW_FONT_TIER) */
|
||||
char background; /* M4:'1'=app.json background=true(可注册无头后台服务) */
|
||||
char background_audio; /* M4:'1'=background_audio=true(播放中退出转后台保活,§9) */
|
||||
char icon[129]; /* icon= 路径 */
|
||||
int nroute; /* route.<id> 行数 */
|
||||
struct meow_route routes[MEOW_MAX_ROUTES];
|
||||
@@ -327,6 +330,8 @@ struct meow_acct {
|
||||
int nsvc;
|
||||
struct meow_svc svcs[MEOW_MAX_SVCS];
|
||||
int declared; /* 是否经 HELLO/ACT_DECLARE 运行时声明过 */
|
||||
char background; /* M4:'1'=app.json background=true(MSG_REGISTER 权限位0) */
|
||||
char background_audio; /* M4:'1'=background_audio=true(MSG_REGISTER MF_PLAYBACK 权限位1) */
|
||||
};
|
||||
|
||||
|
||||
@@ -663,6 +668,19 @@ int meow_ams_pending_start_set_ex(const char *pkg, const char *act,
|
||||
* util.c 转发运行时 declare 与 picker call/result 给 amsd 用。 */
|
||||
void meow_ams_send(const char *payload, int len);
|
||||
|
||||
/* ---- M4 后台保活/后台服务胶水(app_launcher.c 实现;util.c/main_menu.c 消费)---- */
|
||||
/* launcher 侧 meow_bgplay ops 组装(meow_ipc_init 末尾调一次)。 */
|
||||
void meow_bgplay_launcher_init(void);
|
||||
/* 后台表周期维护:drain 各 bg App 的 stdout 管道 + meow_bg_tick + meow_svc_tick。
|
||||
* 挂载点=meow_ipc_process 末尾(主循环单写者排空点,四处循环都经它)。 */
|
||||
void meow_bg_housekeep(void);
|
||||
/* 查静态声明的 service.<svc>;无返回 NULL。 */
|
||||
const struct meow_svc *meow_acct_svc_find(const struct meow_acct *ac, const char *svc);
|
||||
/* 后台权限位:bit0=background(可注册服务)bit1=background_audio(后台音频)。 */
|
||||
int meow_acct_background_perm(const struct meow_acct *ac);
|
||||
/* M4:服务 exec 路径约定 = App 二进制同目录下的 <svc> 名文件。 */
|
||||
void meow_svc_exec_path(const char *app_exec, const char *svc, char *out, int olen);
|
||||
|
||||
void meow_dac_vol_write(int pct);
|
||||
void meow_dac_vol_write_throttled(void);
|
||||
void meow_dirty_add(int x, int y, int w, int h);
|
||||
@@ -680,6 +698,11 @@ void meow_font_scan_presets(void);
|
||||
void meow_font_scan_presets_root(const char *card_root);
|
||||
int meow_ipc_init(void);
|
||||
void meow_ipc_process(void);
|
||||
/* M4:IPC 连接存活查询(熔断广播投递前判连接未断;util.c ipc_conns 表查 fd)。 */
|
||||
int meow_ipc_conn_alive(int fd);
|
||||
/* M4:ipc_send 公开包装(meow_bgplay send_frame 回调用;同 meow_media 的注入口径)。 */
|
||||
size_t meow_ipc_send_pub(int fd, uint16_t type, uint32_t req_id,
|
||||
const void *payload, uint32_t len);
|
||||
/* §10.5:读并清除待处理的 MSG_NOTIF_OPEN(App 前台顶部下滑请求展开通知中心叠加会话)。
|
||||
* 调用方=launch_app waitpid 循环;返回 1 并填 *out(待处理),0=无。 */
|
||||
int meow_notif_open_take(m_notif_open_t *out);
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,376 @@
|
||||
/* 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);
|
||||
* 5) bg 表满(MEOW_BG_MAX)→ MEOW_ERR_NOMEM;重复 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)。 */
|
||||
#define TEST_SVC_PATH "/tmp/meow_bgplay_test_svc.sh"
|
||||
static void write_test_svc(void)
|
||||
{
|
||||
FILE *f = fopen(TEST_SVC_PATH, "w");
|
||||
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.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 判定矩阵 ---- */
|
||||
CHECK(meow_bg_should_keep(0, 0) == 0, "should_keep(未声明,未播放)=0");
|
||||
CHECK(meow_bg_should_keep(1, 0) == 0, "should_keep(声明,未播放)=0");
|
||||
CHECK(meow_bg_should_keep(0, 1) == 0, "should_keep(未声明,播放)=0");
|
||||
CHECK(meow_bg_should_keep(1, 1) == 1, "should_keep(声明,播放)=1");
|
||||
|
||||
/* ---- 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_bg_should_keep(1, meow_media_app_playing("dev.test.bg1")) == 1,
|
||||
"播放中退出 → 应保活");
|
||||
|
||||
pid_t kid = spawn_sleeper();
|
||||
CHECK(kid > 0, "fork 假 App 子进程");
|
||||
CHECK(meow_bg_add("dev.test.bg1", kid, sp[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");
|
||||
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_playing("dev.test.bg1")) == 0,
|
||||
"停播后再退出 → should_keep=0(真退 SIGTERM)");
|
||||
kill(kid, SIGKILL); waitpid(kid, NULL, 0);
|
||||
close(sp[0]); close(sp[1]);
|
||||
meow_media_clear("dev.test.bg1");
|
||||
|
||||
/* ---- 3) 后台 App 崩溃照收 ---- */
|
||||
g_bg_dead = 0;
|
||||
kid = spawn_sleeper();
|
||||
CHECK(meow_bg_add("dev.test.crash", kid, -1, 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, "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;
|
||||
|
||||
/* ---- 5) 表满 / 重复 ---- */
|
||||
{
|
||||
pid_t k[MEOW_BG_MAX];
|
||||
char id[MEOW_APP_ID_MAX];
|
||||
int i, full = 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);
|
||||
}
|
||||
snprintf(id, sizeof id, "dev.test.full%d", MEOW_BG_MAX);
|
||||
{
|
||||
pid_t extra = spawn_sleeper();
|
||||
full = (meow_bg_add(id, extra, -1, 0) == MEOW_ERR_NOMEM);
|
||||
kill(extra, SIGKILL); waitpid(extra, NULL, 0); /* 未入表,就地清理 */
|
||||
}
|
||||
CHECK(full, "bg 表满 → MEOW_ERR_NOMEM");
|
||||
snprintf(id, sizeof id, "dev.test.full%d", 0);
|
||||
CHECK(meow_bg_add(id, 0, -1, 0) == MEOW_ERR_INVAL, "pid<=0 → INVAL");
|
||||
CHECK(meow_bg_add(id, k[0], -1, 0) == MEOW_ERR_EXISTS, "重复 app_id → EXISTS");
|
||||
for (i = 0; i < MEOW_BG_MAX; i++) {
|
||||
snprintf(id, sizeof id, "dev.test.full%d", i);
|
||||
meow_bg_take(id, NULL, NULL);
|
||||
kill(k[i], SIGKILL); waitpid(k[i], NULL, 0);
|
||||
}
|
||||
}
|
||||
CHECK(meow_bg_count() == 0, "take 全部后 bg 表空");
|
||||
|
||||
/* ================= 无头后台服务 ================= */
|
||||
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);
|
||||
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;
|
||||
}
|
||||
@@ -870,6 +870,22 @@ static pthread_t ipc_pthread;
|
||||
|
||||
static struct meow_ipc_conn ipc_conns[MEOW_IPC_CONNS];
|
||||
|
||||
/* M4:连接存活跟踪(ipc_thread 单写者维护计数;主循环 meow_ipc_conn_alive 读)。
|
||||
* 竞态最坏=误判一帧(best-effort 熔断广播可接受;ipc_conns 前 ipc_conn_count 项恒为
|
||||
* 最新存活集,因关闭时shift压实)。 */
|
||||
static volatile int ipc_conn_count = 0;
|
||||
|
||||
/* M4:查 fd 是否为当前存活 IPC 连接(熔断广播 MSG_SYS 投递前判定,防发到已断连接)。 */
|
||||
int meow_ipc_conn_alive(int fd)
|
||||
{
|
||||
int n = ipc_conn_count;
|
||||
if (fd < 0) return 0;
|
||||
if (n > MEOW_IPC_CONNS) n = MEOW_IPC_CONNS;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (ipc_conns[i].fd == fd) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* §10.5:待处理的 App 前台「展开通知中心」请求(ipc_dispatch 单写者置位;launch_app waitpid
|
||||
* 循环 meow_notif_open_take 读取并清除)。仅当 g_app_session_active(App 前台会话)期间才置位,
|
||||
* 否则回 ERR(无 App 前台,不展开)。 */
|
||||
@@ -1046,6 +1062,7 @@ static void *ipc_thread(void *arg)
|
||||
fds[nfds].events = POLLIN;
|
||||
fds[nfds].revents = 0;
|
||||
nfds++;
|
||||
ipc_conn_count = nfds - 1;
|
||||
logmsg("ipc: conn %d accept (nfds=%d)", c, nfds - 1);
|
||||
} else {
|
||||
close(c);
|
||||
@@ -1066,6 +1083,7 @@ static void *ipc_thread(void *arg)
|
||||
ipc_conns[j - 1] = ipc_conns[j];
|
||||
}
|
||||
nfds--;
|
||||
ipc_conn_count = nfds - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1091,6 +1109,9 @@ int meow_ipc_init(void)
|
||||
g_media_ops.log = logmsg;
|
||||
meow_media_init(&g_media_ops);
|
||||
|
||||
/* M4:注入后台保活/后台服务 ops(logmsg/sd_device_present/通知清理/熔断发帧)。 */
|
||||
meow_bgplay_launcher_init();
|
||||
|
||||
ipc_event_fd = eventfd(0, EFD_NONBLOCK);
|
||||
if (ipc_event_fd < 0)
|
||||
logmsg("ipc: eventfd fail %s (10ms poll drains anyway)", strerror(errno));
|
||||
@@ -1119,6 +1140,9 @@ void meow_ipc_process(void)
|
||||
ipc_head++;
|
||||
}
|
||||
__sync_synchronize();
|
||||
/* M4:后台表周期维护挂点(主循环单写者排空点,run_menu/前台会话/idle/ipc-only
|
||||
* 四处循环都经此处)——drain 后台 App stdout + 回收/卡拔 + 服务退避重启。 */
|
||||
meow_bg_housekeep();
|
||||
}
|
||||
|
||||
|
||||
@@ -1269,6 +1293,72 @@ static void ipc_dispatch(struct meow_ipc_msg *m)
|
||||
n.app_id, n.nav_type, n.depth, n.current);
|
||||
break;
|
||||
}
|
||||
/* M4 后台服务注册(FRAMEWORK_DESIGN §5/§9):无头服务 spawn/退避重启/熔断。
|
||||
* 校验链:acct 存在且 exec 非空(NOENT)→ 权限(app.json background=true 或
|
||||
* flags 含 MF_BACKGROUND,否则 PERM)→ service.<svc> 静态声明存在(NOENT)→
|
||||
* meow_svc_register 拉起。成功回 MSG_SERVICE_OK(m_ack_t:code=0,status=pid);
|
||||
* 失败回 MSG_ERR(仿 ACT_QUERY 模式)。注册后调用方须保持连接(熔断广播投递判定)。 */
|
||||
case MSG_REGISTER: {
|
||||
m_register_t r;
|
||||
memset(&r, 0, sizeof r);
|
||||
memcpy(&r, m->payload, m->plen < (uint16_t)sizeof r ? m->plen : (uint16_t)sizeof r);
|
||||
r.app_id[MEOW_APP_ID_MAX - 1] = 0;
|
||||
r.svc[15] = 0;
|
||||
struct meow_acct *ac = meow_acct_find(r.app_id);
|
||||
const struct meow_svc *sv = ac ? meow_acct_svc_find(ac, r.svc) : NULL;
|
||||
int code = MEOW_OK;
|
||||
int pid = 0;
|
||||
if (!ac || !ac->exec[0])
|
||||
code = MEOW_ERR_NOENT;
|
||||
else if (!(meow_acct_background_perm(ac) & 1) && !(r.flags & MF_BACKGROUND))
|
||||
code = MEOW_ERR_PERM;
|
||||
else if (!sv)
|
||||
code = MEOW_ERR_NOENT;
|
||||
else {
|
||||
char sexec[160];
|
||||
meow_svc_exec_path(ac->exec, r.svc, sexec, sizeof sexec);
|
||||
pid = meow_svc_register(r.app_id, r.svc, sexec,
|
||||
sv->restart, sv->max, sv->window_ms,
|
||||
strncmp(sexec, MEOWAPPS, strlen(MEOWAPPS)) == 0,
|
||||
m->fd);
|
||||
if (pid < 0) { code = pid; pid = 0; }
|
||||
}
|
||||
if (code == MEOW_OK) {
|
||||
m_ack_t a;
|
||||
a.code = MEOW_OK;
|
||||
a.status = (uint32_t)pid; /* 服务子进程 pid */
|
||||
ipc_send(m->fd, MSG_SERVICE_OK, m->req_id, &a, sizeof a);
|
||||
logmsg("ipc: REGISTER app=%s svc=%s -> SERVICE_OK pid=%d", r.app_id, r.svc, pid);
|
||||
} else {
|
||||
m_err_t e;
|
||||
memset(&e, 0, sizeof e);
|
||||
e.code = (uint32_t)code;
|
||||
const char *msg = (code == MEOW_ERR_PERM) ? "no background permission"
|
||||
: (code == MEOW_ERR_EXISTS) ? "service already running"
|
||||
: (code == MEOW_ERR_NOMEM) ? "service slots full"
|
||||
: "app/service not found";
|
||||
e.msg_len = (uint16_t)strlen(msg);
|
||||
memcpy(e.msg, msg, e.msg_len);
|
||||
ipc_send(m->fd, MSG_ERR, m->req_id, &e, sizeof e);
|
||||
logmsg("ipc: REGISTER app=%s svc=%s -> ERR(%d)", r.app_id, r.svc, code);
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* M4:请求停止自己已注册的无头服务(SIGTERM,标记 stopping 不触发退避重启)。 */
|
||||
case MSG_SERVICE_STOP: {
|
||||
m_service_stop_t s;
|
||||
memset(&s, 0, sizeof s);
|
||||
memcpy(&s, m->payload, m->plen < (uint16_t)sizeof s ? m->plen : (uint16_t)sizeof s);
|
||||
s.app_id[MEOW_APP_ID_MAX - 1] = 0;
|
||||
s.svc[15] = 0;
|
||||
int rc = meow_svc_stop(s.app_id, s.svc);
|
||||
m_ack_t a;
|
||||
a.code = (uint32_t)(rc < 0 ? -rc : MEOW_OK);
|
||||
a.status = 0;
|
||||
ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a);
|
||||
logmsg("ipc: SERVICE_STOP app=%s svc=%s -> ACK code=%u", s.app_id, s.svc, a.code);
|
||||
break;
|
||||
}
|
||||
/* AMSD_DESIGN §4.3:amsd `am start`/`am kill` 控制请求。主循环单写者仅置位 pending
|
||||
* (run_menu 前台循环 / launch_* waitpid 循环消费),不回执启动结果(一期 best-effort)。 */
|
||||
case MSG_AMS_START: {
|
||||
|
||||
Reference in new issue
Block a user