175 lines
7.4 KiB
Python
175 lines
7.4 KiB
Python
import asyncio
|
|
import threading
|
|
import time
|
|
import atexit
|
|
import signal
|
|
import sys
|
|
import platform
|
|
|
|
import server
|
|
|
|
from ..utils.config import load_config, save_config
|
|
from ..utils.logging import debug_log, log
|
|
from ..utils.network import normalize_host
|
|
from ..utils.cloudflare import cloudflare_tunnel_manager
|
|
from ..utils.constants import WORKER_STARTUP_DELAY
|
|
from . import get_worker_manager
|
|
|
|
|
|
def auto_launch_workers():
|
|
"""Launch enabled workers if auto_launch_workers is set to true."""
|
|
wm = get_worker_manager()
|
|
try:
|
|
config = load_config()
|
|
if config.get('settings', {}).get('auto_launch_workers', False):
|
|
log("Auto-launch workers is enabled, checking for workers to start...")
|
|
|
|
# Clear managed_processes before launching new workers
|
|
# This handles cases where the master was killed without proper cleanup
|
|
if config.get('managed_processes'):
|
|
log("Clearing old managed_processes before auto-launch...")
|
|
config['managed_processes'] = {}
|
|
save_config(config)
|
|
|
|
workers = config.get('workers', [])
|
|
launched_count = 0
|
|
|
|
for worker in workers:
|
|
if worker.get('enabled', False):
|
|
worker_id = worker.get('id')
|
|
worker_name = worker.get('name', f'Worker {worker_id}')
|
|
|
|
# Skip remote workers
|
|
host = (normalize_host(worker.get('host', 'localhost')) or 'localhost').lower()
|
|
if host not in ['localhost', '127.0.0.1', '', None]:
|
|
debug_log(f"Skipping remote worker {worker_name} (host: {host})")
|
|
continue
|
|
|
|
# Check if already running
|
|
if str(worker_id) in wm.processes:
|
|
proc_info = wm.processes[str(worker_id)]
|
|
if wm._is_process_running(proc_info['pid']):
|
|
debug_log(f"Worker {worker_name} already running, skipping")
|
|
continue
|
|
|
|
# Launch the worker
|
|
try:
|
|
pid = wm.launch_worker(worker)
|
|
log(f"Auto-launched worker {worker_name} (PID: {pid})")
|
|
|
|
# Mark as launching in managed processes
|
|
if str(worker_id) in wm.processes:
|
|
wm.processes[str(worker_id)]['launching'] = True
|
|
wm.save_processes()
|
|
|
|
launched_count += 1
|
|
except Exception as e:
|
|
log(f"Failed to auto-launch worker {worker_name}: {e}")
|
|
|
|
if launched_count > 0:
|
|
log(f"Auto-launched {launched_count} worker(s)")
|
|
else:
|
|
debug_log("No workers to auto-launch")
|
|
else:
|
|
debug_log("Auto-launch workers is disabled")
|
|
except Exception as e:
|
|
log(f"Error during auto-launch: {e}")
|
|
|
|
# Schedule auto-launch after a short delay to ensure server is ready
|
|
def delayed_auto_launch():
|
|
"""Delay auto-launch to ensure server is fully initialized."""
|
|
import threading
|
|
timer = threading.Timer(WORKER_STARTUP_DELAY, auto_launch_workers)
|
|
timer.daemon = True
|
|
timer.start()
|
|
|
|
# Async cleanup function for proper shutdown
|
|
async def async_cleanup_and_exit(signum=None):
|
|
"""Async-friendly cleanup and exit."""
|
|
wm = get_worker_manager()
|
|
try:
|
|
config = load_config()
|
|
if config.get('settings', {}).get('stop_workers_on_master_exit', True):
|
|
print("\n[Distributed] Master shutting down, stopping all managed workers...")
|
|
wm.cleanup_all()
|
|
else:
|
|
print("\n[Distributed] Master shutting down, workers will continue running")
|
|
wm.save_processes()
|
|
try:
|
|
await cloudflare_tunnel_manager.stop_tunnel()
|
|
except Exception as tunnel_error:
|
|
log(f"[Distributed] Warning: Cloudflare tunnel did not stop cleanly during shutdown: {tunnel_error}")
|
|
except Exception as e:
|
|
print(f"[Distributed] Error during cleanup: {e}")
|
|
|
|
# On Windows, we need to exit differently
|
|
if platform.system() == "Windows":
|
|
# Force exit on Windows
|
|
sys.exit(0)
|
|
else:
|
|
# On Unix, stop the event loop gracefully
|
|
loop = asyncio.get_running_loop()
|
|
loop.stop()
|
|
|
|
def register_async_signals():
|
|
"""Register async signal handlers for graceful shutdown."""
|
|
wm = get_worker_manager()
|
|
if platform.system() == "Windows":
|
|
# Windows doesn't support add_signal_handler, use traditional signal handling
|
|
def signal_handler(signum, frame):
|
|
# Schedule the async cleanup in the event loop
|
|
loop = server.PromptServer.instance.loop
|
|
if loop and loop.is_running():
|
|
asyncio.run_coroutine_threadsafe(async_cleanup_and_exit(signum), loop)
|
|
else:
|
|
# Fallback to sync cleanup if loop isn't running
|
|
try:
|
|
config = load_config()
|
|
if config.get('settings', {}).get('stop_workers_on_master_exit', True):
|
|
print("\n[Distributed] Master shutting down, stopping all managed workers...")
|
|
wm.cleanup_all()
|
|
else:
|
|
print("\n[Distributed] Master shutting down, workers will continue running")
|
|
wm.save_processes()
|
|
except Exception as e:
|
|
print(f"[Distributed] Error during cleanup: {e}")
|
|
sys.exit(0)
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
else:
|
|
# Unix-like systems support add_signal_handler
|
|
loop = server.PromptServer.instance.loop
|
|
|
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
loop.add_signal_handler(sig, lambda s=sig: asyncio.create_task(async_cleanup_and_exit(s)))
|
|
|
|
# SIGHUP is Unix-only
|
|
loop.add_signal_handler(signal.SIGHUP, lambda: asyncio.create_task(async_cleanup_and_exit(signal.SIGHUP)))
|
|
|
|
def sync_cleanup():
|
|
"""Synchronous wrapper for atexit."""
|
|
wm = get_worker_manager()
|
|
try:
|
|
# For atexit, we don't want to stop the loop or exit
|
|
config = load_config()
|
|
if config.get('settings', {}).get('stop_workers_on_master_exit', True):
|
|
print("\n[Distributed] Master shutting down, stopping all managed workers...")
|
|
wm.cleanup_all()
|
|
else:
|
|
print("\n[Distributed] Master shutting down, workers will continue running")
|
|
wm.save_processes()
|
|
try:
|
|
loop = asyncio.get_event_loop()
|
|
if loop.is_running():
|
|
loop.create_task(cloudflare_tunnel_manager.stop_tunnel())
|
|
else:
|
|
loop.run_until_complete(cloudflare_tunnel_manager.stop_tunnel())
|
|
except RuntimeError:
|
|
# No running loop; create a temporary one
|
|
asyncio.run(cloudflare_tunnel_manager.stop_tunnel())
|
|
except Exception as tunnel_error:
|
|
log(f"[Distributed] Warning: Cloudflare tunnel did not stop cleanly during sync cleanup: {tunnel_error}")
|
|
except Exception as e:
|
|
print(f"[Distributed] Error during cleanup: {e}")
|