video_monitor/monitor_runtime/service.py

86 lines
3.8 KiB
Python
Raw Normal View History

2026-09-04 18:16:14 +08:00
"""One Waitress process owns the background lifecycle and analysis state."""
import json
import logging
import os
import socket
import threading
import time
from monitor_runtime.paths import data, resource, atomic_json
from monitor_runtime.bootstrap import prepare_files, initialize_database, has_admin
from monitor_runtime.licensing import check_license
logger = logging.getLogger('monitor.runtime')
def run(connection):
server = None
manager = None
ready = False
try:
prepare_files()
initialize_database()
from monitor_runtime import web
from django.core.wsgi import get_wsgi_application
from whitenoise import WhiteNoise
from waitress import create_server
from app.utils.GlobalUtils import g_config
from app.services.lifecycle import get_service_manager
manager = get_service_manager()
licensed = check_license()['valid']
initialized = has_admin()
port = g_config.adminPort
app = WhiteNoise(get_wsgi_application(), root=str(resource('public-static')), prefix='static/')
address = '0.0.0.0' if initialized and licensed else '127.0.0.1'
server = create_server(app, host=address, port=port, threads=8, channel_timeout=60,
max_request_body_size=536870912)
thread = threading.Thread(target=server.run, name='web-server', daemon=True)
thread.start()
if licensed and initialized:
if not manager.start():
raise RuntimeError('后台服务已被其他进程占用')
phase = 'ready' if licensed and initialized else 'setup' if not initialized else 'license'
web.SERVICE_STATE.update(phase=phase, reason=check_license()['reason'])
url = 'http://127.0.0.1:%d/%s' % (port, 'login' if phase == 'ready' else phase)
if phase == 'setup':
url += '#' + web.SETUP_TOKEN
connection.send({'phase': phase, 'url': url, 'reason': web.SERVICE_STATE['reason']})
ready = True
last_check = time.monotonic()
failures = 0
while thread.is_alive():
if connection.poll(1):
message = connection.recv()
if message == 'stop':
return 0
if web.RESTART_REQUESTED.is_set():
return 20
if time.monotonic() - last_check >= 5:
last_check = time.monotonic()
status = check_license()
if not status['valid'] and manager.is_leader:
web.SERVICE_STATE.update(phase='license', reason=status['reason'])
connection.send({'phase': 'license', 'url': 'http://127.0.0.1:%d/license' % port,
'reason': status['reason']})
# Restart loopback-only for renewal and reliably discard all worker descendants.
return 20
if manager.is_leader:
from app.utils.MediaServerManager import get_media_server_manager
media = get_media_server_manager()
failures = 0 if media.managed_pid() and media.api_alive() else failures + 1
if failures >= 3:
raise RuntimeError('ZLM 健康检查失败;请查看日志并从托盘重启')
connection.send({'phase': web.SERVICE_STATE['phase'], 'heartbeat': True})
raise RuntimeError('Web 服务意外退出')
except Exception as exc:
logger.exception('Desktop service failed')
try:
connection.send({'phase': 'failed', 'reason': str(exc)})
except (EOFError, OSError):
pass
return 1
finally:
if manager:
manager.stop()
if server:
server.close()
connection.close()