/* meow_bgplay.c — M4 后台播放保活 + 无头后台服务运行时表(见 meow_bgplay.h 头注释)。 */ #include "meow_bgplay.h" #include #include #include #include #include #include #include #include #include #include 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); } } } /* 批③后台管理页:请求终止某个后台保活 App(走既有 SIGTERM→SIGKILL 真退序列)。 * 只置 term_at_ms(tick 续做 SIGTERM→TERM_GRACE_MS→SIGKILL→reap→on_bg_dead→摘表), * 与宽限超时/卡拔回收同一条终止通道,**不在此处直接 reap**(保持单一写者 tick 收口)。 */ 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", 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); }