599 lines
20 KiB
C
599 lines
20 KiB
C
/* 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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ================= 无头后台服务 ================= */
|
||
|
||
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);
|
||
}
|