294 lines
8.4 KiB
Python
294 lines
8.4 KiB
Python
"""
|
|
Stop command implementation for WiFi-DensePose API
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import signal
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from src.config.settings import Settings
|
|
from src.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
async def stop_command(
|
|
settings: Settings,
|
|
force: bool = False,
|
|
timeout: int = 30
|
|
) -> None:
|
|
"""Stop the WiFi-DensePose API server."""
|
|
|
|
logger.info("Stopping WiFi-DensePose API server...")
|
|
|
|
# Get server status
|
|
status = get_server_status(settings)
|
|
|
|
if not status["running"]:
|
|
if status["pid_file_exists"]:
|
|
logger.info("Server is not running, but PID file exists. Cleaning up...")
|
|
_cleanup_pid_file(settings)
|
|
else:
|
|
logger.info("Server is not running")
|
|
return
|
|
|
|
pid = status["pid"]
|
|
logger.info(f"Found running server with PID {pid}")
|
|
|
|
try:
|
|
if force:
|
|
await _force_stop_server(pid, settings)
|
|
else:
|
|
await _graceful_stop_server(pid, timeout, settings)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to stop server: {e}")
|
|
raise
|
|
|
|
|
|
async def _graceful_stop_server(pid: int, timeout: int, settings: Settings) -> None:
|
|
"""Stop server gracefully with timeout."""
|
|
|
|
logger.info(f"Attempting graceful shutdown (timeout: {timeout}s)...")
|
|
|
|
try:
|
|
# Send SIGTERM for graceful shutdown
|
|
os.kill(pid, signal.SIGTERM)
|
|
logger.info("Sent SIGTERM signal")
|
|
|
|
# Wait for process to terminate
|
|
start_time = time.time()
|
|
while time.time() - start_time < timeout:
|
|
try:
|
|
# Check if process is still running
|
|
os.kill(pid, 0)
|
|
await asyncio.sleep(1)
|
|
except OSError:
|
|
# Process has terminated
|
|
logger.info("Server stopped gracefully")
|
|
_cleanup_pid_file(settings)
|
|
return
|
|
|
|
# Timeout reached, force kill
|
|
logger.warning(f"Graceful shutdown timeout ({timeout}s) reached, forcing stop...")
|
|
await _force_stop_server(pid, settings)
|
|
|
|
except OSError as e:
|
|
if e.errno == 3: # No such process
|
|
logger.info("Process already terminated")
|
|
_cleanup_pid_file(settings)
|
|
else:
|
|
logger.error(f"Failed to send signal to process {pid}: {e}")
|
|
raise
|
|
|
|
|
|
async def _force_stop_server(pid: int, settings: Settings) -> None:
|
|
"""Force stop server immediately."""
|
|
|
|
logger.info("Force stopping server...")
|
|
|
|
try:
|
|
# Send SIGKILL for immediate termination
|
|
os.kill(pid, signal.SIGKILL)
|
|
logger.info("Sent SIGKILL signal")
|
|
|
|
# Wait a moment for process to die
|
|
await asyncio.sleep(2)
|
|
|
|
# Verify process is dead
|
|
try:
|
|
os.kill(pid, 0)
|
|
logger.error(f"Process {pid} still running after SIGKILL")
|
|
except OSError:
|
|
logger.info("Server force stopped")
|
|
|
|
except OSError as e:
|
|
if e.errno == 3: # No such process
|
|
logger.info("Process already terminated")
|
|
else:
|
|
logger.error(f"Failed to force kill process {pid}: {e}")
|
|
raise
|
|
|
|
finally:
|
|
_cleanup_pid_file(settings)
|
|
|
|
|
|
def _cleanup_pid_file(settings: Settings) -> None:
|
|
"""Clean up PID file."""
|
|
|
|
pid_file = Path(settings.log_directory) / "wifi-densepose-api.pid"
|
|
|
|
if pid_file.exists():
|
|
try:
|
|
pid_file.unlink()
|
|
logger.info("Cleaned up PID file")
|
|
except Exception as e:
|
|
logger.warning(f"Failed to remove PID file: {e}")
|
|
|
|
|
|
def get_server_status(settings: Settings) -> dict:
|
|
"""Get current server status."""
|
|
|
|
pid_file = Path(settings.log_directory) / "wifi-densepose-api.pid"
|
|
|
|
status = {
|
|
"running": False,
|
|
"pid": None,
|
|
"pid_file": str(pid_file),
|
|
"pid_file_exists": pid_file.exists(),
|
|
}
|
|
|
|
if pid_file.exists():
|
|
try:
|
|
with open(pid_file, 'r') as f:
|
|
pid = int(f.read().strip())
|
|
|
|
status["pid"] = pid
|
|
|
|
# Check if process is running
|
|
try:
|
|
os.kill(pid, 0) # Signal 0 just checks if process exists
|
|
status["running"] = True
|
|
except OSError:
|
|
# Process doesn't exist
|
|
status["running"] = False
|
|
|
|
except (ValueError, IOError):
|
|
# Invalid PID file
|
|
status["running"] = False
|
|
|
|
return status
|
|
|
|
|
|
async def stop_all_background_tasks(settings: Settings) -> None:
|
|
"""Stop all background tasks if they're running."""
|
|
|
|
logger.info("Stopping background tasks...")
|
|
|
|
try:
|
|
# This would typically involve connecting to a task queue or
|
|
# sending signals to background processes
|
|
# For now, we'll just log the action
|
|
|
|
logger.info("Background tasks stop signal sent")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to stop background tasks: {e}")
|
|
|
|
|
|
async def cleanup_resources(settings: Settings) -> None:
|
|
"""Clean up system resources."""
|
|
|
|
logger.info("Cleaning up resources...")
|
|
|
|
try:
|
|
# Close database connections
|
|
from src.database.connection import get_database_manager
|
|
|
|
db_manager = get_database_manager(settings)
|
|
await db_manager.close_all_connections()
|
|
logger.info("Database connections closed")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Failed to close database connections: {e}")
|
|
|
|
try:
|
|
# Clean up temporary files
|
|
temp_files = [
|
|
Path(settings.log_directory) / "temp",
|
|
Path(settings.backup_directory) / "temp",
|
|
]
|
|
|
|
for temp_path in temp_files:
|
|
if temp_path.exists() and temp_path.is_dir():
|
|
import shutil
|
|
shutil.rmtree(temp_path)
|
|
logger.info(f"Cleaned up temporary directory: {temp_path}")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Failed to clean up temporary files: {e}")
|
|
|
|
logger.info("Resource cleanup completed")
|
|
|
|
|
|
def is_server_running(settings: Settings) -> bool:
|
|
"""Check if server is currently running."""
|
|
|
|
status = get_server_status(settings)
|
|
return status["running"]
|
|
|
|
|
|
def get_server_pid(settings: Settings) -> Optional[int]:
|
|
"""Get server PID if running."""
|
|
|
|
status = get_server_status(settings)
|
|
return status["pid"] if status["running"] else None
|
|
|
|
|
|
async def wait_for_server_stop(settings: Settings, timeout: int = 30) -> bool:
|
|
"""Wait for server to stop with timeout."""
|
|
|
|
start_time = time.time()
|
|
|
|
while time.time() - start_time < timeout:
|
|
if not is_server_running(settings):
|
|
return True
|
|
await asyncio.sleep(1)
|
|
|
|
return False
|
|
|
|
|
|
def send_reload_signal(settings: Settings) -> bool:
|
|
"""Send reload signal to running server."""
|
|
|
|
status = get_server_status(settings)
|
|
|
|
if not status["running"]:
|
|
logger.error("Server is not running")
|
|
return False
|
|
|
|
try:
|
|
# Send SIGHUP for reload
|
|
os.kill(status["pid"], signal.SIGHUP)
|
|
logger.info("Sent reload signal to server")
|
|
return True
|
|
|
|
except OSError as e:
|
|
logger.error(f"Failed to send reload signal: {e}")
|
|
return False
|
|
|
|
|
|
async def restart_server(settings: Settings, timeout: int = 30) -> None:
|
|
"""Restart the server (stop then start)."""
|
|
|
|
logger.info("Restarting server...")
|
|
|
|
# Stop server if running
|
|
if is_server_running(settings):
|
|
await stop_command(settings, timeout=timeout)
|
|
|
|
# Wait for server to stop
|
|
if not await wait_for_server_stop(settings, timeout):
|
|
logger.error("Server did not stop within timeout, forcing restart")
|
|
await stop_command(settings, force=True)
|
|
|
|
# Start server
|
|
from src.commands.start import start_command
|
|
await start_command(settings)
|
|
|
|
|
|
def get_stop_status_summary(settings: Settings) -> dict:
|
|
"""Get a summary of stop operation status."""
|
|
|
|
status = get_server_status(settings)
|
|
|
|
return {
|
|
"server_running": status["running"],
|
|
"pid": status["pid"],
|
|
"pid_file_exists": status["pid_file_exists"],
|
|
"can_stop": status["running"],
|
|
"cleanup_needed": status["pid_file_exists"] and not status["running"],
|
|
} |