- Dispatcher limit=10→1: 一次只传一个视频,传完再传下一个 - 退避缩短: min(30*(n+1),300)s → 失败后更快重试(原 min(60*(n+1)*2,600)s) - /api/status: 用 thread.is_alive() 替代 _running 布尔标志 - 三组件(scheduler/dispatcher/poller)添加 is_alive()+check_and_restart() - app.py 新增 watchdog 线程: 每 60s 检测线程死亡并自动重启
98 lines
2.8 KiB
Python
98 lines
2.8 KiB
Python
"""
|
|
FAM-Core 主应用 - Flask 单进程
|
|
|
|
承载: Task-Scheduler / Dispatcher / Poller / Event-Receiver / Chat-Handler / Member-Manager / Video-Server
|
|
"""
|
|
import os
|
|
import sys
|
|
import time
|
|
import threading
|
|
from flask import Flask, jsonify
|
|
|
|
# 确保包路径
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
from .config_loader import load_config
|
|
from .logger import setup_logger
|
|
from .scheduler.scheduler import TaskScheduler
|
|
from .dispatcher.dispatcher import Dispatcher
|
|
from .poller.poller import Poller
|
|
from .event_receiver.event_receiver import event_bp
|
|
from .chat_handler.chat_handler import chat_bp
|
|
from .member_manager.member_manager import member_bp
|
|
from .video_server.video_server import video_bp
|
|
|
|
logger = setup_logger('fam-core.app')
|
|
|
|
app = Flask(__name__)
|
|
|
|
# 注册蓝图
|
|
app.register_blueprint(event_bp)
|
|
app.register_blueprint(chat_bp)
|
|
app.register_blueprint(member_bp)
|
|
app.register_blueprint(video_bp)
|
|
|
|
# 健康检查
|
|
@app.route('/health', methods=['GET'])
|
|
def health():
|
|
return jsonify({"status": "ok", "service": "fam-core"}), 200
|
|
|
|
# 初始化后台线程
|
|
_scheduler = None
|
|
_dispatcher = None
|
|
_poller = None
|
|
|
|
try:
|
|
_scheduler = TaskScheduler()
|
|
_scheduler.start()
|
|
logger.info("Task-Scheduler 已启动")
|
|
except Exception as e:
|
|
logger.error(f"Task-Scheduler 启动失败: {e}")
|
|
|
|
try:
|
|
_dispatcher = Dispatcher()
|
|
_dispatcher.start()
|
|
logger.info("Dispatcher 已启动")
|
|
except Exception as e:
|
|
logger.error(f"Dispatcher 启动失败: {e}")
|
|
|
|
try:
|
|
_poller = Poller()
|
|
_poller.start()
|
|
logger.info("Poller 已启动")
|
|
except Exception as e:
|
|
logger.error(f"Poller 启动失败: {e}")
|
|
|
|
|
|
@app.route('/api/status', methods=['GET'])
|
|
def status():
|
|
"""系统状态(检查线程实际存活)"""
|
|
return jsonify({
|
|
"scheduler_running": _scheduler.is_alive() if _scheduler else False,
|
|
"dispatcher_running": _dispatcher.is_alive() if _dispatcher else False,
|
|
"poller_running": _poller.is_alive() if _poller else False,
|
|
}), 200
|
|
|
|
|
|
def _watchdog_run():
|
|
"""看门狗:每 60s 检查线程存活,崩溃自动重启"""
|
|
logger.info("Watchdog 线程启动,检查间隔 60s")
|
|
while True:
|
|
time.sleep(60)
|
|
for comp, name in [(_scheduler, 'Scheduler'), (_dispatcher, 'Dispatcher'), (_poller, 'Poller')]:
|
|
if comp and hasattr(comp, 'check_and_restart'):
|
|
try:
|
|
comp.check_and_restart()
|
|
except Exception as e:
|
|
logger.error(f"Watchdog 重启 {name} 失败: {e}", exc_info=True)
|
|
|
|
|
|
_watchdog_thread = threading.Thread(target=_watchdog_run, daemon=True, name='watchdog')
|
|
_watchdog_thread.start()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
cfg = load_config()
|
|
port = cfg.get('server', {}).get('port', 8000)
|
|
app.run(host='0.0.0.0', port=port, debug=False)
|