Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/meow_bgplay.h
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

168 lines
11 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.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
/* 批①+宽限:无声明/媒体不活跃 App 入后台的宽限期(默认 30 分钟)。
* 语义:转后台(bg_since_ms)后超 MEOW_BG_GRACE_MS 未再启动 → meow_bg_tick 走真退序列
* SIGTERM→SIGKILL 回收(摘表 + 通知 amsd)。**声明常驻(keepalive=1)或媒体活跃
* (meow_media_app_resident=1)不清**。本批为编译期常量(30*60*1000 = 1800000ms);
* 后续可进设置/按 App 可配(待拍板)。 */
#define MEOW_BG_GRACE_MS 1800000u
/* 无头后台服务运行时槽上限(与 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 兜底计时) */
uint64_t bg_since_ms; /* 批①:转后台时刻(monotonic ms;宽限超时回收/槽满 LRU 掐最久用) */
char keepalive; /* 批①+宽限:1=声明常驻(background_keepalive,无限期不清);0=宽限期类
* (未声明/background_audio,媒体不活跃时超 bg_grace_ms 回收) */
} 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 原始状态;正常/异常由回调方判);
* media_resident:该 App 媒体会话是否「活跃可驻留」(launcher 注入 meow_media_app_resident)。
* 批①+宽限用:宽限超时回收 / 槽满 LRU 判「媒体活跃不清不回收」;NULL=恒 0(视为不活跃)。
* 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);
int (*media_resident)(const char *app_id);
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);
/* ---- 后台播放保活判定(纯函数,无副作用)---- */
/* 退出时是否转后台保活(批①三参化):
* keepalive_declared=1(声明 background_keepalive)→ 恒保活(通用常驻,不依赖媒体会话);
* 否则 bg_audio_declared(声明 background_audio)且 media_resident(该 App 媒体会话活跃
* =PLAYING/PAUSED/BUFFER,见 meow_media_app_resident)→ 保活。
* 未声明/无活跃会话 → 0(走原真退序列)。 */
int meow_bg_should_keep(int bg_audio_declared, int media_resident, int keepalive_declared);
/* ---- 后台保活 App 表 ---- */
/* 转后台:登记 app_id/pid/out_fd,keepalive 标记常驻类型(1=background_keepalive 类=无限期;
* 0=宽限期类=未声明/background_audio,媒体不活跃时超 bg_grace_ms 回收)。批①+宽限:**全 App**
* nav-exit 都经此入表;槽满(MEOW_BG_MAX=4)时 LRU 回收最久未激活(bg_since_ms 最旧)的**未声明**
* 槽(不回收声明常驻/媒体活跃);全为声明常驻/媒体活跃 → MEOW_ERR_NOMEM(调用方回落真退,保安全)。
* 返回 0=成功;MEOW_ERR_EXISTS=已在后台;MEOW_ERR_NOMEM=满且无可回收。 */
int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card, int keepalive);
/* 回前台:按 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);
/* 用户终止后台保活 App(走既有 SIGTERM→SIGKILL 真退序列)。调用者:后台管理页行内「杀」/
* 「一键清除」+ launcher IPC MSG_AMS_KILL(`am kill`)——两入口同通道,页面与面板/CLI 行为对称。
* 置 e->term_at_ms=now 由 meow_bg_tick 续做 SIGTERM→TERM_GRACE_MS→SIGKILL→reap→on_bg_dead→摘表。
* 返回 0=已受理(在后台表且 pid>0);MEOW_ERR_NOENT=不在后台/无进程;MEOW_ERR_INVAL=app_id 空。 */
int meow_bg_terminate(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 清槽)。
* 终止触发(SIGTERM,TERM_GRACE_MS 后 SIGKILL):
* - 卡拔例外:on_card 槽在卡不在时终止(防 SIGBUS);
* - 批①+宽限 超时回收:未声明(keepalive=0)且媒体不活跃(media_resident=0)的槽,转后台超
* MEOW_BG_GRACE_MS 未再启动 → 回收(走真退序列 + 摘表 + 通知 amsd)。声明常驻/媒体活跃不清。 */
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 */