"""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()