Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/meow_bgplay.c
T
AskaEth 6b52740ff1 fix: 后台页杀按钮点不动 — app.json framework=1 被误当「不可杀」门控 + am kill 无前台会话不可执行
根因(2026-11-04 真机 dev.player.ncm pid=7520):
- bg_page.c bgm_app_meta 把 app.json 的 framework 字段映射进 bgmgr_row.framework,而
  bgmgr_can_kill 把它当「系统/框架 App 禁止 kill」。但 framework=1 在 MeowOS 里意为「用
  meow_ui.a 的 SDK 框架 App」(docs/APP_DEV_GUIDE.md §9,推荐值,真机 4 个 App 全写 1)
  → 后台页每行「杀」按钮被置灰,bgm_row_tap 命中后直接 return 1、不调 meow_bg_terminate;
  「一键清除后台」同理 0 行可杀。整条链路无日志,故只能靠翻 app.json 才定位(全部设备日志里
  "后台管理页终止后台App" 出现 0 次 = 4 次进页从未真正杀过)。
- 面板 killfg 正常是因为走 am kill → amsd → MSG_AMS_KILL,无 framework 门控;但它其实靠
  amsd 1200ms×2 超时兜底 kill(pid,SIGTERM) 才杀成:util.c 原本在无前台 App 会话时只回
  ACK -2((uint32_t)MEOW_ERR_NOENT),ACK 及时到达时 amsd 判「launcher refused」完全不杀
  (ams_20260915_031339.log 实证),ACK 迟到才侥幸成功(2026-11-04 真机)。

修复:
- meow_bgmgr: 门控位 framework → protected_app(系统级受保护标记;当前无 App 置位,launcher
  自身组件本就不在 App 表/后台表内)。bg_page 不再把 a->framework 送进门控。
- bg_page: 行副文本改存 ctx 持久缓冲(原 rbuf[].sub 指向 bgm_rows 栈帧 → 悬垂,副标题乱码);
  门控拒绝/未命中后台表各留一行 logmsg,「点了没反应」从此可查。
- util.c MSG_AMS_KILL: 先查后台保活表 → meow_bg_terminate 就地走真退序列(与页面「杀」按钮
  同一通道,故 am kill 与页面按钮行为对称),无前台会话也能杀;否则仍走 pending / ACK -2。
- amsd cmd_kill/cmd_start: ipc 失败原因细分(connect failed / timeout, no reply / ...),日志
  不再笼统写 "launcher offline"(响应串保留该字样,MeowPanel api_ams_ctl 依此映射 503)。
- meow_bgplay: meow_bg_terminate 注释/日志写明「后台管理页 / am kill」双入口同通道。

测试(宿主 gcc,产物在 $TMPDIR):
- test_bgpage 40 checks:新增 framework=1 仍判可杀且按钮真 terminate(本 bug 回归)、
  受保护行按钮不杀但留诊断日志、sub 持久化 + 栈抖动不变(悬垂回归)。
- test_bgmgr 30 checks:protected_app 门控矩阵 + framework=1 可杀回归。
- test_bgplay 92 PASS:新增 4b meow_bg_terminate 全链路(受理→term_at_ms→tick SIGTERM→
  on_bg_dead→清槽;空/不在表分别 INVAL/NOENT);TEST_SVC_PATH 改取 $TMPDIR
  (Termux 无 /tmp,原 fputs(NULL) 直接 abort)。
- amsd 宿主集成(fake launcher 桩,4 例):ACK 0→terminate 且不盲杀;ACK -2→refused 不盲杀;
  无 launcher→direct SIGTERM (connect failed);挂起不回→direct SIGTERM (timeout, no reply)。
0warning:util.c 与 main 同告警逐行一致(仅 SDK 头既有 typedef 重定义),其余文件干净。
2026-11-04 21:00:40 +08:00

623 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 <errno.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_resident, int keepalive_declared)
{
if (keepalive_declared) return 1; /* 通用常驻:进后台即保活 */
return bg_audio_declared && media_resident; /* 播放类:播放/暂停/缓冲都保活 */
}
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;
}
/* 填一槽(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) { 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;
}
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→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;
}
}
/* 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);
if (g_ops.on_bg_dead) g_ops.on_bg_dead(id, pid, st);
}
}
}
/* 批③后台管理页 / `am kill`:请求终止某个后台保活 App(走既有 SIGTERM→SIGKILL 真退序列)。
* 只置 term_at_ms(tick 续做 SIGTERM→TERM_GRACE_MS→SIGKILL→reap→on_bg_dead→摘表),
* 与宽限超时/卡拔回收同一条终止通道,**不在此处直接 reap**(保持单一写者 tick 收口)。
* 调用者:bg_page.c 行内「杀」/「一键清除」+ util.c MSG_AMS_KILL(`am kill`,无前台会话时)
* ——两个入口同一通道,故页面按钮与面板/CLI kill 行为对称。 */
int meow_bg_terminate(const char *app_id)
{
if (!app_id || !app_id[0]) return MEOW_ERR_INVAL;
int i;
for (i = 0; i < MEOW_BG_MAX; i++) {
meow_bg_entry_t *e = &g_bg[i];
if (e->used && e->pid > 0 && !strcmp(e->app_id, app_id)) {
if (e->term_at_ms == 0) {
bg_log("[bgplay] 用户终止后台App: %s pid=%d(后台管理页/am kill 同通道)",
app_id, (int)e->pid);
kill(e->pid, SIGTERM);
e->term_at_ms = bg_now_ms();
}
return 0;
}
}
return MEOW_ERR_NOENT;
}
/* ================= 无头后台服务 ================= */
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);
}