Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/meow_bgplay.c
T

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